Uzyskaj ostatnie daty z wielu kolumn

18

To wydaje się łatwe. Jak uzyskać najnowsze daty w różnych kolumnach

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 

Chciałbym, aby wynik był:

call_case   Latest Date
Key1        2019-11-30 
Key2        2019-10-30 
Key3        2019-11-11 
Key4        2019-10-29 
Ahmed Alkhteeb
źródło

Odpowiedzi:

20

Użyj CASEwyrażenia:

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;

Próbny

Należy pamiętać, że niektóre bazy danych, takie jak MySQL, SQL Server i SQLite, obsługują największą funkcję skalarną. SQL Server nie, więc możemy użyć CASEwyrażenia jako obejścia.

Edytować:

Wygląda na to, że w Twojej rzeczywistej tabeli co najmniej jedna z trzech kolumn dat może mieć NULLwartości. Możemy dostosować powyższe zapytanie w następujący sposób:

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;

Próbny

Tim Biegeleisen
źródło
to nie działa, dostaje datę 3 tylko nie otrzymuje ostatniej daty w 3 kolumnach
Ahmed Alkhteeb
1
@AhmedAlkhteeb Zredagowałem moją odpowiedź, aby poradzić sobie również z przypadkiem, w którym może znajdować się jedna lub więcej kolumn daty NULL.
Tim Biegeleisen,
3
Wówczas wiele z podanych tutaj odpowiedzi złamałoby się i nie zadziała. Szczerze mówiąc, jeśli musisz wykonać to porównanie nawet w czterech kolumnach, możesz ponownie przemyśleć projekt tabeli bazy danych i zamiast tego umieścić każdą wartość daty w osobnym wierszu . Twój wymóg byłby banalny jeśli miał każdy dzień w oddzielnym wierszu, bo wtedy moglibyśmy podjąć MAXużyciu GROUP BY. Więc moja odpowiedź na twoje pytanie brzmi: „nie naprawi się”, ponieważ myślę, że być może projekt bazy danych musi się zmienić.
Tim Biegeleisen,
1
Tim jest tutaj, @ AhmedAlkhteeb, jeśli masz 10 kolumn daty, prawdopodobnie masz zdenormalizowane dane. Para w jednym rzędzie jest w porządku, co oznacza różne rzeczy (powiedzmy początek i koniec oraz datę urodzenia i datę dodania osoby do systemu), ale wiele dat (10 z nich) sugeruje, że jesteś dodawanie nowej daty do kolumny za każdym razem, gdy coś się zmienia; nie wstawianie nowego wiersza w celu zachowania historii. Gdyby na przykład była to baza danych firmy kurierskiej, nie miałaby kolumny dat dla każdego możliwego etapu podróży; wstawiłbyś nowy wiersz dla każdego.
Larnu,
1
@AhmedAlkhteeb w takim przypadku Larnu ma rację - powinieneś mieć tabelę z akcją ( call_case) i znacznikiem czasu. Ani jednego stołu z 50 kolumnami
Dannnno
13

Obecnie akceptowane odpowiedź jest najlepszą odpowiedzią, ale nie sądzę, że ma wystarczająco dobrą robotę wyjaśniając dlaczego. Inne odpowiedzi na pierwszy rzut oka wyglądają o wiele czystiej (kto chce napisać to brzydkie stwierdzenie przypadku), ale prawdopodobnie będzie znacznie gorzej, gdy zaczniesz działać na dużą skalę.

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

Oto jak skonfigurowałem wszystko

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;

W moim systemie otrzymałem 12,872 738 wierszy w tabeli. Jeśli wypróbuję każde z powyższych zapytań (poprawione, aby SELECT INTOnie trzeba było czekać na zakończenie drukowania wyników w SSMS), otrzymam następujące wyniki:

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%

Jeśli spojrzysz na plany zapytań, stanie się dość oczywiste, dlaczego - dodając dowolny rodzaj unpivot lub agregacji (lub niebiańskiego zabrania STRING_SPLIT) otrzymasz szereg różnych operatorów, których nie potrzebujesz (i to zmusza plan do równolegle, zabierając zasoby, których mogą chcieć inne zapytania). Zgodnie z umową, CASEoparte na rozwiązaniu rozwiązanie nie idzie równolegle, działa bardzo szybko i jest niezwykle proste.

W takim przypadku, chyba że masz nieograniczone zasoby (nie masz), powinieneś wybrać najprostsze i najszybsze podejście.


Pojawiło się pytanie, co należy zrobić, jeśli trzeba dodawać nowe kolumny i rozszerzać instrukcję case. Tak, robi się to niewygodne, ale każde inne rozwiązanie. Jeśli jest to w rzeczywistości możliwy przepływ pracy, powinieneś przeprojektować swoją tabelę. To, czego chcesz, prawdopodobnie wygląda mniej więcej tak:

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;

Z pewnością nie jest to wolne od potencjalnych problemów z wydajnością i będzie wymagało starannego dostrajania indeksu, ale jest najlepszym sposobem na obsługę dowolnej liczby potencjalnych znaczników czasu


W przypadku usunięcia jakichkolwiek odpowiedzi, oto wersje, które porównywałem (w kolejności)

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
źródło
To świetna praca detektywistyczna +1 i dziwi mnie, że udało jej się uniknąć aprobaty.
Tim Biegeleisen,
bardzo pomocna odpowiedź +1
Ahmed Alkhteeb,
11

Spróbuj tego:

SELECT call_case,
  (SELECT
     MAX(call_case) 
   FROM ( VALUES 
            (MAX(date1)), 
            (MAX(date2))
            ,(max(date3)) 
        ) MyAlias(call_case)
  ) 
FROM #indebtedness
group by call_case
czerwony diabeł
źródło
@AhmedAlkhteeb. . . To najlepsza odpowiedź. Obsługuje NULLs, powinien mieć dobrą wydajność i łatwo generalizować do większej liczby kolumn.
Gordon Linoff,
MAX () w VALUES () i GROUP BY nie są konieczne i spowalnia zapytanie; lepiej po prostu użyj SELECT i.call_case, (SELECT MAX (d.date) FROM (VALUES ((i.date1)), ((i.date2)), ((i.date3))) AS d (data)) AS max_date FROM #Indebtedness AS i
Thomas Franz
8

FIDDLE SQL

Posługiwać się MAX()

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

Posługiwać się 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.
źródło
2
Nie jest to wskazówka na temat słabszych głosów, moim zdaniem twój przykład użycia MAX jest o wiele bardziej elegancki niż zaakceptowane rozwiązanie (które stanie się bardzo kłopotliwe, jeśli będzie więcej kolumn daty).
BarneyL,
1
Zgadzam się, że przy większej liczbie wartości stosowana metoda VALUESjest znacznie bardziej skalowalna niż duże CASEwyrażenie. Ja również chciałbym dowiedzieć się, dlaczego został on odrzucony, ponieważ głosujący wydaje się wierzyć, że istnieje problem z SQL, a zatem jeśli powiedzą nam o tym problemie, wszyscy możemy się z niego nauczyć.
Larnu,
1

Moim zdaniem Pivot jest najlepszą i wydajną opcją dla tego zapytania. Skopiuj i wklej w MS SQL SERVER. Sprawdź kod napisany poniżej:

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
źródło
0

To naprawdę powinno zostać ponownie ocenione na poziomie projektu, jak wskazali inni. Poniżej znajduje się przykład innego projektu wykorzystującego dwie tabele, aby lepiej osiągnąć to, czego szukasz w wynikach. To sprawi, że wzrost będzie znacznie korzystniejszy.

Oto przykład (używane różne nazwy tabel):

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

Umożliwia to dodanie większej liczby typów spraw, dodanie większej liczby wpisów do dziennika i zapewnia lepszy projekt.

To tylko przykład do celów edukacyjnych.

Henoch
źródło
Przeprojektowanie bazy danych może nie być opcją, w zależności od sytuacji użytkownika. Dostępne są inne opcje, które nie wymagają restrukturyzacji danych.
DWRoelands,
@DWRoelands Zgadzam się, że może to nie być opcja, a może powinienem był to wyjaśnić. Po prostu odpowiadałem na podstawie innych komentarzy, że przeprojektowanie, jeśli to możliwe , byłoby lepszym rozwiązaniem i stanowiło przykład. I jestem w pełni świadomy, że istnieje wiele powodów, dla których baza danych nie mogłaby zostać przeprojektowana.
Enoch,