Есть ли в SQL Server функция Max, которая принимает два значения, такие как Math.Max ​​в .NET?


488

Я хочу написать запрос так:

SELECT o.OrderId, MAX(o.NegotiatedPrice, o.SuggestedPrice)
FROM Order o

Но это не так, как MAXработает функция, верно? Это агрегатная функция, поэтому она ожидает один параметр, а затем возвращает MAX всех строк.

Кто-нибудь знает, как это сделать по-моему?


13
Это реализовано в большинстве других баз данных как GREATESTфункция; SQLite эмулирует поддержку, позволяя использовать несколько столбцов в MAXсовокупности.
OMG Ponies


При поиске решения для max (a, b) ниже, имейте в виду вопрос о том, хотите ли вы, чтобы синтаксис или расчет для «a» и / или «b» повторялись. Т.е. если «b» получено из сложного вычисления, включающего много синтаксиса, тогда вы можете предпочесть решение, где «b» появляется только один раз. Например, решение «IIF (a> b, a, b)» означает повторение «b», что может быть синтаксически уродливо, однако следующее решение означает, что «b» (и «a») появляются только один раз: SELECT MAX (VALUE) ОТ (ВЫБЕРИТЕ СО ЦЕННОСТЬЮ СОЮЗ ВЫБРАТЬ b КАК СТОИМОСТЬ) КАК T1
Эндрю Дженс

Ответы:


158

Вам нужно было бы сделать, User-Defined Functionесли бы вы хотели, чтобы синтаксис был похож на ваш пример, но вы могли бы делать то, что вы хотите, встроенные, довольно легко с CASEзаявлением, как сказали другие.

UDFМожет быть что - то вроде этого:

create function dbo.InlineMax(@val1 int, @val2 int)
returns int
as
begin
  if @val1 > @val2
    return @val1
  return isnull(@val2,@val1)
end

... и вы бы назвали это так ...

SELECT o.OrderId, dbo.InlineMax(o.NegotiatedPrice, o.SuggestedPrice) 
FROM Order o

25
Я бы поддержал ваше решение, единственное, что я бы добавил, - это поддержка значений NULL. Если вы просто измените последнюю строку: «return @ value2», чтобы она читалась как: «return isnull (@ val2, @ val1)», то если одно из значений будет нулевым, функция вернет ненулевое значение, в противном случае она будет работать как нормальный
Кристоф

1
Как насчет других типов данных, например, мне нужно написать HigherIntegerArgument и HigherDateTimeArgument и HigherVarcharArgument и ...?
понедельник,

9
это будет невероятно медленно, как и все скалярные UDF. Вместо этого используйте встроенные UDF
AK

12
@xan Я понятия не имею, что пришло мне в голову, когда я на самом деле задал этот вопрос. Не слишком много, очевидно. В любом случае, спасибо за ответ.
Томас

13
@Thomas Обязательное изображение мема (без обид!) Flickr.com/photos/16201371@N00/2375571206
xan

468

Если вы используете SQL Server 2008 (или выше), то это лучшее решение:

SELECT o.OrderId,
       (SELECT MAX(Price)
        FROM (VALUES (o.NegotiatedPrice),(o.SuggestedPrice)) AS AllPrices(Price))
FROM Order o

Все кредиты и голоса должны идти к ответу Свена на связанный вопрос: «SQL MAX из нескольких столбцов?»
Я говорю, что это « лучший ответ », потому что:

  1. Это не требует усложнения вашего кода с помощью UNION, PIVOT, UNPIVOT, UDF и сумасшедших длинных CASE.
  2. Проблема не связана с обработкой нулей, она прекрасно справляется с ними.
  3. Легко поменять "MAX" на "MIN", "AVG" или "SUM". Вы можете использовать любую функцию агрегирования, чтобы найти агрегат по множеству различных столбцов.
  4. Вы не ограничены именами, которые я использовал (например, «AllPrices» и «Price»). Вы можете выбрать свои собственные имена, чтобы было легче читать и понимать для следующего парня.
  5. Вы можете найти несколько агрегатов, используя производные таблицы SQL Server 2008, например:
    SELECT MAX (a), MAX (b) FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)) AS MyTable (a, b)

27
+1 единственный ответ, который не требует доступа для создания процедуры / функции!
Алекс

6
Именно тот ответ, который я искал. Использование функций медленное, и это также будет работать с датами, что мне и нужно.
Иоганн Стридом

3
+1 Отлично работает, особенно для сравнения более двух столбцов!
январь

11
Это менее производительно, чем решение CASE WHEN, которое требует только вычисления скаляра.
Текумара

5
Хотя более простой синтаксис может никогда не стоить снижения производительности при определении значения MAX 2, это может быть другой вопрос с большим количеством значений. Даже при получении значения MAX из 4 предложения CASE становятся длинными, неуклюжими и подверженными ошибкам, если генерируются вручную, в то время как предложение VALUES остается простым и понятным.
Тифлозавр

221

Можно сделать в одну строку:

-- the following expression calculates ==> max(@val1, @val2)
SELECT 0.5 * ((@val1 + @val2) + ABS(@val1 - @val2)) 

Изменить: Если вы имеете дело с очень большими числами, вам придется преобразовать переменные значения в bigint, чтобы избежать переполнения целых чисел.


18
+1 Я считаю, что вы предоставили самый правильный путь. "SELECT ((@ val1 + @ val2) + ABS (@ val1- @ val2)) / 2 как MAX_OF_TWO" Также помните, "SELECT ((@ val1 + @ val2) - ABS (@ val1- @ val2)) / 2 как MIN_OF_TWO ».
Том

6
Этот способ выдаст ошибку переполнения, если сумма будет больше, чем может быть сохранена в целочисленном типе: объявить @ val1 int объявить @ val2 int установить @ val1 = 1500000000 set @ val2 = 1500000000 SELECT 0.5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2)) - => ошибка переполнения
AakashM

89
Это чрезвычайно «грязный» «трюк». При программировании ваш код должен явно выражать цель, однако в вашем случае это выглядит как код, взятый из конкурса запутывания.
Гринольдман

24
Он может быть «грязным», но это может быть единственным вариантом для баз данных с простыми диалектами SQL.
splattne

12
Я не согласен с Марсиасом. Код сам по себе не обязательно должен явно выражать цель, если комментарии позволяют ее решить. Если вы выполняете какие-либо сложные математические уравнения в коде (или где-либо еще), иногда трудно сделать его информативным. Пока это разбито на более простые, более понятные части, то это правильное программирование.
Роб

127

Я так не думаю. Я хотел этого на днях. Самое близкое, что я получил, было:

SELECT
  o.OrderId,
  CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN o.NegotiatedPrice 
     ELSE o.SuggestedPrice
  END
FROM Order o

4
Это мой любимый метод. Вы не рискуете переполнением, и это менее загадочно, чем решение splattne (что круто, кстати), и у меня нет проблем с созданием UDF. дело очень удобно во многих ситуациях.
Лэнс Фишер

ВЫБЕРИТЕ o.OrderId, СЛУЧАЙ, КОГДА o.NegotiatedPrice> o.SuggestedPrice ИЛИ o.SuggestedPrice НЕДОСТУПНО, ТО o oNegotiatedPrice ELSE o.SuggestedPrice КОНЕЦ ОТ заказа o
mohghaderi

Когда вместо «o.NegotiatedPrice» у вас есть скорее термин «» (datediff (day, convert (datetime, adr_known_since, 120), getdate ()) - 5) * 0.3 «, вы должны повторить этот код. Любые будущие изменения в термине должны быть сделаны дважды. Функция типа min (x, y, ...) была бы намного лучше
Даниэль

87

Почему бы не попробовать функцию IIF (требуется SQL Server 2012 и более поздние версии)

IIF(a>b, a, b)

Вот и все.

(Подсказка: будьте осторожны с любым из них null, так как результат a>bбудет ложным, когда любой из них равен нулю. Так bбудет и в этом случае)


7
Если одно из значений равно NULL, результатом всегда будет второе.
Джаху

4
IIF () является синтаксическим сахаром для оператора CASE. Если какое-либо значение условия CASE равно NULL, результат будет вторым (ELSE).
xxyzzy

@xxyzzy, потому что NULL > 1234утверждение ложно
Xin

8
таким образом, IIF(a>b, a, COALESCE(b,a))чтобы дать значение, когда существует только один
mpag

32
DECLARE @MAX INT
@MAX = (SELECT MAX(VALUE) 
               FROM (SELECT 1 AS VALUE UNION 
                     SELECT 2 AS VALUE) AS T1)

Я даю этому решению +1, потому что оно соответствует DRY (не повторяйся) без необходимости писать UDF. Также замечательно, если оба значения, которые вы должны проверить, являются результатами других sql, например, в моем случае я хочу найти большее из 2 операторов select count (*).
MikeKulls

1
Я ненавижу прибегать к этому решению, но наверняка это лучший способ сделать это в SQL Server, пока они не добавят встроенную поддержку GREATEST или встроенного MAX. Спасибо за публикацию - +1 Вам!
SqlRyan

10

Другие ответы хороши, но если вам нужно беспокоиться о значениях NULL, вам может потребоваться этот вариант:

SELECT o.OrderId, 
   CASE WHEN ISNULL(o.NegotiatedPrice, o.SuggestedPrice) > ISNULL(o.SuggestedPrice, o.NegotiatedPrice)
        THEN ISNULL(o.NegotiatedPrice, o.SuggestedPrice)
        ELSE ISNULL(o.SuggestedPrice, o.NegotiatedPrice)
   END
FROM Order o

1
Единственный требуемый ISNULL - после ELSE. Первоначальное сравнение «>» вернет false и перейдет к ELSE, если любое из значений уже равно null.
Фил Б.

10

В SQL Server 2012 или более поздней версии вы можете использовать комбинацию IIFи ISNULL(или COALESCE), чтобы получить максимум 2 значения.
Даже когда 1 из них равен NULL.

IIF(col1 >= col2, col1, ISNULL(col2, col1)) 

Или, если вы хотите, чтобы он возвращал 0, когда оба NULL

IIF(col1 >= col2, col1, COALESCE(col2, col1, 0)) 

Пример фрагмента:

-- use table variable for testing purposes
declare @Order table 
(
  OrderId int primary key identity(1,1),
  NegotiatedPrice decimal(10,2),
  SuggestedPrice decimal(10,2)
);

-- Sample data
insert into @Order (NegotiatedPrice, SuggestedPrice) values
(0, 1),
(2, 1),
(3, null),
(null, 4);

-- Query
SELECT 
     o.OrderId, o.NegotiatedPrice, o.SuggestedPrice, 
     IIF(o.NegotiatedPrice >= o.SuggestedPrice, o.NegotiatedPrice, ISNULL(o.SuggestedPrice, o.NegotiatedPrice)) AS MaxPrice
FROM @Order o

Результат:

OrderId NegotiatedPrice SuggestedPrice  MaxPrice
1       0,00            1,00            1,00
2       2,00            1,00            2,00
3       3,00            NULL            3,00
4       NULL            4,00            4,00

Но если нужно суммировать несколько значений?
Тогда я предлагаю ПЕРЕКРЕСТИТЬ ПРИМЕНЕНИЕ к совокупности ЗНАЧЕНИЙ.
Это также имеет то преимущество, что он может рассчитывать другие вещи одновременно.

Пример:

SELECT t.*
, ca.[Total]
, ca.[Maximum]
, ca.[Minimum]
, ca.[Average]
FROM SomeTable t
CROSS APPLY (
   SELECT 
    SUM(v.col) AS [Total], 
    MIN(v.col) AS [Minimum], 
    MAX(v.col) AS [Maximum], 
    AVG(v.col) AS [Average]
   FROM (VALUES (t.Col1), (t.Col2), (t.Col3), (t.Col4)) v(col)
) ca

8

Подзапросы могут получать доступ к столбцам из внешнего запроса, поэтому вы можете использовать этот подход для использования агрегатов, например, MAXпо столбцам. (Возможно, более полезно, когда задействовано большее количество столбцов)

;WITH [Order] AS
(
SELECT 1 AS OrderId, 100 AS NegotiatedPrice, 110 AS SuggestedPrice UNION ALL
SELECT 2 AS OrderId, 1000 AS NegotiatedPrice, 50 AS SuggestedPrice
)
SELECT
       o.OrderId, 
       (SELECT MAX(price)FROM 
           (SELECT o.NegotiatedPrice AS price 
            UNION ALL SELECT o.SuggestedPrice) d) 
        AS MaxPrice 
FROM  [Order]  o

Ницца! Это очень хорошо масштабируется.
Гринольдман

+1, чтобы показать Любовь для тех, кто еще в 2005 году. Я не знаю, как я пропустил этот ответ. Под прикрытием я представляю, что это работает так же хорошо, как я опубликовал 2 года спустя. Оглядываясь назад, я должен был понять это и обновить ваш ответ, включив в него более новый синтаксис 2008 года. Извините, если бы я мог поделиться своими очками с вами сейчас.
MikeTeeVee

@MikeTeeVee - Спасибо! Да, под одеялом план будет таким же. Но VALUESсинтаксис лучше.
Мартин Смит

6

SQL Server 2012 представил IIF:

SELECT 
    o.OrderId, 
    IIF( ISNULL( o.NegotiatedPrice, 0 ) > ISNULL( o.SuggestedPrice, 0 ),
         o.NegotiatedPrice, 
         o.SuggestedPrice 
    )
FROM 
    Order o

При использовании рекомендуется использовать NULL IIF, потому что a NULLс любой стороны от вас boolean_expressionвызовет IIFвозврат false_value(в отличие от NULL).


Ваше решение не будет хорошо обрабатывать NULL, когда другое значение будет отрицательным, оно вернет null
t-clausen.dk

5

Я хотел бы пойти с решением, предоставленным kcrumley Просто измените его немного, чтобы обрабатывать NULL

create function dbo.HigherArgumentOrNull(@val1 int, @val2 int)
returns int
as
begin
  if @val1 >= @val2
    return @val1
  if @val1 < @val2
    return @val2

 return NULL
end

РЕДАКТИРОВАТЬ Изменено после комментария от Марка . Как он правильно указал в трехзначной логике, x> NULL или x <NULL всегда должны возвращать NULL. Другими словами, неизвестный результат.


1
Нули важны. И важно обращаться с ними последовательно. Единственный правильный ответ - NULL> x - NULL.
Марк Брэкетт

Вы правы, я изменю свой ответ, чтобы отразить это, спасибо за указание на это
Кристоф

Если мы передадим int и NULL, то я думаю, что более распространенным будет возвращение ненулевого значения, поэтому функция действует как комбинация Max (x, y) и ISNULL (x, y). Следовательно, я лично изменил бы последнюю строку: return ISNULL (@ val1, @ val2) - что по общему признанию, вероятно, является тем, с чего вам пришлось начинать :)
redcalx

@ the-locster, см. комментарий Марка
Кристоф

1
это будет невероятно медленно, как и все скалярные UDF. Вместо этого используйте встроенные UDF
AK

4

Это так просто:

CREATE FUNCTION InlineMax
(
    @p1 sql_variant,
    @p2 sql_variant
)  RETURNS sql_variant
AS
BEGIN
    RETURN CASE 
        WHEN @p1 IS NULL AND @p2 IS NOT NULL THEN @p2 
        WHEN @p2 IS NULL AND @p1 IS NOT NULL THEN @p1
        WHEN @p1 > @p2 THEN @p1
        ELSE @p2 END
END;

См. @Neil комментарий к предыдущему ответу. ВЫБРАТЬ dbo.InlineMax (CAST (0.5 AS FLOAT), 100) неверно.
Лука

4
SELECT o.OrderId,   
--MAX(o.NegotiatedPrice, o.SuggestedPrice)  
(SELECT MAX(v) FROM (VALUES (o.NegotiatedPrice), (o.SuggestedPrice)) AS value(v)) as ChoosenPrice  
FROM Order o

Для объяснения, пожалуйста, обратитесь к этой статье: red-gate.com/simple-talk/sql/sql-training/…
Том Арлет

2
Пожалуйста, не включайте необходимую информацию в ваш код просто по ссылке. Представьте, что срок действия этой ссылки истечет через день, и ваш ответ будет бесполезен. Поэтому, пожалуйста, добавьте необходимую информацию непосредственно в ваш ответ. Но вы все равно можете предоставить эту ссылку в качестве ресурса для других, чтобы искать дополнительную информацию.
Л. Гутардт

3

Ой, я только что опубликовал дупе этого вопроса ...

Ответ заключается в том, что нет встроенной функции, подобной Oracle Greatest , но вы можете достичь аналогичного результата для 2 столбцов с UDF, обратите внимание, что использование sql_variant здесь очень важно.

create table #t (a int, b int) 

insert #t
select 1,2 union all 
select 3,4 union all
select 5,2

-- option 1 - A case statement
select case when a > b then a else b end
from #t

-- option 2 - A union statement 
select a from #t where a >= b 
union all 
select b from #t where b > a 

-- option 3 - A udf
create function dbo.GREATEST
( 
    @a as sql_variant,
    @b as sql_variant
)
returns sql_variant
begin   
    declare @max sql_variant 
    if @a is null or @b is null return null
    if @b > @a return @b  
    return @a 
end


select dbo.GREATEST(a,b)
from #t

Кристофа

Разместил этот ответ:

create table #t (id int IDENTITY(1,1), a int, b int)
insert #t
select 1,2 union all
select 3,4 union all
select 5,2

select id, max(val)
from #t
    unpivot (val for col in (a, b)) as unpvt
group by id

1
Примечание: реализация самой БОЛЬШОЙ функции будет соответствовать поведению оракула для 2 параметров, если любой параметр имеет значение NULL, он вернет значение NULL
Сэм Саффрон,

2
Вы должны быть осторожны при использовании sql_variant. Ваша функция даст неожиданный результат в следующей ситуации: ВЫБЕРИТЕ dbo.greatest (CAST (0.5 AS FLOAT), 100)
Нил

@ Нил прав (я выучил это нелегко), как бы вы улучшили эту функцию, чтобы предотвратить подобные проблемы?
Лука

3

Вот пример случая, который должен обрабатывать пустые значения и работать со старыми версиями MSSQL. Это основано на встроенной функции в одном из популярных примеров:

case
  when a >= b then a
  else isnull(b,a)
end

2

Я, вероятно, не стал бы делать это таким образом, поскольку он менее эффективен, чем уже упомянутые конструкции CASE - если, возможно, у вас не было охватывающих индексов для обоих запросов. В любом случае, это полезный метод для подобных проблем:

SELECT OrderId, MAX(Price) as Price FROM (
   SELECT o.OrderId, o.NegotiatedPrice as Price FROM Order o
   UNION ALL
   SELECT o.OrderId, o.SuggestedPrice as Price FROM Order o
) as A
GROUP BY OrderId

2

Для ответа выше относительно больших чисел вы можете выполнить умножение перед сложением / вычитанием. Это немного громоздче, но не требует приведения. (Я не могу говорить о скорости, но я предполагаю, что это все еще довольно быстро)

ВЫБЕРИТЕ 0.5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2))

Изменения к

SELECT @ val1 * 0.5 + @ val2 * 0.5 + ABS (@ val1 * 0.5 - @ val2 * 0.5)

по крайней мере, альтернатива, если вы хотите избежать кастинга.


2

Вот версия IIF с обработкой NULL (на основе ответа Xin):

IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a > b, a, b))

Логика следующая: если любое из значений равно NULL, вернуть значение, которое не равно NULL (если оба значения равны NULL, возвращается NULL). В противном случае верните большее.

То же самое можно сделать для MIN.

IIF(a IS NULL OR b IS NULL, ISNULL(a,b), IIF(a < b, a, b))


1
SELECT o.OrderID
CASE WHEN o.NegotiatedPrice > o.SuggestedPrice THEN
 o.NegotiatedPrice
ELSE
 o.SuggestedPrice
END AS Price

1
CREATE FUNCTION [dbo].[fnMax] (@p1 INT, @p2 INT)
RETURNS INT
AS BEGIN

    DECLARE @Result INT

    SET @p2 = COALESCE(@p2, @p1)

    SELECT
        @Result = (
                   SELECT
                    CASE WHEN @p1 > @p2 THEN @p1
                         ELSE @p2
                    END
                  )

    RETURN @Result

END

1

В простейшем виде ...

CREATE FUNCTION fnGreatestInt (@Int1 int, @Int2 int )
RETURNS int
AS
BEGIN

    IF @Int1 >= ISNULL(@Int2,@Int1)
        RETURN @Int1
    ELSE
        RETURN @Int2

    RETURN NULL --Never Hit

END

1

Для SQL Server 2012:

SELECT 
    o.OrderId, 
    IIF( o.NegotiatedPrice >= o.SuggestedPrice,
         o.NegotiatedPrice, 
         ISNULL(o.SuggestedPrice, o.NegiatedPrice) 
    )
FROM 
    Order o

1

Вот ответ @Scott Langham с простой обработкой NULL:

SELECT
      o.OrderId,
      CASE WHEN (o.NegotiatedPrice > o.SuggestedPrice OR o.SuggestedPrice IS NULL) 
         THEN o.NegotiatedPrice 
         ELSE o.SuggestedPrice
      END As MaxPrice
FROM Order o

0
select OrderId, (
    select max([Price]) from (
        select NegotiatedPrice [Price]
        union all
        select SuggestedPrice
    ) p
) from [Order]

0
 -- Simple way without "functions" or "IF" or "CASE"
 -- Query to select maximum value
 SELECT o.OrderId
  ,(SELECT MAX(v)
   FROM (VALUES (o.NegotiatedPrice), (o.SuggestedPrice)) AS value(v)) AS MaxValue
  FROM Order o;

Хотя интересное использование VALUESinline, как это, я не уверен, что это проще, чем CASEили IFF. Мне было бы интересно посмотреть, как производительность этого решения сочетается с другими вариантами, хотя
Крис Шаллер

0

Расширяя ответ Xin и предполагая, что типом значения сравнения является INT, этот подход также работает:

SELECT IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)

Это полный тест с примерами значений:

DECLARE @A AS INT
DECLARE @B AS INT

SELECT  @A = 2, @B = 1
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 2

SELECT  @A = 2, @B = 3
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 3

SELECT  @A = 2, @B = NULL
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 2    

SELECT  @A = NULL, @B = 1
SELECT  IIF(ISNULL(@A, -2147483648) > ISNULL(@B, -2147483648), @A, @B)
-- 1

0

В MemSQL сделайте следующее:

-- DROP FUNCTION IF EXISTS InlineMax;
DELIMITER //
CREATE FUNCTION InlineMax(val1 INT, val2 INT) RETURNS INT AS
DECLARE
  val3 INT = 0;
BEGIN
 IF val1 > val2 THEN
   RETURN val1;
 ELSE
   RETURN val2;
 END IF; 
END //
DELIMITER ;

SELECT InlineMax(1,2) as test;

-1

В Presto вы можете использовать

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.