SQL MAX de várias colunas?

372

Como você retorna 1 valor por linha do máximo de várias colunas:

Nome da tabela

[Number, Date1, Date2, Date3, Cost]

Preciso retornar algo como isto:

[Number, Most_Recent_Date, Cost]

Inquerir?

BenB
fonte

Respostas:

161

Bem, você pode usar a instrução 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 o Microsoft SQL Server 2008 e superior, considere a resposta mais simples de Sven abaixo.]

Lasse V. Karlsen
fonte
11
Não seria suficiente usar WHEN Date1 > Date2 AND Date1 > Date3 THEN Date1; WHEN Date2 > Date3 THEN Date3; ELSE Date3?
Treb
21
A resposta óbvia, mas não funciona com valores NULL, e tentar corrigir isso fica muito confuso.
Desiludido
5
Necro'ing esta postagem mais antiga, mas você pode agrupar cada data em um COALESCE para lidar com NULL's. Uma dessas instruções WHEN seria semelhante a: WHEN Date1> = COALESCE (Date2, '') AND Date1> = COALESCE (Date3, '') THEN Date3 (faça o mesmo para o outro quando)
Bill Sambrone
para quem veio aqui procurando uma maneira MySQL, dar uma olhada para resposta @ bajafresh4life: stackoverflow.com/a/331873/1412157
Lucam
2
BTW, retorna Data1 quando Data2 é nulo, mesmo que Data3> Data1.
precisa saber é o seguinte
853

Aqui está outra boa solução para a Maxfuncionalidade usando T-SQL e SQL Server

SELECT [Other Fields],
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MaxDate]
FROM [YourTableName]
Sven
fonte
47
Versão SQL deve ser> = 2008.
Daniel
10
Isso funciona muito bem com 2008 e lida com NULLs. Solução muito boa.
Ncicdan
10
@Cheburek: Do valor (v), "value" é o alias da tabela virtual e "v" é o nome da coluna virtual dos valores da data.
Jonas Lincoln
2
Isto é brilhante. Onde posso encontrar a documentação para esta tabela virtual Value ()?
Meu outro Mim
33
Inicialmente, também não entendi VALUE (v). Se você deseja entender VALUE, tente esta consulta que cria uma tabela virtual de 1 coluna: SELECT * FROM (VALUES (1), (5), (1)) como listOfValues ​​(columnName) E essa consulta que cria uma tabela virtual de 2 colunas: SELECT * FROM (VALUES (1,2), (5,3), (1,4)) como tableOfValues ​​(columnName1, ColumnName2) Agora você pode entender por que essa consulta de amostra possui o valor AS (v). Minha consulta final ficou assim: SELECT Max (currentValues) como Max FROM (VALUES (12), (25), (35)) AS allCurrents (currentValues) Ele selecionará o valor máximo que, neste caso, é 35.
Jackson
148

Se você estiver usando o MySQL, poderá usar

SELECT GREATEST(col1, col2 ...) FROM table
bajafresh4life
fonte
41
A tag é sqlserver
Codewerks
104
É verdade, mas ainda é uma resposta muito útil, pois as pessoas encontram essa pergunta em referência ao MySQL.
philfreo
4
Também disponível no PostgreSQL a partir da 8.1 .
Frozen Flame
4
Não manipula NULL está bem, mas se você se aglutinam (col1, 0) em torno de seus valores de coluna você vai ser cozinhar com gás ver esta resposta stackoverflow.com/questions/9831851/...
Stan Quinn
E quanto a esta solução: stackoverflow.com/a/2166693/4824854 #
Sandburg
64

Existem mais 3 métodos em que UNPIVOT(1) é o mais rápido de longe, seguido por Simulação não dinâmica (3), que é muito mais lenta que (1), mas ainda mais rápida 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

Solução 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

Solução 2 (subconsulta por linha)

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

Solução 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
fonte
11
Agradável. Eu não tinha conhecimento dos operadores PIVOT e UNPIVOT.
Sako73
Alguma idéia de quais versões do SQL Server suportam pivô / não pivô?
Desiludido
11
@CraigYoung SQL Server 2005 com COMPATIBILITY_LEVEL definido para 90.
Paul Syfrett
18

Qualquer uma das duas amostras abaixo 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

O segundo é um complemento para a resposta 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
fonte
A primeira resposta é boa, mas pode ser significativamente simplificada. A segunda resposta não funciona com valores NULL. Tentar corrigir esse problema fica muito confuso.
Desiludido
Você deve usar UNION ALL e não UNION para evitar uma operação DISTINCT implícita desnecessária.
JamieSee
17

Para T-SQL (MSSQL 2008+)

SELECT
  (SELECT
     MAX(MyMaxName) 
   FROM ( VALUES 
            (MAX(Field1)), 
            (MAX(Field2)) 
        ) MyAlias(MyMaxName)
  ) 
FROM MyTable1
doker
fonte
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
fonte
Trabalhou em qualquer versão SQL para mim, boa solução
Kirill
9

A função escalar causa todos os tipos de problemas de desempenho; portanto, é melhor agrupar a lógica em uma função com valor de tabela embutida, se possível. Esta é a função que eu usei para substituir algumas funções definidas pelo usuário que selecionaram as datas Mín / Máx de uma lista de até dez datas. Quando testada no meu conjunto de dados de 1 milhão de linhas, a Função Escalar demorou mais de 15 minutos antes de interromper a consulta. Para usar esta chamada, a função de uma subconsulta no SELECT ou em um 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
fonte
5
SELECT 
    CASE 
        WHEN Date1 >= Date2 AND Date1 >= Date3 THEN Date1 
        WHEN Date2 >= Date3 THEN Date2 
        ELSE Date3
    END AS MostRecentDate 

Isso é um pouco mais fácil de escrever e ignora as etapas de avaliação, pois a instrução de caso é avaliada em ordem.

Nat
fonte
4
Cuidado. Se Date2 for NULL, a resposta será Date3; mesmo se Date1 for maior.
Desiludido
4

Infelizmente, a resposta de Lasse , embora aparentemente óbvia, tem uma falha crucial. Ele não pode manipular valores NULL. Qualquer valor NULL único resulta no retorno de Data1. Infelizmente, qualquer tentativa de corrigir esse problema tende a ficar extremamente bagunçada e não aumenta muito bem para 4 ou mais valores.

A primeira resposta de databyss parecia (e é) boa. No entanto, não ficou claro se a resposta extrapolaria facilmente para 3 valores de uma junção de várias tabelas em vez dos 3 valores mais simples de uma única tabela. Eu queria evitar transformar essa consulta em uma subconsulta apenas para obter o máximo de 3 colunas. Também tinha certeza de que a excelente idéia dos bancos de dados poderia ser um pouco mais limpa.

Portanto, sem mais delongas, aqui está a minha solução (derivada da ideia do databyss).
Ele usa junções cruzadas selecionando constantes para simular o efeito de uma junção de várias tabelas. O importante a ser observado é que todos os aliases necessários são executados corretamente (o que nem sempre é o caso) e isso mantém o padrão bastante simples e razoavelmente escalável por meio de colunas adicionais.

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
Desiludido
fonte
4

Problema: escolha o valor mínimo da taxa dado a uma entidade Requisitos: As taxas da agência podem 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 resposta de Nat

Luis Miguel Rosa
fonte
3

Se você estiver usando o SQL Server 2005, poderá usar o recurso UNPIVOT. Aqui está um exemplo 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
fonte
11
Acho que gosto mais do exemplo da UNION.
Lance Fisher
"Como você retorna UM VALOR POR LINHA do máximo de várias colunas"
Niikola 09/09/09
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
fonte
3

No SQL Server 2012, podemos usar o 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
fonte
Muito bom, mas não lida com nulos. Por exemplo:DECLARE @Date1 DATE='2014-08-01'; DECLARE @Date2 DATE=null; DECLARE @Date3 DATE='2014-07-05'; /*this gets returned*/
jumxozizi 25/01
Nós poderíamos lidar com valores 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

Por favor, tente usar UNPIVOT:

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

Prefiro soluções baseadas em caso-em-caso, minha suposição é que ela tenha o menor impacto possível na queda de desempenho em comparação com outras soluções possíveis, como aquelas com aplicação cruzada, valores (), funções personalizadas etc.

Aqui está a versão case-when que lida com valores nulos com a maioria dos casos de teste possíveis:

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 o resultado é:

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
fonte
11
oh deus, obrigado senhor! Passei tanto tempo fazendo essa fórmula monstruosa que ainda me dava nulos e agora vejo a luz no fim do túnel.
Max S.
0

Você pode criar uma função na qual passa as datas e, em seguida, adiciona a função à instrução select como abaixo. selecione Número, dbo.fxMost_Recent_Date (Data1, Data2, Data3), Custo

create FUNCTION  fxMost_Recent_Date 

(@ Date1 smalldatetime, @ Date2 smalldatetime, @ Date3 smalldatetime) RETORNA smalldatetime COMO COMEÇAR A DECLARAR @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

FIM

DrYodo
fonte
0

Com base na solução de ScottPletcher , de http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/Q_24204894.html , criei um conjunto de funções (por exemplo, GetMaxOfDates3, GetMaxOfDates13) para encontrar o máximo de até 13 valores de data usando UNION ALL. Consulte a função T-SQL para obter o máximo de valores da mesma linha. No entanto, não considerei a solução UNPIVOT no momento em que escrevi essas funções.

Michael Freidgeim
fonte
0

Outra maneira de usar 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
fonte
-1

insira a descrição da imagem aquiA tabela acima é uma tabela salarial de funcionários com salário1, salário2, salário3, salário4 como colunas. A consulta abaixo retornará o valor máximo de quatro colunas

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

A execução acima da consulta fornecerá a saída como o maior valor (10001)

A lógica da consulta acima é a seguinte:

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

saída será 10001

Brijesh Ray
fonte
Esta é quase uma cópia da solução postada em 29 jul '11 por @sven
Luuk
-3

aqui está uma boa solução:

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

Eu não sei se é no SQL, etc ... na ajuda do M $ ACCESS, existe uma função chamada MAXA(Value1;Value2;...)que deveria fazer isso.

A esperança pode ajudar alguém.

PD: os valores podem ser colunas ou calculados, etc.

claudio
fonte
11
O Microsoft Access é um produto completamente diferente. Além disso, você é capaz de obter sua reivindicação dessa função? Eu nunca vi ou ouvi falar disso no Access.
deutschZuid
11
MAXAé uma função do Excel , não o Access.
Felix Eve