Ottenere il minimo di due valori in SQL


180

Ho due variabili, una viene chiamata PaidThisMonthe l'altra viene chiamata OwedPast. Sono entrambi risultati di alcune sottoquery in SQL. Come posso selezionare il più piccolo dei due e restituirlo come valore intitolato PaidForPast?

La MINfunzione funziona su colonne, non su variabili.


1
Se usi Postgres o MySQL, passa alla risposta di @ Gil_Margolin.
Noumenon,

Risposte:


127

Caso d'uso:

   Select Case When @PaidThisMonth < @OwedPast 
               Then @PaidThisMonth Else @OwedPast End PaidForPast

Come tabella Inline valutato UDF

CREATE FUNCTION Minimum
(@Param1 Integer, @Param2 Integer)
Returns Table As
Return(Select Case When @Param1 < @Param2 
                   Then @Param1 Else @Param2 End MinValue)

Uso:

Select MinValue as PaidforPast 
From dbo.Minimum(@PaidThisMonth, @OwedPast)

ADDENDUM: Questo è probabilmente il migliore per affrontare solo due possibili valori, se ce ne sono più di due, considera la risposta di Craig usando la clausola Values.


sintassi meglio comprensibili: return (selezionare minValue = case when @@ param1 <@@ param2 then @@ param1 else @@ param2 end). Ok questo potrebbe non essere normalizzato, non lo so. Ma è molto più comprensibile e dovrebbe essere normalizzato.
Softlion,

1
Un altro motivo per preferire la risposta di @ Craig di seguito è dovuto alla gestione nulla. Se i valori confrontati sono nulla e uno dei valori confrontati è null, il caso mostrato potrebbe restituire null o il valore, a seconda dell'ordine del test WHEN (a meno che non si aggiunga l'uso di ISNULL). L'approccio di Craig preferirà sempre la selezione del valore non nullo che mi sembra più corretto, almeno nel mio caso d'uso attuale nel confronto di date annullabili.
Nij,

148

SQL Server 2012 e 2014 supporta la funzione IIF (cont, true, false). Quindi per una selezione minima puoi usarlo come

SELECT IIF(first>second, second, first) the_minimal FROM table

Mentre IIF è solo una scorciatoia per scrivere CASE...WHEN...ELSE, è più facile da scrivere.


8
IIFè solo uno zucchero sintattico per CASE...WHEN...ELSE.
Salman,

55
Forse sì. Ma più facile da scrivere.
Mert Gülsoy,

1
@ MertGülsoy E più facile da leggere, che dovrebbe essere in cima alla lista delle priorità di tutti, subito dopo la correttezza.
Daniel,

118

Le soluzioni che utilizzano CASE, IIF e UDF sono adeguate, ma poco pratiche quando si estende il problema al caso generale utilizzando più di 2 valori di confronto. La soluzione generalizzata in SQL Server 2008+ utilizza una strana applicazione della clausola VALUES:

SELECT
PaidForPast=(SELECT MIN(x) FROM (VALUES (PaidThisMonth),(OwedPast)) AS value(x))

Credito dovuto a questo sito Web: http://sqlblog.com/blogs/jamie_thomson/archive/2012/01/20/use-values-clause-to-get-the-ma maximum value from some columns sql- Server-t-sql.aspx


12
Questa è la risposta migliore
FindOutIslamNow,

se vuoi il minimo diverso da zero:MIN(x*(case x when 0 then null else 1 end))
mpag

Tranne MartinC ha dato la stessa risposta quattro anni prima, e in realtà l'ha mostrata con più di due valori ...
Auspex,

4
Auspex, la risposta di MartinC non è correlata. Questa risposta non utilizza i sindacati.
Craig,

30

Ho appena avuto una situazione in cui ho dovuto trovare il massimo di 4 selezioni complesse all'interno di un aggiornamento. Con questo approccio puoi averne quanti ne vuoi!

Puoi anche sostituire i numeri con selezioni adizionali

select max(x)
 from (
 select 1 as 'x' union
 select 4 as 'x' union
 select 3 as 'x' union
 select 2 as 'x' 
 ) a

Utilizzo più complesso

 @answer = select Max(x)
           from (
                select @NumberA as 'x' union
                select @NumberB as 'x' union
                select @NumberC as 'x' union
                select (
                       Select Max(score) from TopScores
                       ) as 'x' 
     ) a

Sono sicuro che un UDF abbia prestazioni migliori.


Mi piace di più quello dal momento che è SQL di base. Inoltre, gli UDF non sono necessariamente più veloci. Per la maggior parte dei negozi di colonne, ogni attributo (suppongo che anche tu filtrerai gli attributi) può essere calcolato in parallelo e solo il set di qualificazione viene unito. Quindi i sindacati non sono lenti di per sé.
Bouncner,

semplice e fantastico.
ashleedawg,

22

Per MySQL o PostgreSQL 9.3+, un modo migliore è usare le funzioni LEASTe GREATEST.

SELECT GREATEST(A.date0, B.date0) AS date0, 
       LEAST(A.date1, B.date1, B.date2) AS date1
FROM A, B
WHERE B.x = A.x

Con:

  • GREATEST(value [, ...]): Restituisce l' argomento più grande (massimo valore) dai valori forniti
  • LEAST(value [, ...])Restituisce l' argomento più piccolo (valore minimo) dai valori forniti

Link alla documentazione:


Questo funziona anche in PostgreSQL (ed è esattamente quello che stavo cercando :) Vedi: postgresql.org/docs/9.5/static/functions-conditional.html
Albert Vaca Cintora,

1
Questa è di gran lunga la risposta migliore.
Roberto Rodriguez,

2
@RobertoRodriguez sarebbe il migliore se la domanda avesse MySQL o PostgreSQL taggati come parte della domanda. La domanda riguardava specificamente tsql, quindi questa risposta non aiuta affatto.
Jmaurier,

questa non è una risposta per MSSQL
Mujah Maskey il

13

Ecco un trucco se vuoi calcolare il massimo (campo, 0):

SELECT (ABS(field) + field)/2 FROM Table

restituisce 0 se fieldè negativo, altrimenti restituisce field.


3
Quindi, per calcolare il minimo (@a, @b), è possibile utilizzare:SELECT @a - ( ABS(@a-@b) + (@a-@b) ) / 2
scottyc

1
e non dimenticare di digitare overflow;)
pkuderov,

Questo è un risparmio dal punto di vista della precisione in virgola mobile? È certo che il risultato non sarà mai qualcosa di vicino allo zero ma negativo?
zuraff,

6

Utilizzare un'istruzione CASE.

L'esempio B in questa pagina dovrebbe essere vicino a quello che stai cercando di fare:
http://msdn.microsoft.com/en-us/library/ms181765.aspx

Ecco il codice dalla pagina:

USE AdventureWorks;
GO
SELECT   ProductNumber, Name, 'Price Range' = 
      CASE 
         WHEN ListPrice =  0 THEN 'Mfg item - not for resale'
         WHEN ListPrice < 50 THEN 'Under $50'
         WHEN ListPrice >= 50 and ListPrice < 250 THEN 'Under $250'
         WHEN ListPrice >= 250 and ListPrice < 1000 THEN 'Under $1000'
         ELSE 'Over $1000'
      END
FROM Production.Product
ORDER BY ProductNumber ;
GO

2

Utilizzare una tabella temporanea per inserire l'intervallo di valori, quindi selezionare il min / max della tabella temporanea all'interno di una procedura memorizzata o UDF. Questo è un costrutto di base, quindi sentiti libero di rivedere secondo necessità.

Per esempio:

CREATE PROCEDURE GetMinSpeed() AS
BEGIN

    CREATE TABLE #speed (Driver NVARCHAR(10), SPEED INT);
    '
    ' Insert any number of data you need to sort and pull from
    '
    INSERT INTO #speed (N'Petty', 165)
    INSERT INTO #speed (N'Earnhardt', 172)
    INSERT INTO #speed (N'Patrick', 174)

    SELECT MIN(SPEED) FROM #speed

    DROP TABLE #speed

END

2

Funziona fino a 5 date e gestisce i valori null. Non riuscivo proprio a farlo funzionare come una funzione Inline.

CREATE FUNCTION dbo.MinDate(@Date1 datetime = Null,
                            @Date2 datetime = Null,
                            @Date3 datetime = Null,
                            @Date4 datetime = Null,
                            @Date5 datetime = Null)
RETURNS Datetime AS
BEGIN
--USAGE select dbo.MinDate('20120405',null,null,'20110305',null)
DECLARE @Output datetime;

WITH Datelist_CTE(DT)
AS (
        SELECT @Date1 AS DT WHERE @Date1 is not NULL UNION
        SELECT @Date2 AS DT WHERE @Date2 is not NULL UNION
        SELECT @Date3 AS DT WHERE @Date3 is not NULL UNION
        SELECT @Date4 AS DT WHERE @Date4 is not NULL UNION
        SELECT @Date5 AS DT WHERE @Date5 is not NULL
   )
Select @Output=Min(DT) FROM Datelist_CTE

RETURN @Output
END

Ho appena realizzato che non hai bisogno delle clausole WHERE poiché MIN rimuoverà comunque i Null.
Lawrence,

2

Basandomi sulla brillante logica / codice di matematix e scottyc, invio:

DECLARE @a INT, @b INT, @c INT = 0

WHILE @c < 100
    BEGIN
        SET @c += 1
        SET @a = ROUND(RAND()*100,0)-50
        SET @b = ROUND(RAND()*100,0)-50
        SELECT @a AS a, @b AS b,
            @a - ( ABS(@a-@b) + (@a-@b) ) / 2 AS MINab,
            @a + ( ABS(@b-@a) + (@b-@a) ) / 2 AS MAXab,
            CASE WHEN (@a <= @b AND @a = @a - ( ABS(@a-@b) + (@a-@b) ) / 2)
            OR (@a >= @b AND @a = @a + ( ABS(@b-@a) + (@b-@a) ) / 2)
            THEN 'Success' ELSE 'Failure' END AS Status
    END

Anche se il salto dalla funzione MIN di scottyc alla funzione MAX avrebbe dovuto essere ovvio per me, non lo era, quindi l'ho risolto e l'ho incluso qui: SELECT @a + (ABS (@ b- @ a) + ( @ b- @ a)) / 2. I numeri generati casualmente, sebbene non provino, dovrebbero almeno convincere gli scettici che entrambe le formule sono corrette.

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.