Holen Sie sich die letzten Daten aus mehreren Spalten

18

Das fühlt sich so an, als ob es einfach sein sollte. Wie erhalte ich die neuesten Daten in verschiedenen Spalten?

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 

Ich möchte, dass das Ergebnis lautet:

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

Antworten:

20

Verwenden Sie einen CASEAusdruck:

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

Beachten Sie, dass einige Datenbanken wie MySQL, SQL Server und SQLite eine skalar größte Funktion unterstützen. SQL Server nicht, daher können wir einen CASEAusdruck als Problemumgehung verwenden.

Bearbeiten:

Es scheint, dass in Ihrer tatsächlichen Tabelle eine oder mehrere der drei Datumsspalten NULLWerte haben können. Wir können die obige Abfrage wie folgt anpassen:

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
quelle
es funktioniert nicht es bekommt das Datum3 nur nicht das letzte Datum in den 3 Spalten
Ahmed Alkhteeb
1
@AhmedAlkhteeb Ich habe meine Antwort bearbeitet, um auch den Fall zu behandeln, in dem sich eine oder mehrere Datumsspalten befinden könnten NULL.
Tim Biegeleisen
3
Dann würden viele der hier gegebenen Antworten brechen und nicht funktionieren. Wenn Sie diesen Vergleich über vier Spalten hinweg durchführen müssen, sollten Sie das Design Ihrer Datenbanktabelle überdenken und stattdessen jeden Datumswert in eine separate Zeile übertragen . Ihre Anforderung wäre trivial, wenn Sie jedes Datum in einer separaten Zeile hätten, denn dann könnten wir einfach die MAXVerwendung übernehmen GROUP BY. Meine Antwort auf Ihre Frage lautet also "wird nicht behoben", da ich denke, dass sich Ihr Datenbankdesign möglicherweise ändern muss.
Tim Biegeleisen
1
Tim ist genau hier, @AhmedAlkhteeb. Wenn Sie 10 Datumsspalten haben, haben Sie wahrscheinlich Daten denormalisiert. Ein Paar in einer einzelnen Reihe ist in Ordnung, das bedeutet verschiedene Dinge (sagen wir einen Anfang und ein Ende und ein Geburtsdatum und ein Datum, an dem eine Person zum System hinzugefügt wurde), aber viele Daten (10 davon) deuten darauf hin, dass Sie es sind jedes Mal, wenn sich etwas ändert, ein neues Datum in eine Spalte einfügen; keine neue Zeile einfügen, um einen Verlauf zu pflegen. Wenn es sich beispielsweise um eine Datenbank eines Lieferserviceunternehmens handeln würde, hätte es nicht für jeden möglichen Schritt der Reise eine Datumsspalte. Sie würden für jede eine neue Zeile einfügen.
Larnu
1
@AhmedAlkhteeb in diesem Fall ist Larnu korrekt - Sie sollten eine Tabelle mit einer Aktion ( call_case) und einem Zeitstempel haben. Keine einzige Tabelle mit 50 Spalten
Dannnno
13

Die derzeit akzeptierte Antwort ist die beste Antwort, aber ich denke nicht, dass sie gut genug ist, um zu erklären, warum. Die anderen Antworten sehen auf den ersten Blick sicherlich viel sauberer aus (wer möchte diese hässliche Fallerklärung schreiben), sind aber wahrscheinlich viel schlechter, wenn Sie anfangen, in großem Maßstab zu arbeiten.

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

So richte ich alles ein

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;

Auf meinem System werden dadurch 12.872.738 Zeilen in der Tabelle angezeigt. Wenn ich jede der oben genannten Abfragen versuche (angepasst, SELECT INTOdamit ich nicht warten muss, bis der Druck der Ergebnisse in SSMS abgeschlossen ist), erhalte ich die folgenden Ergebnisse:

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%

Wenn Sie sich die Abfragepläne ansehen, wird es ziemlich offensichtlich, warum - wenn Sie irgendeine Art von Unpivot oder Aggregat hinzufügen (oder der Himmel verbietet STRING_SPLIT), erhalten Sie alle möglichen zusätzlichen Operatoren, die Sie nicht benötigen (und das zwingt den Plan dazu Gehen Sie parallel und nehmen Sie Ressourcen weg, die andere Abfragen möglicherweise benötigen. Laut Vertrag CASEläuft die basierte Lösung nicht parallel, läuft sehr schnell und ist unglaublich einfach.

In diesem Fall sollten Sie den einfachsten und schnellsten Ansatz wählen, es sei denn, Sie verfügen über unbegrenzte Ressourcen (Sie haben keine).


Es stellte sich die Frage, was zu tun ist, wenn Sie ständig neue Spalten hinzufügen und die case-Anweisung erweitern müssen. Ja, das wird unhandlich, aber jede andere Lösung auch. Wenn dies tatsächlich ein plausibler Workflow ist, sollten Sie Ihre Tabelle neu gestalten. Was Sie wollen, sieht wahrscheinlich ungefähr so ​​aus:

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;

Dies ist sicherlich nicht frei von potenziellen Leistungsproblemen und erfordert eine sorgfältige Indexoptimierung, ist jedoch der beste Weg, um mit einer beliebigen Anzahl potenzieller Zeitstempel umzugehen


Falls irgendwelche Antworten gelöscht werden, hier sind die Versionen, die ich verglichen habe (in der Reihenfolge)

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
quelle
Dies ist eine großartige Detektivarbeit +1, und ich bin überrascht, dass keine Gegenstimmen erzielt wurden.
Tim Biegeleisen
es ist sehr hilfreich Antwort +1
Ahmed Alkhteeb
11

Versuche dies:

SELECT call_case,
  (SELECT
     MAX(call_case) 
   FROM ( VALUES 
            (MAX(date1)), 
            (MAX(date2))
            ,(max(date3)) 
        ) MyAlias(call_case)
  ) 
FROM #indebtedness
group by call_case
Roter Teufel
quelle
@ AhmedAlkhteeb. . . Dies ist die beste Antwort. Es handhabt NULLs, sollte eine gute Leistung haben und lässt sich leicht auf mehr Spalten verallgemeinern.
Gordon Linoff
MAX () in VALUES () und GROUP BY sind nicht erforderlich und verlangsamen die Abfrage. Verwenden Sie besser 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

Verwenden MAX()

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

Verwenden 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.
quelle
2
Kein Hinweis auf die Abstimmungen, meiner Meinung nach ist Ihr Beispiel mit MAX weitaus eleganter als die akzeptierte Lösung (die bei einer größeren Anzahl von Datumsspalten sehr umständlich wird).
BarneyL
1
Ich stimme zu, mit mehr Werten ist die verwendete Methode VALUESweitaus skalierbarer als ein großer CASEAusdruck. Ich würde auch gerne erfahren, warum es abgelehnt wurde, da der Wähler zu glauben scheint , dass es ein Problem mit dem SQL gibt, und wenn sie uns dieses Problem mitteilen, können wir alle daraus lernen.
Larnu
1

Aus meiner Sicht ist Pivot die beste und effizienteste Option für diese Abfrage. Kopieren und Einfügen in den MS SQL Server. Bitte überprüfen Sie den unten angegebenen Code:

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

Dies sollte wirklich auf der Designebene neu bewertet werden, wie andere angegeben haben. Im Folgenden finden Sie ein Beispiel für ein anderes Design, bei dem zwei Tabellen verwendet werden, um das zu erreichen, wonach Sie in Ihren Ergebnissen suchen. Dies wird das Wachstum viel günstiger machen.

Hier ist ein Beispiel (verschiedene Tabellennamen verwendet):

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

Dies ermöglicht das Hinzufügen weiterer Falltypen, das Hinzufügen von viel mehr Protokolleinträgen und ein besseres Design.

Dies ist nur ein Beispiel für Lernzwecke.

Henoch
quelle
Abhängig von der Situation des Benutzers ist eine Neugestaltung der Datenbank möglicherweise keine Option. Es stehen andere Optionen zur Verfügung, für die keine Umstrukturierung der Daten erforderlich ist.
DWRoelands
@DWRoelands Ich würde zustimmen, dass dies möglicherweise keine Option ist, und vielleicht hätte ich dies klarer machen sollen. Ich antwortete nur aufgrund anderer Kommentare, dass eine Neugestaltung, wenn möglich , die bessere Lösung wäre und ein Beispiel liefern würde. Und mir ist klar, dass es viele Gründe gibt, warum eine Datenbank nicht neu gestaltet werden kann.
Henoch