SQL MAX di più colonne?


372

Come si restituisce 1 valore per riga del massimo di più colonne:

TableName

[Number, Date1, Date2, Date3, Cost]

Devo restituire qualcosa del genere:

[Number, Most_Recent_Date, Cost]

Query?

Risposte:


161

Bene, puoi usare l'istruzione CASE:

SELECT
    CASE
        WHEN Date1 >= Date2 AND Date1 >= Date3 THEN Date1
        WHEN Date2 >= Date1 AND Date2 >= Date3 THEN Date2
        WHEN Date3 >= Date1 AND Date3 >= Date2 THEN Date3
        ELSE                                        Date1
    END AS MostRecentDate

[Per Microsoft SQL Server 2008 e versioni successive, è possibile prendere in considerazione la risposta più semplice di Sven di seguito.]


11
Non basterebbe usare WHEN Date1 > Date2 AND Date1 > Date3 THEN Date1; WHEN Date2 > Date3 THEN Date3; ELSE Date3?
— Treb,

21
La risposta ovvia, ma non funziona con i valori NULL e il tentativo di risolvere ciò diventa molto disordinato.
— Disilluso il

5
Annullare questo post più vecchio, ma potresti racchiudere ogni data in una COALESCE per gestire i NULL. Una di quelle dichiarazioni QUANDO assomiglierebbe quindi: QUANDO Data1> = COALESCE (Date2, '') AND Date1> = COALESCE (Date3, '') THEN Date3 (fare lo stesso per l'altro quando è)
— Bill Sambrone,

per coloro che sono venuti qui in cerca di un modo di MySQL, date un'occhiata alla risposta @ bajafresh4life: stackoverflow.com/a/331873/1412157
— Lucam

2
A proposito, restituisce Date1 quando Date2 è null anche se Date3> Date1.
— jumxozizi,

853

Ecco un'altra bella soluzione per la Maxfunzionalità che utilizza T-SQL e SQL Server

SELECT [Other Fields],
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MaxDate]
FROM [YourTableName]

47
La versione SQL deve essere> = 2008.
— Daniel,

10
Funziona molto bene con il 2008 e gestisce i NULL. Soluzione molto bella.
— nycdan,

10
@Cheburek: da value (v), "value" è l'alias per la tabella virtuale e "v" è il nome della colonna virtuale dei valori della data.
— Jonas Lincoln,

2
È brillante. Dove posso trovare la documentazione per questa tabella virtuale Value ()?
— My Other Me

33
Inizialmente non capivo neanche VALUE (v). Se vuoi capire VALUE prova questa query che crea una tabella di 1 colonna virtuale: SELECT * FROM (VALUES (1), (5), (1)) come listOfValues ​​(columnName) E questa query che crea una tabella di 2 colonne virtuale: SELEZIONA * FROM (VALUES (1,2), (5,3), (1,4)) come tableOfValues ​​(columnName1, ColumnName2) Ora puoi capire perché quella query di esempio ha un valore AS (v) in essa. La mia query finale sembrava così: SELEZIONA Max (currentValues) come Max FROM (VALUES (12), (25), (35)) AS allCurrents (currentValues) Sceglierà il valore massimo che in questo caso è 35.
— Jackson

148

Se stai usando MySQL, puoi usare

SELECT GREATEST(col1, col2 ...) FROM table

41
il tag è sqlserver
— Codewerks,

104
Vero, ma comunque una risposta molto utile in quanto le persone trovano questa domanda in riferimento a MySQL.
— philfreo,

4
Disponibile anche in PostgreSQL da 8.1 .
— Frozen Flame,

4
Non gestisce bene i NULL, ma se si fondono (col1, 0) attorno ai valori della colonna si cucina con gas, vedere questa risposta stackoverflow.com/questions/9831851/…
— Stan Quinn,

E che dire di questa soluzione: stackoverflow.com/a/2166693/4824854
— Sandburg,

64

Esistono altri 3 metodi in cui UNPIVOT(1) è di gran lunga il più veloce, seguito da Simulated Unpivot (3) che è molto più lento di (1) ma comunque più veloce di (2)

CREATE TABLE dates
    (
      number INT PRIMARY KEY ,
      date1 DATETIME ,
      date2 DATETIME ,
      date3 DATETIME ,
      cost INT
    )

INSERT  INTO dates
VALUES  ( 1, '1/1/2008', '2/4/2008', '3/1/2008', 10 )
INSERT  INTO dates
VALUES  ( 2, '1/2/2008', '2/3/2008', '3/3/2008', 20 )
INSERT  INTO dates
VALUES  ( 3, '1/3/2008', '2/2/2008', '3/2/2008', 30 )
INSERT  INTO dates
VALUES  ( 4, '1/4/2008', '2/1/2008', '3/4/2008', 40 )
GO

Soluzione 1 ( UNPIVOT)

SELECT  number ,
        MAX(dDate) maxDate ,
        cost
FROM    dates UNPIVOT ( dDate FOR nDate IN ( Date1, Date2,
                                            Date3 ) ) as u
GROUP BY number ,
        cost 
GO

Soluzione 2 (query secondaria per riga)

SELECT  number ,
        ( SELECT    MAX(dDate) maxDate
          FROM      ( SELECT    d.date1 AS dDate
                      UNION
                      SELECT    d.date2
                      UNION
                      SELECT    d.date3
                    ) a
        ) MaxDate ,
        Cost
FROM    dates d
GO

Soluzione 3 (simulata UNPIVOT)

;WITH    maxD
          AS ( SELECT   number ,
                        MAX(CASE rn
                              WHEN 1 THEN Date1
                              WHEN 2 THEN date2
                              ELSE date3
                            END) AS maxDate
               FROM     dates a
                        CROSS JOIN ( SELECT 1 AS rn
                                     UNION
                                     SELECT 2
                                     UNION
                                     SELECT 3
                                   ) b
               GROUP BY Number
             )
    SELECT  dates.number ,
            maxD.maxDate ,
            dates.cost
    FROM    dates
            INNER JOIN MaxD ON dates.number = maxD.number
GO

DROP TABLE dates
GO

1
Bello. Non ero a conoscenza degli operatori PIVOT e UNPIVOT.
— Sako73,

Qualche idea su quali versioni di SQL Server supportano pivot / unpivot?
— Disilluso il

1
@CraigYoung SQL Server 2005 con COMPATIBILITY_LEVEL impostato su 90.
— Paul Syfrett,

18

Uno dei due campioni seguenti funzionerà:

SELECT  MAX(date_columns) AS max_date
FROM    ( (SELECT   date1 AS date_columns
           FROM     data_table         )
          UNION
          ( SELECT  date2 AS date_columns
            FROM    data_table
          )
          UNION
          ( SELECT  date3 AS date_columns
            FROM    data_table
          )
        ) AS date_query

Il secondo è un componente aggiuntivo alla risposta di Lassevk .

SELECT  MAX(MostRecentDate)
FROM    ( SELECT    CASE WHEN date1 >= date2
                              AND date1 >= date3 THEN date1
                         WHEN date2 >= date1
                              AND date2 >= date3 THEN date2
                         WHEN date3 >= date1
                              AND date3 >= date2 THEN date3
                         ELSE date1
                    END AS MostRecentDate
          FROM      data_table
        ) AS date_query 

La prima risposta è buona, ma può essere notevolmente semplificata. La seconda risposta non funziona con i valori NULL. Il tentativo di risolvere quel problema diventa molto complicato.
— Disilluso il

È necessario utilizzare UNION ALL e non UNION per evitare un'operazione DISTINCT implicita non necessaria.
— Jamie Vedi il

17

Per T-SQL (MSSQL 2008+)

SELECT
  (SELECT
     MAX(MyMaxName) 
   FROM ( VALUES 
            (MAX(Field1)), 
            (MAX(Field2)) 
        ) MyAlias(MyMaxName)
  ) 
FROM MyTable1

9
DECLARE @TableName TABLE (Number INT, Date1 DATETIME, Date2 DATETIME, Date3 DATETIME, Cost MONEY)

INSERT INTO @TableName 
SELECT 1, '20000101', '20010101','20020101',100 UNION ALL
SELECT 2, '20000101', '19900101','19980101',99 

SELECT Number,
       Cost  ,
       (SELECT MAX([Date])
       FROM    (SELECT Date1 AS [Date]
               UNION ALL
               SELECT Date2
               UNION ALL
               SELECT Date3
               )
               D
       )
       [Most Recent Date]
FROM   @TableName

Ha funzionato in qualsiasi versione SQL per me, bella soluzione
— Kirill

9

La funzione scalare causa tutti i tipi di problemi di prestazioni, quindi è meglio avvolgere la logica in una funzione con valori di tabella in linea, se possibile. Questa è la funzione che ho usato per sostituire alcune funzioni definite dall'utente che hanno selezionato le date Min / Max da un elenco di fino a dieci date. Quando testato sul mio set di dati da 1 milione di righe, la funzione scalare ha impiegato più di 15 minuti prima di interrompere la query, Inline TVF ha impiegato 1 minuto, che è lo stesso tempo impiegato per selezionare il set di risultati in una tabella temporanea. Per utilizzare questa chiamata la funzione da una sottoquery in SELECT o CROSS APPLY.

CREATE FUNCTION dbo.Get_Min_Max_Date
(
    @Date1  datetime,
    @Date2  datetime,
    @Date3  datetime,
    @Date4  datetime,
    @Date5  datetime,
    @Date6  datetime,
    @Date7  datetime,
    @Date8  datetime,
    @Date9  datetime,
    @Date10 datetime
)
RETURNS TABLE
AS
RETURN
(
    SELECT      Max(DateValue)  Max_Date,
                Min(DateValue)  Min_Date
    FROM        (
                    VALUES  (@Date1),
                            (@Date2),
                            (@Date3),
                            (@Date4),
                            (@Date5),
                            (@Date6),
                            (@Date7),
                            (@Date8),
                            (@Date9),
                            (@Date10)
                )   AS Dates(DateValue)
)

5
SELECT 
    CASE 
        WHEN Date1 >= Date2 AND Date1 >= Date3 THEN Date1 
        WHEN Date2 >= Date3 THEN Date2 
        ELSE Date3
    END AS MostRecentDate 

Questo è leggermente più facile da scrivere e salta i passaggi di valutazione quando l'istruzione case viene valutata in ordine.


4
Attento. Se Date2 è NULL, la risposta sarà Date3; anche se Date1 è più grande.
— Disilluso il

4

Sfortunatamente la risposta di Lasse , sebbene apparentemente ovvia, ha un difetto cruciale. Non può gestire i valori NULL. Qualsiasi valore NULL singolo restituisce Date1. Sfortunatamente, qualsiasi tentativo di risolvere questo problema tende a diventare estremamente caotico e non si adatta a 4 o più valori molto bene.

la prima risposta di databyss sembrava (ed è) buona. Tuttavia, non era chiaro se la risposta potesse estrapolare facilmente a 3 valori da un join a più tabelle anziché ai 3 valori più semplici da una singola tabella. Volevo evitare di trasformare una tale query in una sottoquery solo per ottenere il massimo di 3 colonne, inoltre ero abbastanza sicuro che l'eccellente idea di databyss potesse essere ripulita un po '.

Quindi, senza ulteriori indugi, ecco la mia soluzione (derivata dall'idea di databyss).
Usa i cross-join selezionando le costanti per simulare l'effetto di un join multi-tabella. La cosa importante da notare è che tutti gli alias necessari vengono eseguiti correttamente (cosa che non sempre accade) e questo mantiene il modello abbastanza semplice e abbastanza scalabile attraverso colonne aggiuntive.

DECLARE @v1 INT ,
        @v2 INT ,
        @v3 INT
--SET @v1 = 1 --Comment out SET statements to experiment with 
              --various combinations of NULL values
SET @v2 = 2
SET @v3 = 3

SELECT  ( SELECT    MAX(Vals)
          FROM      ( SELECT    v1 AS Vals
                      UNION
                      SELECT    v2
                      UNION
                      SELECT    v3
                    ) tmp
          WHERE     Vals IS NOT NULL -- This eliminates NULL warning

        ) AS MaxVal
FROM    ( SELECT    @v1 AS v1
        ) t1
        CROSS JOIN ( SELECT @v2 AS v2
                   ) t2
        CROSS JOIN ( SELECT @v3 AS v3
                   ) t3

4

Problema: scegliere il valore di aliquota minima assegnato a un'entità Requisiti: i tassi di agenzia possono essere nulli

[MinRateValue] = 
CASE 
   WHEN ISNULL(FitchRating.RatingValue, 100) < = ISNULL(MoodyRating.RatingValue, 99) 
   AND  ISNULL(FitchRating.RatingValue, 100) < = ISNULL(StandardPoorsRating.RatingValue, 99) 
   THEN FitchgAgency.RatingAgencyName

   WHEN ISNULL(MoodyRating.RatingValue, 100) < = ISNULL(StandardPoorsRating.RatingValue , 99)
   THEN MoodyAgency.RatingAgencyName

   ELSE ISNULL(StandardPoorsRating.RatingValue, 'N/A') 
END 

Ispirato da questa risposta di Nat


3

Se si utilizza SQL Server 2005, è possibile utilizzare la funzionalità UNPIVOT. Ecco un esempio completo:

create table dates 
(
  number int,
  date1 datetime,
  date2 datetime,
  date3 datetime 
)

insert into dates values (1, '1/1/2008', '2/4/2008', '3/1/2008')
insert into dates values (1, '1/2/2008', '2/3/2008', '3/3/2008')
insert into dates values (1, '1/3/2008', '2/2/2008', '3/2/2008')
insert into dates values (1, '1/4/2008', '2/1/2008', '3/4/2008')

select max(dateMaxes)
from (
  select 
    (select max(date1) from dates) date1max, 
    (select max(date2) from dates) date2max,
    (select max(date3) from dates) date3max
) myTable
unpivot (dateMaxes For fieldName In (date1max, date2max, date3max)) as tblPivot

drop table dates

1
Penso che mi piaccia di più l'esempio UNION.
— Lance Fisher,

"Come si restituisce UN VALORE PER FILE del massimo di più colonne"
— Niikola,

3

Utilizzo di CROSS APPLY (per il 2005+) ....

SELECT MostRecentDate 
FROM SourceTable
    CROSS APPLY (SELECT MAX(d) MostRecentDate FROM (VALUES (Date1), (Date2), (Date3)) AS a(d)) md

3

Da SQL Server 2012 possiamo usare IIF .

 DECLARE @Date1 DATE='2014-07-03';
 DECLARE @Date2 DATE='2014-07-04';
 DECLARE @Date3 DATE='2014-07-05';

 SELECT IIF(@Date1>@Date2,
        IIF(@Date1>@Date3,@Date1,@Date3),
        IIF(@Date2>@Date3,@Date2,@Date3)) AS MostRecentDate

Molto carino, ma non gestisce i null. Ad esempio:DECLARE @Date1 DATE='2014-08-01'; DECLARE @Date2 DATE=null; DECLARE @Date3 DATE='2014-07-05'; /*this gets returned*/
— jumxozizi,

Potremmo gestire i null in questo modo:select IIF(@Date1 > @Date2 or @Date2 is null, IIF(@Date1 > @Date3 or @Date3 is null, @Date1, @Date3), IIF(@Date2 > @Date3 or @Date3 is null, @Date2, @Date3)) as MostRecentDate
— jumxozizi il

1

Prova a utilizzare UNPIVOT:

SELECT MAX(MaxDt) MaxDt
   FROM tbl 
UNPIVOT
   (MaxDt FOR E IN 
      (Date1, Date2, Date3)
)AS unpvt;

1

Preferisco soluzioni basate sul caso-quando, la mia ipotesi è che dovrebbe avere il minimo impatto sul possibile calo delle prestazioni rispetto ad altre possibili soluzioni come quelle con cross-apply, valori (), funzioni personalizzate ecc.

Ecco la versione case-when che gestisce valori null con la maggior parte dei casi di test possibili:

SELECT
    CASE 
        WHEN Date1 > coalesce(Date2,'0001-01-01') AND Date1 > coalesce(Date3,'0001-01-01') THEN Date1 
        WHEN Date2 > coalesce(Date3,'0001-01-01') THEN Date2 
        ELSE Date3
    END AS MostRecentDate
    , *
from 
(values
     (  1, cast('2001-01-01' as Date), cast('2002-01-01' as Date), cast('2003-01-01' as Date))
    ,(  2, cast('2001-01-01' as Date), cast('2003-01-01' as Date), cast('2002-01-01' as Date))
    ,(  3, cast('2002-01-01' as Date), cast('2001-01-01' as Date), cast('2003-01-01' as Date))
    ,(  4, cast('2002-01-01' as Date), cast('2003-01-01' as Date), cast('2001-01-01' as Date))
    ,(  5, cast('2003-01-01' as Date), cast('2001-01-01' as Date), cast('2002-01-01' as Date))
    ,(  6, cast('2003-01-01' as Date), cast('2002-01-01' as Date), cast('2001-01-01' as Date))
    ,( 11, cast(NULL         as Date), cast('2002-01-01' as Date), cast('2003-01-01' as Date))
    ,( 12, cast(NULL         as Date), cast('2003-01-01' as Date), cast('2002-01-01' as Date))
    ,( 13, cast('2003-01-01' as Date), cast(NULL         as Date), cast('2002-01-01' as Date))
    ,( 14, cast('2002-01-01' as Date), cast(NULL         as Date), cast('2003-01-01' as Date))
    ,( 15, cast('2003-01-01' as Date), cast('2002-01-01' as Date), cast(NULL         as Date))
    ,( 16, cast('2002-01-01' as Date), cast('2003-01-01' as Date), cast(NULL         as Date))
    ,( 21, cast('2003-01-01' as Date), cast(NULL         as Date), cast(NULL         as Date))
    ,( 22, cast(NULL         as Date), cast('2003-01-01' as Date), cast(NULL         as Date))
    ,( 23, cast(NULL         as Date), cast(NULL         as Date), cast('2003-01-01' as Date))
    ,( 31, cast(NULL         as Date), cast(NULL         as Date), cast(NULL         as Date))

) as demoValues(id, Date1,Date2,Date3)
order by id
;

e il risultato è:

MostRecent    id   Date1      Date2      Date3
2003-01-01    1    2001-01-01 2002-01-01 2003-01-01
2003-01-01    2    2001-01-01 2003-01-01 2002-01-01
2003-01-01    3    2002-01-01 2001-01-01 2002-01-01
2003-01-01    4    2002-01-01 2003-01-01 2001-01-01
2003-01-01    5    2003-01-01 2001-01-01 2002-01-01
2003-01-01    6    2003-01-01 2002-01-01 2001-01-01
2003-01-01    11   NULL       2002-01-01 2003-01-01
2003-01-01    12   NULL       2003-01-01 2002-01-01
2003-01-01    13   2003-01-01 NULL       2002-01-01
2003-01-01    14   2002-01-01 NULL       2003-01-01
2003-01-01    15   2003-01-01 2002-01-01 NULL
2003-01-01    16   2002-01-01 2003-01-01 NULL
2003-01-01    21   2003-01-01 NULL       NULL
2003-01-01    22   NULL       2003-01-01 NULL
2003-01-01    23   NULL       NULL       2003-01-01
NULL          31   NULL       NULL       NULL

1
oh dio, grazie signore! Ho passato così tanto tempo a fare questo diamine di una formula mostruosa che mi dava ancora dei nullità e ora vedo la luce alla fine del tunnel.
— Max S.

0

È possibile creare una funzione in cui si passano le date e quindi aggiungere la funzione all'istruzione select come di seguito. selezionare Numero, dbo.fxMost_Recent_Date (Data1, Data2, Data3), Costo

create FUNCTION  fxMost_Recent_Date 

(@ Date1 smalldatetime, @ Date2 smalldatetime, @ Date3 smalldatetime) RITORNA smalldatetime COME INIZIARE DECLARE @Result smalldatetime

declare @MostRecent smalldatetime

set @MostRecent='1/1/1900'

if @Date1>@MostRecent begin set @MostRecent=@Date1 end
if @Date2>@MostRecent begin set @MostRecent=@Date2 end
if @Date3>@MostRecent begin set @MostRecent=@Date3 end
RETURN @MostRecent

FINE



0

Un altro modo di usare CASE QUANDO

SELECT CASE true 
       WHEN max(row1) >= max(row2) THEN CASE true WHEN max(row1) >= max(row3) THEN max(row1) ELSE max(row3) end ELSE
       CASE true WHEN max(row2) >= max(row3) THEN max(row2) ELSE max(row3) END END
FROM yourTable

-1

inserisci qui la descrizione dell'immagineSopra la tabella è una tabella di stipendio dei dipendenti con stipendio1, stipendio2, stipendio3, stipendio4 come colonne. La query di seguito restituirà il valore massimo di quattro colonne

select  
 (select Max(salval) from( values (max(salary1)),(max(salary2)),(max(salary3)),(max(Salary4)))alias(salval)) as largest_val
 from EmployeeSalary

L'esecuzione sopra la query fornirà l'output come larger_val (10001)

La logica della query sopra è la seguente:

select Max(salvalue) from(values (10001),(5098),(6070),(7500))alias(salvalue)

l'output sarà 10001


Questa è quasi una copia della soluzione pubblicata il 29 luglio 11 da @sven
— Luuk il

-3

ecco una buona soluzione:

CREATE function [dbo].[inLineMax] (@v1 float,@v2 float,@v3 float,@v4 float)
returns float
as
begin
declare @val float
set @val = 0 
declare @TableVal table
(value float )
insert into @TableVal select @v1
insert into @TableVal select @v2
insert into @TableVal select @v3
insert into @TableVal select @v4

select @val= max(value) from @TableVal

return @val
end 

-3

Non so se è su SQL, ecc ... nella guida di M $ ACCESS c'è una funzione chiamata MAXA(Value1;Value2;...)che dovrebbe fare questo.

La speranza può aiutare qualcuno.

PD: i valori possono essere colonne o calcolati, ecc.


1
Microsoft Access è un prodotto completamente diverso. Inoltre, sei in grado di ottenere la tua richiesta di tale funzione? Non ho mai visto o sentito parlare di questo in Access.
— deutschZuid

1
MAXAè una funzione di Excel , non Access.
— Felix Eve,
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.