Ottieni le ultime date da più colonne


18

Sembra che dovrebbe essere facile. Come posso ottenere le ultime date che si trovano in colonne diverse

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 

Vorrei che il risultato fosse:

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

Risposte:


20

Usa CASEun'espressione:

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;

dimostrazione

Si noti che alcuni database, come MySQL, SQL Server e SQLite, supportano una funzione scalare massima. SQL Server no, quindi possiamo usare CASEun'espressione come soluzione alternativa.

Modificare:

Sembra che nella tua tabella effettiva, una o più delle tre colonne della data possano avere NULLvalori. Possiamo adattare la query sopra come segue:

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;

dimostrazione


non funziona ottiene la data3 solo non ottiene l'ultima data nelle 3 colonne
Ahmed Alkhteeb

1
@AhmedAlkhteeb Ho modificato la mia risposta per gestire anche il caso in cui una o più colonne di date potrebbero essere NULL.
Tim Biegeleisen,

3
Quindi molte delle risposte fornite qui si romperanno e non funzioneranno. Onestamente, se hai bisogno di fare questo confronto anche su quattro colonne, potresti voler ripensare il design della tabella del database e ottenere invece ogni valore di data su una riga separata . Il tuo requisito sarebbe banale se avessi ciascuna data su una riga separata, perché allora potremmo semplicemente prendere l' MAXuso GROUP BY. Quindi la mia risposta alla tua domanda è "non risolverà", perché penso che forse il design del tuo database debba cambiare.
Tim Biegeleisen,

1
Tim è proprio qui, @AhmedAlkhteeb se hai colonne con decine di date, probabilmente hai dati denormalizzati. Una coppia in una sola fila va bene, ciò significa cose diverse (diciamo un inizio e una fine e una data di nascita e una data in cui una persona è stata aggiunta al sistema), ma molte date (10 di loro) suggeriscono che sei aggiungere una nuova data in una colonna ogni volta che qualcosa cambia; non inserire una nuova riga per mantenere una cronologia. Se fosse un database di una società di servizi di consegna, ad esempio, non avrebbe una colonna di data per ogni possibile passaggio del viaggio; inseriresti una nuova riga per ognuna.
Larnu,

1
@AhmedAlkhteeb in quel caso Larnu è corretto - dovresti avere una tabella con un'azione ( call_case) e un timestamp. Non una sola tabella con 50 colonne
Dannnno,

13

La risposta attualmente accettata è la risposta migliore, ma non penso che faccia un buon lavoro per spiegare il perché. Le altre risposte sembrano certamente molto più pulite a colpo d'occhio (chi vuole scrivere quella brutta dichiarazione del caso), ma probabilmente peggioreranno molto quando inizi a operare su larga scala.

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

Ecco come ho impostato tutto

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;

Sul mio sistema questo mi dà 12.872.738 righe nella tabella. Se provo ciascuna delle query precedenti (ottimizzata in SELECT INTOmodo da non dover aspettare che finisca di stampare i risultati in SSMS), ottengo i seguenti risultati:

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 guardi i piani di query, diventa abbastanza ovvio il motivo: aggiungendo qualsiasi tipo di non-perno o aggregato (o paradiso proibito STRING_SPLIT) finirai con tutti i tipi di operatori aggiuntivi che non ti servono (e costringe il piano a andare in parallelo, portando via risorse che potrebbero desiderare altre query). Per contratto, la CASEsoluzione basata non è parallela, funziona molto rapidamente ed è incredibilmente semplice.

In questo caso, a meno che tu non abbia risorse illimitate (no), dovresti scegliere l'approccio più semplice e veloce.


C'è stata una domanda su cosa fare se è necessario continuare ad aggiungere nuove colonne ed espandere l'istruzione case. Sì, questo diventa ingombrante, ma anche ogni altra soluzione. Se si tratta in realtà di un flusso di lavoro plausibile, è necessario riprogettare la tabella. Quello che vuoi probabilmente assomiglia a questo:

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;

Questo non è certamente privo di potenziali problemi di prestazioni e richiederà un'attenta regolazione dell'indice, ma è il modo migliore per gestire un numero arbitrario di potenziali timestamp


Nel caso in cui le risposte vengano eliminate, ecco le versioni che stavo confrontando (in ordine)

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

Questo è un ottimo lavoro investigativo +1, e sono sorpreso che abbia evitato di attirare voti positivi.
Tim Biegeleisen

è una risposta molto utile +1
Ahmed Alkhteeb,

11

Prova questo:

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. . . Questa è la risposta migliore Gestisce NULLs, dovrebbe avere buone prestazioni e si generalizza facilmente su più colonne.
Gordon Linoff,

MAX () in VALUES () e GROUP BY non sono necessari e rendono la query più lenta; meglio usare SELECT i.call_case, (SELECT MAX (d.date) FROM (VALUES ((i.date1)), ((i.date2)), ((i.date3))) AS d (date)) AS max_date DA #Indebtedness AS i
Thomas Franz il

8

SQL FIDDLE

Uso MAX()

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

Uso 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
Non è un indizio sui voti negativi, secondo me il tuo esempio usando MAX è molto più elegante della soluzione accettata (che diventerà molto ingombrante se ci fosse un numero maggiore di colonne di date).
BarneyL

1
Sono d'accordo, con più valori il metodo che utilizza VALUESè molto più scalabile di una grande CASEespressione. Anch'io vorrei sapere perché è stato sottoposto a downgrade, poiché l'elettore sembra credere che ci sia un problema con l'SQL, e quindi se ci dicono quel problema tutti possiamo imparare da esso.
Larnu,

1

A mio avviso, Pivot è l'opzione migliore ed efficiente per questa query. Copia e incolla nel SERVER MS SQL. Si prega di controllare il codice scritto di seguito:

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

Questo dovrebbe davvero essere rivalutato a livello di progettazione, come altri hanno indicato. Di seguito è riportato un esempio di un design diverso che utilizza due tabelle per realizzare meglio ciò che sembra che si sta cercando nei risultati. Ciò renderà la crescita molto più favorevole.

Ecco un esempio (vengono usati diversi nomi di tabella):

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

Ciò consente l'aggiunta di più tipi di casi, l'aggiunta di molte più voci di registro e una progettazione migliore.

Questo è solo un esempio a fini di apprendimento.


La riprogettazione del database potrebbe non essere un'opzione, a seconda della situazione dell'utente. Sono disponibili altre opzioni che non richiedono la ristrutturazione dei dati.
DWRoelands

@DWRoelands Concordo sul fatto che potrebbe non essere un'opzione, e forse avrei dovuto renderlo più chiaro. Stavo solo rispondendo sulla base di altri commenti che una riprogettazione, se possibile , sarebbe stata la soluzione migliore e fornendo un esempio. E sono ben consapevole che ci sono molte ragioni per cui un database non potrebbe essere riprogettato.
Enoch,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.