SQL MAX de múltiples columnas?

372

¿Cómo devuelve 1 valor por fila del máximo de varias columnas:

Nombre de la tabla

[Number, Date1, Date2, Date3, Cost]

Necesito devolver algo como esto:

[Number, Most_Recent_Date, Cost]

¿Consulta?

Ben B
fuente

Respuestas:

161

Bueno, puedes usar la declaración 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

[Para Microsoft SQL Server 2008 y superior, puede considerar la respuesta más simple de Sven a continuación.]

Lasse V. Karlsen
fuente
11
¿No sería suficiente usarlo WHEN Date1 > Date2 AND Date1 > Date3 THEN Date1; WHEN Date2 > Date3 THEN Date3; ELSE Date3?
Treb
21
La respuesta obvia, pero no funciona con valores NULL, e intentar arreglar eso se vuelve muy complicado.
Desilusionado el
55
Necro'ing esta publicación anterior, pero podría envolver cada fecha en una COALESCE para manejar NULL. Una de esas declaraciones CUANDO se vería así: CUANDO Fecha1> = COALESCE (Fecha2, '') Y Fecha1> = COALESCE (Fecha3, '') ENTONCES Fecha3 (haga lo mismo para el otro cuando)
Bill Sambrone
Para aquellos que vinieron aquí buscando una manera MySQL, echen un vistazo a @ bajafresh4life respuesta: stackoverflow.com/a/331873/1412157
LucaM
2
Por cierto, devuelve Date1 cuando Date2 es nulo, incluso si Date3> Date1.
jumxozizi
853

Aquí hay otra buena solución para la Maxfuncionalidad usando T-SQL y SQL Server

SELECT [Other Fields],
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MaxDate]
FROM [YourTableName]
Sven
fuente
47
La versión de SQL debe ser> = 2008.
Daniel
10
Esto funciona muy bien con 2008 y maneja NULLs. Muy buena solución.
nycdan
10
@Cheburek: Del valor (v), "valor" es el alias de la tabla virtual y "v" es el nombre de la columna virtual de los valores de fecha.
Jonas Lincoln
2
Esto es brillante. ¿Dónde puedo encontrar la documentación para esta tabla virtual Value ()?
Mi otro yo
33
Inicialmente tampoco entendí VALOR (v). Si desea comprender VALUE, pruebe esta consulta que crea una tabla virtual de 1 columna: SELECT * FROM (VALUES (1), (5), (1)) como listOfValues ​​(columnName) Y esta consulta que crea una tabla virtual de 2 columnas: SELECT * FROM (VALUES (1,2), (5,3), (1,4)) como tableOfValues ​​(columnName1, ColumnName2) Ahora puede entender por qué esa consulta de muestra tiene un valor AS (v). Mi consulta final se veía así: SELECCIONAR Max (currentValues) como Max FROM (VALUES (12), (25), (35)) AS allCurrents (currentValues) Escogerá el valor máximo que en este caso es 35.
Jackson
148

Si está usando MySQL, puede usar

SELECT GREATEST(col1, col2 ...) FROM table
bajafresh4life
fuente
41
tag is sqlserver
Codewerks
104
Es cierto, pero sigue siendo una respuesta muy útil ya que las personas encuentran esta pregunta en referencia a MySQL.
philfreo
44
También disponible en PostgreSQL desde 8.1 .
Frozen Flame
44
No maneja bien NULL, pero si se fusiona (col1, 0) alrededor de los valores de su columna, estará cocinando con gas. Vea esta respuesta stackoverflow.com/questions/9831851/…
Stan Quinn
Y qué hay de esta solución: stackoverflow.com/a/2166693/4824854
Sandburg
64

Hay 3 métodos más donde UNPIVOT(1) es el más rápido, seguido por Unpivot simulado (3) que es mucho más lento que (1) pero aún más rápido 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

Solución 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

Solución 2 (Subconsulta por fila)

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

Solución 3 (simulada 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
fuente
1
Agradable. No tenía conocimiento de los operadores PIVOT y UNPIVOT.
Sako73
¿Alguna idea de qué versiones de SQL Server son compatibles con pivot / unpivot?
Desilusionado el
1
@CraigYoung SQL Server 2005 con COMPATIBILITY_LEVEL establecido en 90.
Paul Syfrett
18

Cualquiera de las dos muestras a continuación funcionará:

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

El segundo es un complemento de la respuesta 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
fuente
La primera respuesta es buena, pero puede simplificarse significativamente. La segunda respuesta no funciona con valores NULL. Intentar solucionar ese problema se vuelve muy complicado.
Desilusionado el
Debe usar UNION ALL y no UNION para evitar una operación DISTINCT implícita innecesaria.
JamieVer
17

Para T-SQL (MSSQL 2008+)

SELECT
  (SELECT
     MAX(MyMaxName) 
   FROM ( VALUES 
            (MAX(Field1)), 
            (MAX(Field2)) 
        ) MyAlias(MyMaxName)
  ) 
FROM MyTable1
Doker
fuente
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
fuente
Funcionó en cualquier versión de SQL para mí, buena solución
Kirill
9

La función escalar causa todo tipo de problemas de rendimiento, por lo que es mejor incluir la lógica en una función con valor de tabla en línea si es posible. Esta es la función que utilicé para reemplazar algunas funciones definidas por el usuario que seleccionaron las fechas mín. / Máx. De una lista de hasta diez fechas. Cuando se probó en mi conjunto de datos de 1 millón de filas, la función escalar tardó más de 15 minutos antes de finalizar la consulta, el TVF en línea tardó 1 minuto, que es la misma cantidad de tiempo que seleccionar el conjunto de resultados en una tabla temporal. Para utilizar esta función, llame a la función desde una subconsulta en 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)
)
MartinC
fuente
5
SELECT 
    CASE 
        WHEN Date1 >= Date2 AND Date1 >= Date3 THEN Date1 
        WHEN Date2 >= Date3 THEN Date2 
        ELSE Date3
    END AS MostRecentDate 

Esto es un poco más fácil de escribir y omite los pasos de evaluación ya que la declaración del caso se evalúa en orden.

Nat
fuente
44
Cuidado. Si Date2 es NULL, la respuesta será Date3; incluso si Date1 es más grande.
Desilusionado el
4

Lamentablemente la respuesta de Lasse , aunque aparentemente obvia, tiene un defecto crucial. No puede manejar valores NULL. Cualquier valor NULL único hace que se devuelva Date1. Desafortunadamente, cualquier intento de solucionar ese problema tiende a ser extremadamente desordenado y no se escala muy bien a 4 o más valores.

primera respuesta de databyss parecía (y es) buena. Sin embargo, no estaba claro si la respuesta se extrapolaría fácilmente a 3 valores de una combinación de varias tablas en lugar de los 3 valores más simples de una sola tabla. Quería evitar convertir esa consulta en una subconsulta solo para obtener el máximo de 3 columnas, también estaba bastante seguro de que la excelente idea de databyss podría limpiarse un poco.

Entonces, sin más preámbulos, aquí está mi solución (derivada de la idea de databyss).
Utiliza constantes de selección de combinaciones cruzadas para simular el efecto de una combinación de varias tablas. Lo importante a tener en cuenta es que todos los alias necesarios se llevan a cabo correctamente (que no siempre es el caso) y esto mantiene el patrón bastante simple y bastante escalable a través de columnas adicionales.

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
Desilusionado
fuente
4

Problema: elija el valor de tasa mínimo dado a una entidad Requisitos: las tasas de agencia pueden ser nulas

[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 

Inspirado por esta respuesta de Nat

Luis Miguel Rosa
fuente
3

Si está utilizando SQL Server 2005, puede usar la función UNPIVOT. Aquí hay un ejemplo 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
Lance Fisher
fuente
1
Creo que me gusta más el ejemplo de UNION.
Lance Fisher
"¿Cómo se devuelve UN VALOR POR FILA del máximo de varias columnas"
Niikola
3

Usando CROSS APPLY (para 2005+) ....

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

Desde SQL Server 2012 podemos usar 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
fuente
Bastante agradable, pero no maneja nulos. Por ejemplo:DECLARE @Date1 DATE='2014-08-01'; DECLARE @Date2 DATE=null; DECLARE @Date3 DATE='2014-07-05'; /*this gets returned*/
jumxozizi
Podríamos manejar nulos como este: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
1

Intenta usar UNPIVOT:

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

Prefiero soluciones basadas en casos, cuando supongo que debería tener el menor impacto en la posible caída del rendimiento en comparación con otras posibles soluciones como aquellas con aplicación cruzada, valores (), funciones personalizadas, etc.

Aquí está la versión de caso en que maneja valores nulos con la mayoría de los casos de prueba posibles:

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
;

y el resultado es:

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
fuente
1
oh dios, gracias señor! Pasé tanto tiempo haciendo esta increíble fórmula de monstruos que todavía me daban nulos y ahora veo la luz al final del túnel.
Max S.
0

Puede crear una función donde pase las fechas y luego agregar la función a la declaración de selección como se muestra a continuación. seleccione Número, dbo.fxMost_Recent_Date (Fecha1, Fecha2, Fecha3), Costo

create FUNCTION  fxMost_Recent_Date 

(@ Date1 smalldatetime, @ Date2 smalldatetime, @ Date3 smalldatetime) DEVOLUCION smalldatetime COMO COMIENZA DECLARAR @Resultar 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

FINAL

DrYodo
fuente
0

Otra forma de usar CASO CUANDO

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

ingrese la descripción de la imagen aquíLa tabla de arriba es una tabla de salarios de empleados con salario1, salario2, salario3, salario4 como columnas. La consulta a continuación devolverá el valor máximo de cuatro columnas

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

La ejecución de la consulta anterior dará como resultado el mayor_val (10001)

La lógica de la consulta anterior es la siguiente:

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

la salida será 10001

Rayo Brijesh
fuente
Esta es casi una copia de la solución publicada el 29 de julio de 2011 por @sven
Luuk
-3

Aquí hay una buena solución:

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

No sé si está en SQL, etc ... en la ayuda de M $ ACCESS hay una función llamada MAXA(Value1;Value2;...)que se supone que debe hacer eso.

La esperanza puede ayudar a alguien.

PD: Los valores pueden ser columnas o calculados, etc.

claudio
fuente
1
Microsoft Access es un producto completamente diferente. Además, ¿puedes obtener tu reclamo de tal función? Nunca he visto ni oído hablar de esto en Access.
deutschZuid
1
MAXAes una función de Excel , no Access.
Felix Eve