Obtenga las últimas fechas de varias columnas

18

Parece que debería ser fácil. ¿Cómo obtengo las últimas fechas que están en diferentes columnas?

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 

Me gustaría que el resultado sea:

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

Respuestas:

20

Usa una CASEexpresión:

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;

Manifestación

Tenga en cuenta que algunas bases de datos, como MySQL, SQL Server y SQLite, admiten una función escalar. SQL Server no lo hace, por lo que podemos usar una CASEexpresión como solución alternativa.

Editar:

Parece que en su tabla real, una o más de las tres columnas de fecha podrían tener NULLvalores. Podemos adaptar la consulta anterior de la siguiente manera:

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;

Manifestación

Tim Biegeleisen
fuente
no funciona, obtiene la fecha3 solo no obtiene la última fecha en las 3 columnas
Ahmed Alkhteeb
1
@AhmedAlkhteeb Edité mi respuesta para manejar también el caso en que podrían estar una o más columnas de fecha NULL.
Tim Biegeleisen, el
3
Entonces, muchas de las respuestas dadas aquí se romperían y no funcionarán. Honestamente, si necesita hacer esta comparación en incluso cuatro columnas, es posible que desee repensar el diseño de la tabla de la base de datos y, en su lugar, obtener cada valor de fecha en una fila separada . Su requisito sería trivial si tuviera cada fecha en una fila separada, porque entonces podríamos tomar el MAXuso GROUP BY. Entonces, mi respuesta a su pregunta es "no se solucionará", porque creo que quizás el diseño de su base de datos deba cambiar.
Tim Biegeleisen, el
1
Tim está aquí, @AhmedAlkhteeb si tiene columnas de 10 de fecha, es probable que tenga datos desnormalizados. Una pareja en una sola fila está bien, eso significa cosas diferentes (digamos un comienzo y un final, y una fecha de nacimiento y una fecha en que una persona se agregó al sistema), pero muchas fechas (10 de ellas) sugieren que agregar una nueva fecha en una columna cada vez que algo cambia; no insertando una nueva fila para mantener un historial. Si se tratara de la base de datos de una empresa de servicios de entrega, por ejemplo, no tendría una columna de fecha para cada posible paso del viaje; insertarías una nueva fila para cada una.
Larnu
1
@AhmedAlkhteeb en ese caso Larnu es correcto: debe tener una tabla con una acción ( call_case) y una marca de tiempo. No hay una sola mesa con 50 columnas
Dannnno
13

La respuesta actualmente aceptada es la mejor respuesta, pero no creo que explique por qué. Las otras respuestas ciertamente parecen mucho más limpias de un vistazo (quién quiere escribir esa fea declaración de caso), pero es probable que sean mucho peores cuando comience a operar a 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: )

Así es como configuro todo

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;

En mi sistema, esto me da 12,872,738 filas en la tabla. Si intento cada una de las consultas anteriores (ajustada para SELECT INTOno tener que esperar a que termine de imprimir los resultados en SSMS), obtengo los siguientes 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%

Si nos fijamos en los planes de consulta, resulta bastante obvio por qué: al agregar cualquier tipo de elemento no dinámico o agregado (o Dios no lo quiera STRING_SPLIT), terminará con todo tipo de operadores adicionales que no necesita (y obliga al plan a ir en paralelo, quitando recursos que otras consultas podrían desear). Por contrato, la CASEsolución basada no es paralela, se ejecuta muy rápidamente y es increíblemente simple.

En este caso, a menos que tenga recursos ilimitados (no los tiene), debe elegir el enfoque más simple y rápido.


Hubo una pregunta sobre qué hacer si necesita seguir agregando nuevas columnas y expandir la declaración del caso. Sí, esto se vuelve difícil de manejar, pero también lo hace cualquier otra solución. Si este es realmente un flujo de trabajo plausible, entonces debe rediseñar su tabla. Lo que quieres probablemente se parece a esto:

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;

Ciertamente, esto no está exento de posibles problemas de rendimiento y requerirá un ajuste cuidadoso del índice, pero es la mejor manera de manejar un número arbitrario de marcas de tiempo potenciales


En caso de que se eliminen las respuestas, aquí están las versiones que estaba comparando (en orden)

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
fuente
Este es un gran trabajo de detective +1, y me sorprende que haya evitado atraer votos a favor.
Tim Biegeleisen, el
es muy útil respuesta +1
Ahmed Alkhteeb
11

Prueba esto:

SELECT call_case,
  (SELECT
     MAX(call_case) 
   FROM ( VALUES 
            (MAX(date1)), 
            (MAX(date2))
            ,(max(date3)) 
        ) MyAlias(call_case)
  ) 
FROM #indebtedness
group by call_case
diablo rojo
fuente
@AhmedAlkhteeb. . . Esta es la mejor respuesta. Maneja NULLs, debe tener un buen rendimiento y se generaliza fácilmente a más columnas.
Gordon Linoff el
MAX () en VALUES () y GROUP BY no son necesarios y hace que la consulta sea más lenta; mejor simplemente use SELECT i.call_case, (SELECT MAX (d.date) FROM (VALUES ((i.date1)), ((i.date2)), ((i.date3))) AS d (date)) AS max_date DE #Endeudamiento AS i
Thomas Franz
8

FIDDLE SQL

Utilizar MAX()

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

Utilizar 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
fuente
2
No tengo ni idea de los votos negativos, en mi opinión, su ejemplo de uso de MAX es mucho más elegante que la solución aceptada (que se volvería muy engorrosa si hubiera un mayor número de columnas de fecha).
BarneyL
1
Estoy de acuerdo, con más valores, el método que usa VALUESes mucho más escalable que una CASEexpresión grande . A mí también me gustaría saber por qué se votó en contra, ya que el votante parece creer que hay un problema con el SQL y, por lo tanto, si nos dicen ese problema, todos podemos aprender de él.
Larnu
1

En mi opinión, Pivot es la mejor y más eficiente opción para esta consulta. Copie y pegue en el servidor MS SQL. Por favor revise el código escrito a continuación:

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

Esto realmente debería ser reevaluado a nivel de diseño como lo han indicado otros. A continuación se muestra un ejemplo de un diseño diferente que utiliza dos tablas para lograr mejor lo que parece que está buscando en sus resultados. Esto hará que el crecimiento sea mucho más favorable.

Aquí hay un ejemplo (se usan diferentes nombres de tabla):

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

Esto permite agregar más tipos de casos, agregar muchas más entradas de registro y proporciona un mejor diseño.

Este es solo un ejemplo para fines de aprendizaje.

Enoc
fuente
El rediseño de la base de datos puede no ser una opción, dependiendo de la situación del usuario. Hay otras opciones disponibles que no requieren reestructurar los datos.
DWRoelands
@DWRoelands Estoy de acuerdo en que puede no ser una opción, y tal vez debería haber dejado esto más claro. Solo respondía en base a otros comentarios de que un rediseño, de ser posible , sería la mejor solución y proporcionaría un ejemplo. Y soy consciente de que hay muchas razones por las que una base de datos no podría rediseñarse.
Enoch