Obter as últimas datas de várias colunas

18

Parece que deve ser fácil. Como obtenho as datas mais recentes em colunas diferentes

DROP TABLE #indebtedness
CREATE TABLE #indebtedness (call_case CHAR(10), date1 DATETIME, date2 DATETIME, date3 DATETIME)
INSERT #indebtedness VALUES ('Key1', '2019-10-30', '2019-11-30', '2019-10-25')
INSERT #indebtedness VALUES ('Key2', '2019-10-20', '2019-10-30', '2019-10-15')
INSERT #indebtedness VALUES ('Key3', '2019-11-11', '2019-10-29', '2019-10-30')
INSERT #indebtedness VALUES ('Key4',     null    , '2019-10-29', '2019-10-13')

select call_case, ?? AS 'Latest Date' from #indebtedness 

Eu gostaria que o resultado fosse:

call_case   Latest Date
Key1        2019-11-30 
Key2        2019-10-30 
Key3        2019-11-11 
Key4        2019-10-29 
Ahmed Alkhteeb
fonte

Respostas:

20

Use uma CASEexpressão:

SELECT
    call_case,
    CASE WHEN date1 > date2 AND date1 > date3
         THEN date1
         WHEN date2 > date3
         THEN date2
         ELSE date3 END AS [Latest Date]
FROM #indebtedness;

Demo

Observe que alguns bancos de dados, como MySQL, SQL Server e SQLite, suportam uma função escalar máxima. O SQL Server não, portanto, podemos usar uma CASEexpressão como solução alternativa.

Editar:

Parece que na sua tabela real, uma ou mais das três colunas de data podem ter NULLvalores. Podemos adaptar a consulta acima da seguinte maneira:

SELECT
    call_case,
    CASE WHEN (date1 > date2 OR date2 IS NULL) AND (date1 > date3 OR date3 IS NULL)
         THEN date1
         WHEN date2 > date3 OR date3 IS NULL
         THEN date2
         ELSE date3 END AS [Latest Date]
FROM #indebtedness;

Demo

Tim Biegeleisen
fonte
não trabalhando-lo tirar o DATE3 não só obter a última data nas 3 colunas
Ahmed Alkhteeb
11
@AhmedAlkhteeb editei minha resposta para também tratar do caso em que uma ou mais colunas de data podem estar NULL.
Tim Biegeleisen 6/11/19
3
Então, muitas das respostas dadas aqui quebrariam e não funcionariam. Honestamente, se você precisar fazer essa comparação em até quatro colunas, poderá repensar o design da tabela do banco de dados e, em vez disso, colocar cada valor de data em uma linha separada . Sua exigência seria trivial se você tivesse cada data em uma linha separada, pois poderíamos MAXusar o uso GROUP BY. Portanto, minha resposta para sua pergunta é "não será corrigida", porque acho que talvez o design do seu banco de dados precise mudar.
Tim Biegeleisen 6/11/19
11
O Tim está bem aqui, @AhmedAlkhteeb, se você tem dez colunas de datas, provavelmente possui dados desnormalizados. Um casal em uma única fileira é bom, isso significa coisas diferentes (digamos um Início e Fim, Data de nascimento e uma data em que uma pessoa foi adicionada ao sistema), mas muitas datas (dez delas) sugerem que você adicionar uma nova data a uma coluna sempre que algo mudar; não inserindo uma nova linha para manter um histórico. Se fosse o banco de dados de uma empresa de serviços de entrega, por exemplo, não haveria uma coluna de data para todas as etapas possíveis da jornada; você inseriria uma nova linha para cada uma.
Larnu 6/11/19
11
@AhmedAlkhteeb, nesse caso, Larnu está correto - você deve ter uma tabela com uma action ( call_case) e um carimbo de data / hora. Nem uma única tabela com 50 colunas
Dannnno
13

A resposta atualmente aceita é a melhor resposta, mas não acho que seja suficiente explicar o porquê. As outras respostas certamente parecem muito mais limpas à primeira vista (quem quer escrever essa declaração feia de caso), mas provavelmente serão muito piores quando você começar a operar em escala.

SELECT @@VERSION

Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64) 
Mar 18 2018 09:11:49 
Copyright (c) Microsoft Corporation
Developer Edition (64-bit) on Windows 10 Enterprise 10.0 <X64> (Build 17763: )

Aqui está como eu configuro tudo

DECLARE @Offset bigint = 0;
DECLARE @Max bigint = 10000000;

DROP TABLE IF EXISTS #Indebtedness;
CREATE TABLE #Indebtedness
(
  call_case char(10) COLLATE DATABASE_DEFAULT NOT NULL,
  date1     datetime NULL,
  date2     datetime NULL,
  date3     datetime NULL
);

WHILE @Offset < @Max
BEGIN

  INSERT INTO #Indebtedness
  ( call_case, date1, date2, date3 )
    SELECT @Offset + ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )),
           DATEADD( DAY,
                    CASE WHEN RAND() > 0 THEN 1
                         ELSE -1 END * ROUND( RAND(), 0 ),
                    CURRENT_TIMESTAMP ),
           DATEADD( DAY,
                    CASE WHEN RAND() > 0 THEN 1
                         ELSE -1 END * ROUND( RAND(), 0 ),
                    CURRENT_TIMESTAMP ),
           DATEADD( DAY,
                    CASE WHEN RAND() > 0 THEN 1
                         ELSE -1 END * ROUND( RAND(), 0 ),
                    CURRENT_TIMESTAMP )
      FROM master.dbo.spt_values a
        CROSS APPLY master.dbo.spt_values b;


  SET @Offset = @Offset + ROWCOUNT_BIG();
END;

No meu sistema, isso gera 12.872.738 linhas na tabela. Se eu tentar cada uma das consultas acima (ajustada para SELECT INTOque eu não precise esperar que ela termine de imprimir os resultados no SSMS), recebo os seguintes resultados:

Method                                | CPU time (ms) | Elapsed time (ms) | Relative Cost
-----------------------------------------------------------------------------------------
Tim Biegeleisen (CASE)                | 13485         | 2167              | 2%
Red Devil (Subquery over MAX columns) | 55187         | 9891              | 14%
Vignesh Kumar (Subquery over columns) | 33750         | 5139              | 5%
Serkan Arslan (UNPIVOT)               | 86205         | 15023             | 12%
Metal (STRING_SPLIT)                  | 459668        | 186742            | 68%

Se você olhar para os planos de consulta, torna-se bastante óbvio o motivo: ao adicionar qualquer tipo de dinâmica não agregada ou agregada (ou proibida STRING_SPLIT), você terminará com todos os tipos de operadores adicionais dos quais não precisa (e força o plano a paralelamente, retirando recursos que outras consultas podem querer). Por contrato, a CASEsolução baseada não é paralela, é executada muito rapidamente e é incrivelmente simples.

Nesse caso, a menos que você tenha recursos ilimitados (não possui), escolha a abordagem mais simples e rápida.


Havia uma pergunta sobre o que fazer se você precisar continuar adicionando novas colunas e expandindo a instrução de caso. Sim, isso fica pesado, mas o mesmo acontece com qualquer outra solução. Se esse é realmente um fluxo de trabalho plausível, você deve redesenhar sua tabela. O que você deseja provavelmente se parece com isso:

CREATE TABLE #Indebtedness2
(
  call_case     char(10) COLLATE DATABASE_DEFAULT NOT NULL,
  activity_type bigint   NOT NULL,  -- This indicates which date# column it was, if you care
  timestamp     datetime NOT NULL
);

SELECT Indebtedness.call_case,
       Indebtedness.activity_type,
       Indebtedness.timestamp
  FROM ( SELECT call_case,
                activity_type,
                timestamp,
                ROW_NUMBER() OVER ( PARTITION BY call_case
                                    ORDER BY timestamp DESC ) RowNumber
           FROM #Indebtedness2 ) Indebtedness
  WHERE Indebtedness.RowNumber = 1;

Isso certamente não está livre de possíveis problemas de desempenho e exigirá um ajuste cuidadoso do índice, mas é a melhor maneira de lidar com um número arbitrário de possíveis carimbos de data / hora


Caso alguma resposta seja excluída, aqui estão as versões que eu estava comparando (em ordem)

SELECT
    call_case,
    CASE WHEN date1 > date2 AND date1 > date3
         THEN date1
         WHEN date2 > date3
         THEN date2
         ELSE date3 END AS [Latest Date]
FROM #indebtedness;

SELECT call_case,
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MostRecentDate]
FROM #indebtedness

SELECT call_case,
  (SELECT
     MAX(call_case) 
   FROM ( VALUES 
            (MAX(date1)), 
            (MAX(date2))
            ,(max(date3)) 
        ) MyAlias(call_case)
  ) 
FROM #indebtedness
group by call_case

select call_case, MAX(date)  [Latest Date] from #indebtedness 
UNPIVOT(date FOR col IN ([date1], [date2], [date3])) UNPVT
GROUP BY call_case

select call_case , max(cast(x.Item as date)) as 'Latest Date' from #indebtedness  t
cross apply dbo.SplitString(concat(date1, ',', date2, ',', date3), ',') x
group by call_case
Dannnno
fonte
Este é um ótimo trabalho de detetive +1, e estou surpreso por ter evitado atrair votos positivos.
Tim Biegeleisen 8/11/19
resposta muito útil +1
Ahmed Alkhteeb 10/11/19
11

Tente o seguinte:

SELECT call_case,
  (SELECT
     MAX(call_case) 
   FROM ( VALUES 
            (MAX(date1)), 
            (MAX(date2))
            ,(max(date3)) 
        ) MyAlias(call_case)
  ) 
FROM #indebtedness
group by call_case
diabo Vermelho
fonte
@AhmedAlkhteeb. . . Esta é a melhor resposta. Ele lida com NULLs, deve ter bom desempenho e generaliza facilmente para mais colunas.
Gordon Linoff 6/11/19
o MAX () no VALUES () e o GROUP BY não são necessários e tornam a consulta mais lenta; é melhor usar SELECT i.call_case, (SELECT MAX (d.date) FROM (VALUES ((i.date1)), ((i.date2)), ((i.date3))) AS d (date)) AS max_date FROM #Indebtedness AS i
Thomas Franz
8

SQL FIDDLE

Usar MAX()

SELECT call_case,
  (SELECT Max(v) 
   FROM (VALUES (date1), (date2), (date3),...) AS value(v)) as [MostRecentDate]
FROM #indebtedness

Usar 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
 FROM  #indebtedness
Vignesh Kumar A
fonte
2
Nem uma pista sobre os votos negativos, na minha opinião, seu exemplo usando o MAX é muito mais elegante que a solução aceita (que ficará muito complicada se houver um número maior de colunas de datas).
BarneyL
11
Eu concordo, com mais valores que o método usado VALUESé muito mais escalável do que uma CASEexpressão grande . Eu também gostaria de saber por que a votação foi reduzida, pois o eleitor parece acreditar que há um problema com o SQL e, portanto, se eles nos disserem esse problema, todos nós podemos aprender com ele.
Larnu 6/11/19
1

Na minha opinião, o Pivot é a opção melhor e eficiente para esta consulta. Copie e cole no MS SQL SERVER. Por favor, verifique o código escrito abaixo:

CREATE TABLE #indebtedness (call_case CHAR(10), date1 DATETIME, date2 DATETIME, date3 DATETIME)
INSERT #indebtedness VALUES ('Key1', '2019-10-30', '2019-11-30', '2019-10-31')
INSERT #indebtedness VALUES ('Key2', '2019-10-20', '2019-10-30', '2019-11-21')
INSERT #indebtedness VALUES ('Key3', '2019-11-11', '2019-10-29', '2019-10-30')
INSERT #indebtedness VALUES ('Key4', Null, '2019-10-29', '2019-10-13')

--Solution-1:
SELECT        
    call_case,
    MAX(RecnetDate) as MaxDateColumn         
FROM #indebtedness
UNPIVOT
(RecnetDate FOR COL IN ([date1], [date2], [date3])) as TRANSPOSE
GROUP BY call_case 

--Solution-2:
select 
    call_case, case 
    when date1>date2 and date1 > date3 then date1
    when date2>date3                   then date2
    when date3>date1                   then date1 
   else date3 end as date
from #indebtedness as a 


Drop table #indebtedness
Satheesh
fonte
0

Isso realmente deve ser reavaliado no nível do design, como outros indicaram. Abaixo está um exemplo de um design diferente usando duas tabelas para realizar melhor o que parece que você está procurando em seus resultados. Isso tornará o crescimento muito mais favorável.

Aqui está um exemplo (nomes de tabela diferentes usados):

-- Drop pre-existing tables
DROP TABLE #call_log
DROP TABLE #case_type

-- Create table for Case Types
CREATE TABLE #case_type (id INT PRIMARY KEY CLUSTERED NOT NULL, 
    descript VARCHAR(50) NOT NULL)
INSERT #case_type VALUES (1,'No Answer')
INSERT #case_type VALUES (2,'Answer')
INSERT #case_type VALUES (3,'Not Exist')
INSERT #case_type VALUES (4,'whatsapp')
INSERT #case_type VALUES (5,'autodial')
INSERT #case_type VALUES (6,'SMS')

-- Create a Call Log table with a primary identity key and also an index on the call types
CREATE TABLE #call_log (call_num BIGINT PRIMARY KEY CLUSTERED IDENTITY NOT NULL,
    call_type INT NOT NULL REFERENCES #case_type(id), call_date DATETIME)
CREATE NONCLUSTERED INDEX ix_call_log_entry_type ON #call_log(call_type)
INSERT #call_log(call_type, call_date) VALUES (1,'2019-11-30')
INSERT #call_log(call_type, call_date) VALUES (2,'2019-10-15')
INSERT #call_log(call_type, call_date) VALUES (3,null)
INSERT #call_log(call_type, call_date) VALUES (3,'2019-10-29')
INSERT #call_log(call_type, call_date) VALUES (1,'2019-10-25')
INSERT #call_log(call_type, call_date) VALUES (2,'2019-10-30')
INSERT #call_log(call_type, call_date) VALUES (3,'2019-10-13')
INSERT #call_log(call_type, call_date) VALUES (2,'2019-10-20')
INSERT #call_log(call_type, call_date) VALUES (1,'2019-10-30')

-- use an aggregate to show only the latest date for each case type
SELECT DISTINCT ct.descript, MAX(cl.call_date) AS "Date" 
    FROM #call_log cl JOIN #case_type ct ON cl.call_type = ct.id GROUP BY ct.descript

Isso permite que mais tipos de casos sejam adicionados, muito mais entradas de log a serem adicionadas e fornece um design melhor.

Este é apenas um exemplo para fins de aprendizado.

Enoch
fonte
Redesenhar o banco de dados pode não ser uma opção, dependendo da situação do usuário. Existem outras opções disponíveis que não exigem a reestruturação dos dados.
DWRoelands
@DWRoelands Concordo que pode não ser uma opção, e talvez eu devesse ter deixado isso mais claro. Eu estava respondendo com base em outros comentários que um redesenho, se possível , seria a melhor solução e forneceria um exemplo. E estou ciente de que há muitas razões pelas quais um banco de dados não poderia ser redesenhado.
Enoch