Obtenez les dernières dates de plusieurs colonnes


18

Il semble que cela devrait être facile. Comment obtenir les dernières dates qui se trouvent dans différentes colonnes

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 

J'aimerais que le résultat soit:

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

Réponses:


20

Utilisez une CASEexpression:

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;

Démo

Notez que certaines bases de données, telles que MySQL, SQL Server et SQLite, prennent en charge une plus grande fonction scalaire. SQL Server ne le fait pas, nous pouvons donc utiliser une CASEexpression comme solution de contournement.

Éditer:

Il semble que dans votre table réelle, une ou plusieurs des trois colonnes de date pourraient avoir des NULLvaleurs. Nous pouvons adapter la requête ci-dessus comme suit:

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;

Démo


ça ne marche pas obtenir la date3 seulement ne pas avoir la dernière date dans les 3 colonnes
Ahmed Alkhteeb

1
@AhmedAlkhteeb J'ai modifié ma réponse pour gérer également le cas où une ou plusieurs colonnes de date pourraient se trouver NULL.
Tim Biegeleisen

3
Ensuite, la plupart des réponses données ici se briseraient et ne fonctionneraient pas. Honnêtement, si vous devez faire cette comparaison même sur quatre colonnes, vous voudrez peut-être repenser la conception de votre table de base de données et obtenir à la place chaque valeur de date sur une ligne distincte . Votre exigence serait triviale si vous aviez chaque date sur une ligne distincte, car alors nous pourrions simplement prendre l' MAXutilisation GROUP BY. Donc, ma réponse à votre question est «ne résoudra pas», car je pense que la conception de votre base de données doit peut-être changer.
Tim Biegeleisen

1
Tim est ici, @AhmedAlkhteeb si vous avez 10 colonnes de date, vous avez probablement des données dénormalisées. Un couple dans une seule rangée est très bien, cela signifie différentes choses (disons un début et une fin, une date de naissance et une date à laquelle une personne a été ajoutée au système), mais de nombreuses dates (10 d'entre elles) suggèrent que vous êtes ajouter une nouvelle date dans une colonne chaque fois que quelque chose change; ne pas insérer de nouvelle ligne pour conserver un historique. S'il s'agissait d'une base de données d'une entreprise de services de livraison, par exemple, elle n'aurait pas de colonne de date pour chaque étape possible du voyage; vous insérez une nouvelle ligne pour chacun.
Larnu

1
@AhmedAlkhteeb dans ce cas, Larnu a raison - vous devriez avoir une table avec une action ( call_case) et un horodatage. Pas une seule table avec 50 colonnes
Dannnno

13

La réponse actuellement acceptée est la meilleure réponse, mais je ne pense pas qu'elle explique assez bien pourquoi. Les autres réponses semblent certainement beaucoup plus propres en un coup d'œil (qui veut écrire cette déclaration de cas laide), mais sont susceptibles d'être bien pires lorsque vous commencez à opérer à grande échelle.

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

Voici comment j'ai tout mis en place

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;

Sur mon système, cela me donne 12 872 738 lignes dans le tableau. Si j'essaye chacune des requêtes ci-dessus (peaufinée pour SELECT INTOne pas avoir à attendre qu'elle termine l'impression des résultats dans SSMS), j'obtiens les résultats suivants:

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 vous regardez les plans de requête, il devient assez évident pourquoi - en ajoutant tout type de pivot ou d'agrégat (ou le ciel ne plaise STRING_SPLIT), vous vous retrouverez avec toutes sortes d'opérateurs supplémentaires dont vous n'avez pas besoin (et cela force le plan à aller en parallèle, en supprimant les ressources que d'autres requêtes pourraient souhaiter). Par contrat, la CASEsolution basée ne va pas en parallèle, fonctionne très rapidement et est incroyablement simple.

Dans ce cas, à moins que vous n'ayez des ressources illimitées (vous n'en avez pas), vous devez choisir l'approche la plus simple et la plus rapide.


Il y avait une question de quoi faire si vous devez continuer à ajouter de nouvelles colonnes et à développer l'instruction case. Oui, cela devient compliqué, mais il en va de même pour toutes les autres solutions. S'il s'agit en fait d'un flux de travail plausible, vous devez reconcevoir votre table. Ce que vous voulez ressemble probablement à ceci:

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;

Ce n'est certainement pas exempt de problèmes de performances potentiels, et nécessitera un réglage minutieux de l'index, mais c'est le meilleur moyen de gérer un nombre arbitraire d'horodatages potentiels


Dans le cas où des réponses seraient supprimées, voici les versions que je comparais (dans l'ordre)

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

C'est un excellent travail de détective +1, et je suis surpris qu'il n'ait pas attiré de votes positifs.
Tim Biegeleisen

c'est une réponse très utile +1
Ahmed Alkhteeb

11

Essaye ça:

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

@AhmedAlkhteeb. . . C'est la meilleure réponse. Il gère NULLs, doit avoir de bonnes performances et se généralise facilement à plus de colonnes.
Gordon Linoff

le MAX () dans le VALUES () et le GROUP BY ne sont pas nécessaires et rend la requête plus lente; il vaut mieux utiliser 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

Utilisation MAX()

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

Utilisation 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

2
Pas un indice sur les votes négatifs, à mon avis, votre exemple d'utilisation de MAX est beaucoup plus élégant que la solution acceptée (qui deviendra très lourde s'il y avait un plus grand nombre de colonnes de date).
BarneyL

1
Je suis d'accord, avec plus de valeurs, la méthode utilisant VALUESest beaucoup plus évolutive qu'une grande CASEexpression. Je voudrais moi aussi savoir pourquoi il a été rétrogradé, car l'électeur semble croire qu'il y a un problème avec le SQL, et donc s'il nous dit ce problème, nous pouvons tous en tirer des leçons.
Larnu

1

À mon avis, Pivot est l'option la meilleure et la plus efficace pour cette requête. Copiez et collez dans le SERVEUR MS SQL. Veuillez vérifier le code écrit ci-dessous:

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

0

Cela devrait vraiment être réévalué au niveau de la conception, comme d'autres l'ont indiqué. Vous trouverez ci-dessous un exemple de conception différente utilisant deux tableaux pour mieux accomplir ce que vous recherchez dans vos résultats. Cela rendra la croissance beaucoup plus favorable.

Voici un exemple (différents noms de table utilisés):

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

Cela permet d'ajouter plus de types de cas, d'ajouter beaucoup plus d'entrées de journal et offre une meilleure conception.

Ceci est juste un exemple à des fins d'apprentissage.


La refonte de la base de données peut ne pas être une option, selon la situation de l'utilisateur. Il existe d'autres options qui ne nécessitent pas de restructuration des données.
DWRoelands

@DWRoelands Je conviens que ce n'est peut-être pas une option, et j'aurais peut-être dû le préciser. Je répondais simplement sur la base d'autres commentaires qu'un remaniement, si possible , serait la meilleure solution et en fournissant un exemple. Et je suis bien conscient qu'il existe de nombreuses raisons pour lesquelles une base de données ne pourrait pas être repensée.
Enoch
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.