SQL MAX de plusieurs colonnes?

372

Comment renvoyez-vous 1 valeur par ligne du maximum de plusieurs colonnes:

Nom de la table

[Number, Date1, Date2, Date3, Cost]

Je dois retourner quelque chose comme ça:

[Number, Most_Recent_Date, Cost]

Requete?

Ben B
la source

Réponses:

161

Eh bien, vous pouvez utiliser l'instruction 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

[Pour Microsoft SQL Server 2008 et supérieur, vous pouvez envisager la réponse plus simple de Sven ci-dessous.]

Lasse V. Karlsen
la source
11
Ne suffirait-il pas de l'utiliser WHEN Date1 > Date2 AND Date1 > Date3 THEN Date1; WHEN Date2 > Date3 THEN Date3; ELSE Date3?
Treb
21
La réponse évidente, mais cela ne fonctionne pas avec les valeurs NULL, et tenter de résoudre ce problème devient très compliqué.
Désillusionné le
5
Necro'ing cet ancien article, mais vous pouvez envelopper chaque date dans un COALESCE pour gérer les NULL. Une de ces instructions WHEN ressemblerait alors à: WHEN Date1> = COALESCE (Date2, '') AND Date1> = COALESCE (Date3, '') THEN Date3 (faites la même chose pour l'autre quand)
Bill Sambrone
pour ceux qui sont venus ici à la recherche d'un moyen MySQL, jetez un œil à @ bajafresh4life reply: stackoverflow.com/a/331873/1412157
LucaM
2
BTW, il renvoie Date1 lorsque Date2 est nul même si Date3> Date1.
jumxozizi
854

Voici une autre belle solution pour la Maxfonctionnalité utilisant T-SQL et SQL Server

SELECT [Other Fields],
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MaxDate]
FROM [YourTableName]
Sven
la source
47
La version SQL doit être> = 2008.
Daniel
10
Cela fonctionne très bien avec 2008 et gère les valeurs NULL. Très belle solution.
nycdan
10
@Cheburek: à partir de la valeur (v), "valeur" est l'alias de la table virtuelle et "v" est le nom de la colonne virtuelle des valeurs de date.
Jonas Lincoln
2
C'est génial. Où puis-je trouver la documentation de cette table virtuelle Value ()?
My Other Me
33
Au départ, je ne comprenais pas non plus VALUE (v). Si vous voulez comprendre VALUE, essayez cette requête qui crée une table virtuelle à 1 colonne: SELECT * FROM (VALUES (1), (5), (1)) as listOfValues ​​(columnName) Et cette requête qui crée une table virtuelle à 2 colonnes: SELECT * FROM (VALUES (1,2), (5,3), (1,4)) as tableOfValues ​​(columnName1, ColumnName2) Vous pouvez maintenant comprendre pourquoi cet exemple de requête contient une valeur AS (v). Ma dernière requête ressemblait à ceci: SELECT Max (currentValues) as Max FROM (VALUES (12), (25), (35)) AS allCurrents (currentValues) Il choisira la valeur maximale qui dans ce cas est 35.
Jackson
148

Si vous utilisez MySQL, vous pouvez utiliser

SELECT GREATEST(col1, col2 ...) FROM table
bajafresh4life
la source
41
tag is sqlserver
Codewerks
104
Certes, mais toujours une réponse très utile car les gens trouvent cette question en référence à MySQL.
philfreo
4
Également disponible dans PostgreSQL à partir de 8.1 .
Frozen Flame
4
Ne gère pas bien NULL, mais si vous fusionnez (col1, 0) autour des valeurs de votre colonne, vous cuisinerez avec du gaz voir cette réponse stackoverflow.com/questions/9831851/…
Stan Quinn
Et que dire de cette solution: stackoverflow.com/a/2166693/4824854
Sandburg
64

Il y a 3 autres méthodes où UNPIVOT(1) est de loin le plus rapide, suivi de Simulated Unpivot (3) qui est beaucoup plus lent que (1) mais toujours plus rapide que (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

Solution 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

Solution 2 (sous-requête par ligne)

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

Solution 3 (simulée 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
Niikola
la source
1
Agréable. Je n'étais pas au courant des opérateurs PIVOT et UNPIVOT.
Sako73
Avez-vous une idée des versions de SQL Server qui prennent en charge pivot / unpivot?
Désillusionné le
1
@CraigYoung SQL Server 2005 avec COMPATIBILITY_LEVEL défini sur 90.
Paul Syfrett
18

L'un des deux exemples ci-dessous fonctionnera:

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

Le second est un complément à la réponse de 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 
databyss
la source
La première réponse est bonne, mais peut être considérablement simplifiée. La deuxième réponse ne fonctionne pas avec les valeurs NULL. Tenter de résoudre ce problème devient très compliqué.
Désillusionné le
Vous devez utiliser UNION ALL et non UNION pour éviter une opération DISTINCT implicite inutile.
JamieSee
17

Pour T-SQL (MSSQL 2008+)

SELECT
  (SELECT
     MAX(MyMaxName) 
   FROM ( VALUES 
            (MAX(Field1)), 
            (MAX(Field2)) 
        ) MyAlias(MyMaxName)
  ) 
FROM MyTable1
doker
la source
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
Martin Smith
la source
A travaillé dans n'importe quelle version SQL pour moi, belle solution
Kirill
9

La fonction scalaire provoque toutes sortes de problèmes de performances, il est donc préférable d'envelopper la logique dans une fonction à valeur de table en ligne si possible. C'est la fonction que j'ai utilisée pour remplacer certaines fonctions définies par l'utilisateur qui sélectionnaient les dates Min / Max dans une liste de jusqu'à dix dates. Lorsqu'elle a été testée sur mon ensemble de données de 1 million de lignes, la fonction scalaire a pris plus de 15 minutes avant de tuer la requête. Le TVF en ligne a pris 1 minute, ce qui équivaut à la sélection de l'ensemble de résultats dans une table temporaire. Pour utiliser cet appel, la fonction à partir d'une sous-requête dans le SELECT ou un 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)
)
MartinC
la source
5
SELECT 
    CASE 
        WHEN Date1 >= Date2 AND Date1 >= Date3 THEN Date1 
        WHEN Date2 >= Date3 THEN Date2 
        ELSE Date3
    END AS MostRecentDate 

Ceci est légèrement plus facile à écrire et ignore les étapes d'évaluation car l'instruction de cas est évaluée dans l'ordre.

Nat
la source
4
Prudent. Si Date2 est NULL, la réponse sera Date3; même si Date1 est plus grand.
Désillusionné le
4

Malheureusement, la réponse de Lasse , bien qu'apparemment évidente, a un défaut crucial. Il ne peut pas gérer les valeurs NULL. Toute valeur NULL unique entraîne le renvoi de Date1. Malheureusement, toute tentative de résoudre ce problème a tendance à devenir extrêmement compliquée et ne se transforme pas très bien en 4 valeurs ou plus.

La première réponse de databyss semblait (et est) bonne. Cependant, il n'était pas clair si la réponse serait facilement extrapolable à 3 valeurs à partir d'une jointure multi-table au lieu des 3 valeurs plus simples d'une table unique. Je voulais éviter de transformer une telle requête en sous-requête juste pour obtenir le maximum de 3 colonnes, j'étais également sûr que l'excellente idée de databyss pouvait être nettoyée un peu.

Alors sans plus tarder, voici ma solution (dérivée de l'idée de databyss).
Il utilise des constantes de sélection de jointures croisées pour simuler l'effet d'une jointure multi-tables. La chose importante à noter est que tous les alias nécessaires s'exécutent correctement (ce qui n'est pas toujours le cas) et cela maintient le modèle assez simple et assez évolutif à travers des colonnes supplémentaires.

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
Désabusé
la source
4

Problème: choisissez la valeur de taux minimum donnée à une entité Exigences: Les taux d'agence peuvent être nuls

[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 

Inspiré par cette réponse de Nat

Luis Miguel Rosa
la source
3

Si vous utilisez SQL Server 2005, vous pouvez utiliser la fonctionnalité UNPIVOT. Voici un exemple complet:

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
Lance Fisher
la source
1
Je pense que j'aime mieux l'exemple UNION.
Lance Fisher
"Comment renvoyer UNE VALEUR PAR LIGNE du maximum de plusieurs colonnes"
Niikola
3

Utilisation de CROSS APPLY (pour 2005+) ....

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

À partir de SQL Server 2012, nous pouvons utiliser 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
abdulbasit
la source
Assez agréable, mais ne gère pas les null. Par exemple:DECLARE @Date1 DATE='2014-08-01'; DECLARE @Date2 DATE=null; DECLARE @Date3 DATE='2014-07-05'; /*this gets returned*/
jumxozizi
Nous pourrions gérer des 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
valeurs
1

Veuillez essayer d'utiliser UNPIVOT:

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

Je préfère les solutions basées sur le cas où, mon hypothèse est que cela devrait avoir le moins d'impact sur la baisse de performance possible par rapport à d'autres solutions possibles comme celles avec application croisée, valeurs (), fonctions personnalisées, etc.

Voici la version au cas où qui gère les valeurs nulles avec la plupart des cas de test possibles:

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
;

et le résultat est:

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
Robert Lujo
la source
1
oh mon dieu, merci monsieur! J'ai passé tellement de temps à faire cette sacrée formule de monstre qui me donnait toujours des valeurs nulles et maintenant je vois la lumière au bout du tunnel.
Max S.
0

Vous pouvez créer une fonction où vous passez les dates, puis ajouter la fonction à l'instruction select comme ci-dessous. sélectionnez Number, dbo.fxMost_Recent_Date (Date1, Date2, Date3), Cost

create FUNCTION  fxMost_Recent_Date 

(@ Date1 smalldatetime, @ Date2 smalldatetime, @ Date3 smalldatetime) RETOURNE smalldatetime COMME COMMENCER 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

FIN

DrYodo
la source
0

Basé sur la solution ScottPletcher de http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/Q_24204894.html, j'ai créé un ensemble de fonctions (par exemple GetMaxOfDates3, GetMaxOfDates13) pour trouver max jusqu'à 13 valeurs de date en utilisant UNION ALL. Voir la fonction T-SQL pour obtenir le maximum de valeurs de la même ligne. Cependant, je n'ai pas envisagé la solution UNPIVOT au moment d'écrire ces fonctions

Michael Freidgeim
la source
0

Une autre façon d'utiliser CASE WHEN

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
MABell
la source
-1

entrez la description de l'image iciLe tableau ci-dessus est un tableau des salaires des employés avec salaire1, salaire2, salaire3, salaire4 sous forme de colonnes.La requête ci-dessous renverra la valeur maximale sur quatre colonnes

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

L'exécution de la requête ci-dessus donnera la sortie comme la plus grande_val (10001)

La logique de la requête ci-dessus est la suivante:

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

la sortie sera 10001

Brijesh Ray
la source
Ceci est presque une copie de la solution publiée le 29 juillet '11 par @sven
Luuk
-3

voici une bonne solution:

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 
danvasiloiu
la source
-3

Je ne sais pas si c'est sur SQL, etc ... sur l'aide M $ ACCESS il y a une fonction appelée MAXA(Value1;Value2;...)qui est censée faire ça.

L'espoir peut aider quelqu'un.

PD: Les valeurs peuvent être des colonnes ou des valeurs calculées, etc.

Claudio
la source
1
Microsoft Access est un produit complètement différent. D'ailleurs, êtes-vous en mesure de faire valoir votre revendication d'une telle fonction? Je n'ai jamais vu ou entendu parler de cela dans Access.
deutschZuid
1
MAXAest une fonction Excel , pas Access.
Felix Eve du