Esiste una funzione Max in SQL Server che accetta due valori come Math.Max ​​in .NET?


488

Voglio scrivere una query come questa:

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

Ma non è così che MAXfunziona la funzione, giusto? È una funzione aggregata, quindi si aspetta un singolo parametro e quindi restituisce il MAX di tutte le righe.

Qualcuno sa come fare a modo mio?


13
È implementato nella maggior parte degli altri database come GREATESTfunzione; SQLite emula il supporto consentendo più colonne MAXnell'aggregazione.
OMG Ponies,


Quando si trova una soluzione per max (a, b) di seguito, tenere presente la domanda se si desidera ripetere la sintassi o il calcolo per "a" e / o "b". Vale a dire se "b" deriva da un calcolo complesso che coinvolge molta sintassi, allora potresti preferire una soluzione in cui "b" appare solo una volta. Ad esempio, la soluzione "IIF (a> b, a, b)" significa ripetere "b" - che potrebbe essere sintatticamente brutta, tuttavia la seguente soluzione significa che "b" (e "a") appaiono solo una volta: SELEZIONA MAX (VALORE) DA (SELEZIONA UN'UNIONE DI VALORE AS SELEZIONA b COME VALORE) COME T1
Andrew Jens

Risposte:


158

Avresti bisogno di fare un User-Defined Functionse volessi avere una sintassi simile al tuo esempio, ma potresti fare quello che vuoi fare, in linea, abbastanza facilmente con una CASEdichiarazione, come hanno detto gli altri.

Il UDFpotrebbe essere qualcosa di simile:

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

... e lo chiameresti così ...

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

25
Vorrei supportare la tua soluzione, l'unica cosa che aggiungerei è il supporto per i valori NULL. Se modifichi semplicemente la riga finale: "return @ value2" per leggere come: "return isnull (@ val2, @ val1)", se uno dei valori è nullo la funzione restituirà il valore non nullo, altrimenti funzionerà come normale
kristof il

1
Che dire di altri tipi di dati, ad esempio dovrei scrivere un HigherIntegerArgument e un HigherDateTimeArgument e un HigherVarcharArgument e un ...?
onedayquando il

9
questo sarà incredibilmente lento, come tutte le UDF scalari. Usa invece gli UDF in linea
AK

12
@xan Non ho idea di cosa mi sia passato per la testa quando ho effettivamente posto quella domanda. Non troppo, ovviamente. Grazie comunque per la risposta.
Thomas,

13
@Thomas Immagine obbligatoria del meme (nessuna offesa per te in alcun modo!) Flickr.com/photos/16201371@N00/2375571206
xan

468

Se si utilizza SQL Server 2008 (o versioni successive), questa è la soluzione migliore:

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

Tutti i crediti e i voti dovrebbero andare alla risposta di Sven a una domanda correlata, "SQL MAX di più colonne?"
Dico che è la " migliore risposta " perché:

  1. Non richiede complicazioni del codice con le dichiarazioni UNION, PIVOT, UNPIVOT, UDF e CASE pazzesche.
  2. Non è afflitto dal problema di gestire i null, li gestisce bene.
  3. È facile sostituire "MAX" con "MIN", "AVG" o "SUM". È possibile utilizzare qualsiasi funzione di aggregazione per trovare l'aggregazione su molte colonne diverse.
  4. Non sei limitato ai nomi che ho usato (ad esempio "Tutti i prezzi" e "Prezzo"). Puoi scegliere i tuoi nomi per facilitare la lettura e la comprensione per il prossimo.
  5. È possibile trovare più aggregati utilizzando le tabelle derivate di SQL Server 2008 in questo modo:
    SELECT MAX (a), MAX (b) FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)) AS MyTable (a, b)

27
+1 solo una risposta che non richiede l'accesso per creare procedure / funzioni!
Alex,

6
Esattamente il tipo di risposta che stavo cercando. L'uso delle funzioni è lento e questo funzionerà anche con le date, che è ciò di cui ho bisogno.
Johann Strydom,

3
+1 Funziona perfettamente, soprattutto per confrontare più di 2 colonne!
JanW

11
Questo è meno performante della soluzione CASE WHEN che deve solo calcolare uno scalare.
Tekumara,

5
Mentre la sintassi più semplice non può mai valere il colpo di prestazione quando si determinano i valori MAX di 2, può essere una questione diversa con più valori. Anche quando si ottengono i valori MAX di 4, le clausole CASE diventano lunghe, goffe e soggette a errori se generate manualmente mentre la clausola VALUES rimane semplice e chiara.
Typhlosaurus,

221

Può essere fatto in una riga:

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

Modifica: se hai a che fare con numeri molto grandi dovrai convertire le variabili di valore in bigint per evitare un overflow di numeri interi.


18
+1 Credo che tu abbia fornito il modo più corretto. "SELECT ((@ val1 + @ val2) + ABS (@ val1- @ val2)) / 2 come MAX_OF_TWO" Ricorda anche, "SELECT ((@ val1 + @ val2) - ABS (@ val1- @ val2)) / 2 come MIN_OF_TWO ".
Tom,

6
In questo modo verrà generato un errore di overflow se la somma è maggiore di quella memorizzabile in un int: declare @ val1 int dichiarare @ val2 int set @ val1 = 1500000000 set @ val2 = 1500000000 SELEZIONA 0,5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2)) - => errore di overflow
AakashM

89
Questo è un "trucco" estremamente "sporco". Quando programmate il vostro codice, dovreste esprimere esplicitamente lo scopo, tuttavia nel vostro caso sembra un codice preso dal concorso di offuscamento.
Greenoldman,

24
Potrebbe essere "sporco", ma potrebbe essere l'unica opzione per database con semplici dialetti SQL.
splattne,

12
Non sono d'accordo con le marcia. Il codice non deve necessariamente necessariamente esprimere esplicitamente l'obiettivo, purché i commenti consentano di elaborarlo. Se stai eseguendo complesse equazioni matematiche nel codice (o ovunque) a volte è difficile renderlo auto-descrittivo. Fintanto che è suddiviso in parti più semplici, più facili da comprendere, questa è una programmazione corretta.
Rob,

127

Io non la penso così. L'ho voluto l'altro giorno. Il più vicino che ho avuto è stato:

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

4
Questo è il mio metodo preferito. Non rischi un overflow, ed è meno enigmatico della soluzione di splattne (che è interessante tra l'altro), e non ho il fastidio di creare un UDF. case è molto utile in molte situazioni.
Lance Fisher,

SELEZIONA o.OrderId, CASE WHEN o.NegotiatedPrice> o.SuggestedPrice OR o o.SuggestedPrice IS NULL THEN o.NegotiatedPrice ELSE o.SuggestedPrice END FROM Order o
mohghaderi

Quando invece di "o.NegotiatedPrice" hai piuttosto un termine simile a "(datiff (day, convert (datetime, adr_known_since, 120), getdate ()) - 5) * 0.3" devi ripetere questo codice. Eventuali future modifiche al termine devono essere fatte due volte. Una funzione di tipo min (x, y, ...) sarebbe molto più piacevole
Daniel

87

Perché non provare la funzione IIF (richiede SQL Server 2012 e versioni successive)

IIF(a>b, a, b)

Questo è tutto.

(Suggerimento: fare attenzione a entrambi sarebbe null, dal momento che il risultato di a>bsarà falso ogni volta che uno dei due è nullo. Così bsarà il risultato in questo caso)


7
Se uno dei valori è NULL, il risultato sarà sempre il secondo.
jahu,

4
IIF () è lo zucchero sintattico per l'istruzione CASE. Se uno dei due valori del condizionale CASE è NULL, il risultato sarà il secondo (ELSE).
xxyzzy,

@xxyzzy questo perché l' NULL > 1234affermazione è falsa
Xin

8
quindi IIF(a>b, a, COALESCE(b,a))per dare il valore quando ne esiste solo uno
mpag

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

Dò a questa soluzione un +1 perché è conforme a DRY (non ripeterti) senza la necessità di scrivere un UDF. È fantastico anche se entrambi i valori che devi controllare sono i risultati di altri sql, ad esempio nel mio caso voglio trovare il maggiore di 2 istruzioni count (*).
MikeKulls

1
Odio dover ricorrere a questa soluzione, ma è sicuramente il modo migliore per farlo in SQL Server fino a quando non aggiungono supporto nativo per il massimo o in-line MAX. Grazie per averlo pubblicato - +1 a te!
SqlRyan,

10

Le altre risposte sono buone, ma se devi preoccuparti di avere valori NULL, potresti voler questa variante:

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
L'unico ISNULL richiesto è dopo l'ELSE. Il confronto ">" iniziale restituirà false e passerà all'ELSE se uno dei valori è già nullo.
Phil B,

10

In SQL Server 2012 o versioni successive, è possibile utilizzare una combinazione di IIFe ISNULL(o COALESCE) per ottenere il massimo di 2 valori.
Anche quando 1 di questi è NULL.

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

O se vuoi che ritorni 0 quando entrambi sono NULL

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

Snippet di esempio:

-- 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

Risultato:

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

Ma se uno ha bisogno di sommare più valori?
Quindi suggerisco di CROSS APPLY a un'aggregazione dei VALORI.
Ciò ha anche il vantaggio di poter calcolare altre cose contemporaneamente.

Esempio:

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

Le query secondarie possono accedere alle colonne dalla query esterna in modo da poter utilizzare questo approccio per utilizzare aggregati come MAXtra le colonne. (Probabilmente più utile quando c'è un numero maggiore di colonne coinvolte)

;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

Bello! Si ingrandisce molto bene.
Greenoldman,

+1 per mostrare l'amore per quelli ancora nel 2005. Non so come ho trascurato questa risposta. Sotto le copertine, immagino che si esibisca esattamente come quello che ho pubblicato 2 anni dopo. In retrospettiva, avrei dovuto rendermene conto e aver aggiornato la tua risposta per includere la sintassi più recente del 2008 in quel momento. Scusa, vorrei poter condividere i miei punti con te ora.
MikeTeeVee,

@MikeTeeVee - Grazie! Sì, sotto le coperte il piano sarà lo stesso. Ma la VALUESsintassi è più bella.
Martin Smith,

6

SQL Server 2012 introdotto IIF:

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

Si consiglia di maneggiare i NULL durante l'utilizzo IIF, poiché NULLsu entrambi i lati boolean_expressionsi IIFrestituirà false_value(al contrario di NULL).


La tua soluzione non gestirà bene NULL quando l'altro valore è negativo, questo restituirà null
t-clausen.dk il

5

Vorrei andare con la soluzione fornita da kcrumley Basta modificarlo leggermente per gestire i 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 Modificato dopo il commento di Mark . Come ha correttamente sottolineato nella logica a 3 valori x> NULL o x <NULL dovrebbe sempre restituire NULL. In altre parole, risultato sconosciuto.


1
I null sono importanti. Ed è importante gestirli in modo coerente. L'unica risposta corretta a È NULL> x è NULL.
Mark Brackett,

Hai ragione, modificherò la mia risposta per riflettere questo, grazie per
averlo

Se passiamo un int e un NULL, penso che sia più comune voler restituire il valore non nullo, quindi la funzione agisce come una combinazione di Max (x, y) e ISNULL (x, y). Quindi personalmente cambierei l'ultima riga in modo che sia: return ISNULL (@ val1, @ val2) - che certamente è probabilmente quello con cui
dovevi

@ the-locster, vedi commento di Mark
kristof

1
questo sarà incredibilmente lento, come tutte le UDF scalari. Usa invece gli UDF in linea
AK

4

È semplice come questo:

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;

Vedi il commento di @Neil a una risposta precedente SELECT dbo.InlineMax (CAST (0.5 AS FLOAT), 100) è errato.
Luca,

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

Per spiegazioni consultare questo articolo: red-gate.com/simple-talk/sql/sql-training/…
Tom Arleth

2
Si prega di non includere le informazioni necessarie al codice solo tramite un collegamento Immagina che questo link scadrà un giorno e la tua risposta sarà inutile allora. Quindi, vai avanti e aggiungi le informazioni essenziali direttamente nella tua risposta. Ma puoi comunque fornire quel link come risorsa per consentire ad altri di cercare ulteriori informazioni.
L. Guthardt,

3

Spiacenti, ho appena pubblicato una copia di questa domanda ...

La risposta è che non esiste una funzione integrata come Oracle's Greatest , ma puoi ottenere un risultato simile per 2 colonne con un UDF, nota, qui l'uso di sql_variant è abbastanza importante.

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

Kristof

Inserita questa risposta:

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
Nota: l'implementazione della funzione PIÙ GRANDE corrisponderà al comportamento dell'oracolo per 2 parametri, se un parametro è nullo restituirà null
Sam Saffron

2
Dovresti stare attento quando usi sql_variant. La tua funzione darà un risultato inaspettato nella seguente situazione: SELECT dbo.greatest (CAST (0.5 AS FLOAT), 100)
Neil

@Neil ha ragione (l'ho imparato a mie spese), come miglioreresti questa funzione per prevenire questo tipo di problemi?
Luca,

3

Ecco un esempio di caso che dovrebbe gestire valori null e funzionerà con le versioni precedenti di MSSQL. Questo si basa sulla funzione incorporata in uno degli esempi popolari:

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

2

Probabilmente non lo farei in questo modo, poiché è meno efficiente dei già citati costrutti CASE - a meno che, forse, non avessi indici di copertura per entrambe le query. In entrambi i casi, è una tecnica utile per problemi simili:

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

Per la risposta sopra riguardante numeri di grandi dimensioni, è possibile effettuare la moltiplicazione prima dell'addizione / sottrazione. È un po 'più ingombrante ma non richiede cast. (Non posso parlare per la velocità ma presumo sia ancora piuttosto veloce)

SELEZIONA 0,5 * ((@ val1 + @ val2) + ABS (@ val1 - @ val2))

Cambia in

SELEZIONA @ val1 * 0,5 + @ val2 * 0,5 + ABS (@ val1 * 0,5 - @ val2 * 0,5)

almeno un'alternativa se vuoi evitare il casting.


2

Ecco una versione IIF con gestione NULL (basata sulla risposta di Xin):

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

La logica è la seguente, se uno dei valori è NULL, restituisce quello che non è NULL (se entrambi sono NULL, viene restituito un NULL). Altrimenti restituisci quello più grande.

Lo stesso può essere fatto per MIN.

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

1

Puoi fare qualcosa del genere:

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

Nella sua forma più semplice ...

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

Per SQL Server 2012:

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

1

Ecco la risposta di @Scott Langham con una semplice gestione 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;

Mentre un uso interessante di VALUESinline come quello, non sono sicuro che sia più semplice di CASEo IFF. Sarei interessato a vedere come le prestazioni di questa soluzione si confrontano con le altre opzioni
Chris Schaller

0

Espandendo la risposta di Xin e assumendo che il tipo di valore di confronto sia INT, anche questo approccio funziona:

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

Questo è un test completo con valori di esempio:

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

In MemSQL procedi come segue:

-- 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

In Presto potresti usare use

SELECT array_max(ARRAY[o.NegotiatedPrice, o.SuggestedPrice])
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.