Чи є в 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") з'являються лише один раз: ВИБІР МАКС (VALUE) ВІД (ВИБІРТЕ ЯК ЗНАЧЕННЯ СІЛЬНОГО ВИБІРУТЬСЯ, ЯК ВАРТІСТЬ) ЯК Т1
Ендрю Єнс

Відповіді:


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

24
Я б підтримав ваше рішення, єдине, що я б додав - це підтримка значень NULL. Якщо ви просто модифікуєте підсумковий рядок: "return @ value2" читати як: "return isnull (@ val2, @ val1)", тоді, якщо одне зі значень є null, функція поверне ненулеве значення, інакше воно буде працювати як нормально
Крістоф

1
Як щодо інших типів даних, наприклад, мені потрібно написати аргумент HigherIntegerArgher та HigherDateTimeArgument та a HigherVarcharArgument та ...?
Один день, коли

9
це буде неймовірно повільно, як і всі скандальні АДС. Замість цього використовуйте вбудовані 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 Працює ідеально, особливо для порівняння понад 2 колонки!
січень

11
Це менш ефективно, ніж рішення CASE WHEN, для якого потрібно лише обчислити скаляр.
tekumara

5
Хоча простіший синтаксис може ніколи не вартий ефективності при визначенні MAX двох значень, це може бути інша справа з більшою кількістю значень. Навіть при отриманні 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 ".
Tom

6
Таким чином ви отримаєте помилку переповнення, якщо сума більша, ніж може бути збережена в int: оголосити @ val1 int оголосити @ val2 int встановити @ val1 = 1500000000 встановити @ val2 = 1500000000 SELECT 0,5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2)) - => Помилка переповнення
AakashM

89
Це надзвичайно "брудна" "хитрість". При програмуванні ваш код повинен чітко виражати мету, однак у вашому випадку він виглядає як код, взятий з конкурсу обфускування.
greenoldman

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 (це круто btw), і я не маю клопоту створити UDF. Справа дуже зручна у багатьох ситуаціях.
Ленс Фішер

ВИБІРТЕ o.OrderId, СЛУЧАЙ КОЛИ o.NogotiatedCena> o.ПредложенаЦена АБО.ПредложенаЦена НУЛЬНА, ніж o.NogotiatedCrice ELSE o.ПредложенаЦена END OF OF Order o
mohghaderi

Коли замість "o.NegotiatedPrice" у вас є термін на зразок "(dateiff (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, результат завжди буде другим.
jahu

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. Початкове порівняння ">" поверне помилкове значення і перейде до ELSE, якщо будь-яке значення вже є нульовим.
Філ Б

10

У SQL Server 2012 або новіших версіях ви можете використовувати комбінацію IIFта ISNULL(або COALESCE), щоб отримати максимум 2 значення.
Навіть коли 1 з них НУЛЬНИЙ.

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

Приємно! Це масштабується дуже добре.
greenoldman

+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, оскільки NULLз обох боків вашої сторони boolean_expressionбуде IIFповернути false_value(на відміну від 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

EDIT Змінено після коментаря від Марка . Як він правильно вказав у 3-х ціннісних логіках x> NULL або x <NULL завжди повинен повертати NULL. Іншими словами, невідомий результат.


1
Нулі важливі. І важливо поводитися з ними послідовно. Єдина правильна відповідь на "NULL> x" - NULL.
Марк Брекетт

Ви маєте рацію, я модифікую свою відповідь, щоб відобразити це, спасибі за вказівку на це
kristof

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

@ the-locster, дивіться коментар Марка
kristof

1
це буде неймовірно повільно, як і всі скандальні АДС. Замість цього використовуйте вбудовані 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 до попередньої відповіді SELECT 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's Greatest , але ви можете досягти подібного результату для двох стовпців з 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
Примітка: реалізація GREATEST функції буде відповідати поведінці Oracle для 2-х парам, якщо будь-який параметр недійсний, він повернеться до нуля
Sam Saffron

2
Будьте обережні, використовуючи sql_variant. Ваша функція дасть несподіваний результат у такій ситуації: SELECT dbo.greatest (CAST (0,5 AS FLOAT), 100)
Neil

@Neil має рацію (я навчився цього важко), як би ти вдосконалив цю функцію, щоб запобігти подібним проблемам?
Лука

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 case when o.NegotiatedPrice > o.SuggestedPrice 
then o.NegotiatedPrice
else o.SuggestedPrice
end

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 ви можете використовувати use

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.