Birden çok sütundan son tarihleri ​​alma


18

Bu kolay bir şey gibi geliyor. Farklı sütunlardaki en son tarihleri ​​nasıl edinebilirim?

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 

Sonucun olmasını istiyorum:

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

Yanıtlar:


20

CASEİfade kullanı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;

gösteri

MySQL, SQL Server ve SQLite gibi bazı veritabanlarının en büyük skaler fonksiyonu desteklediğini unutmayın. SQL Server kullanmaz, bu nedenle bir CASEifadeyi geçici çözüm olarak kullanabiliriz .

Düzenle:

Gerçek tablonuzda, üç tarih sütunundan bir veya daha fazlasının NULLdeğerleri olabileceği anlaşılıyor . Yukarıdaki sorguyu aşağıdaki gibi uyarlayabiliriz:

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;

gösteri


çalışmıyor 3 tarihini sadece 3 sütunda son tarihi
almıyorum

1
@AhmedAlkhteeb Bir ya da daha fazla tarih sütununun olabileceği durumu ele almak için cevabımı düzenledim NULL.
Tim Biegeleisen

3
O zaman burada verilen cevapların çoğu kırılır ve işe yaramaz. Dürüst olmak gerekirse, bu karşılaştırmayı dört sütunda bile yapmanız gerekiyorsa, veritabanı tablosu tasarımınızı yeniden düşünmek ve bunun yerine her tarih değerini ayrı bir satıra almak isteyebilirsiniz . Her bir tarih ayrı bir satırda olsaydı, gereksiniminiz önemsiz olurdu, çünkü o zaman sadece MAXkullanmaya başlayabiliriz GROUP BY. Bu yüzden sorunuza cevabım "düzeltmeyecek" çünkü veritabanı tasarımınızın değişmesi gerektiğini düşünüyorum.
Tim Biegeleisen

1
Tim tam burada, @AhmedAlkhteeb 10'luk tarih sütununuz varsa, büyük olasılıkla denormalize edilmiş verileriniz var. Tek bir satırdaki bir çift iyidir, bu farklı şeyler anlamına gelir (Diyelim ki bir Başlangıç ​​ve Bitiş, Doğum Tarihi ve bir kişinin sisteme eklendiği bir tarih), ancak birçok tarih (10'ları) sizin her şey değiştiğinde sütuna yeni bir tarih eklemek; bir geçmişi korumak için yeni bir satır eklememek. Örneğin, bir dağıtım hizmeti şirketinin veritabanı olsaydı, yolculuğun olası her adımı için bir tarih sütunu olmazdı; her biri için yeni bir satır eklersiniz.
Larnu

1
@AhmedAlkhteeb bu durumda Larnu doğrudur - bir eylem ( call_case) ve zaman damgası içeren bir tablonuz olmalıdır . 50 sütunlu tek bir tablo değil
Dannnno

13

Şu anda kabul edilen cevap en iyi cevaptır, ancak bunun nedenini açıklamak için yeterince iyi bir iş yaptığını düşünmüyorum. Diğer cevaplar kesinlikle bir bakışta daha temiz görünüyor (bu çirkin vaka ifadesini yazmak isteyen), ancak ölçekli çalışmaya başladığınızda çok daha kötü olacak gibi görünüyor.

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

İşte her şeyi nasıl ayarladım

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;

Sistemimde bu bana tabloda 12.872.738 satır alıyor. Yukarıdaki sorguların her birini denerseniz ( SELECT INTOböylece SSMS sonuçları yazdırmayı bitirmek için beklemek gerekmez için tweaked ), aşağıdaki sonuçları elde:

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%

Sorgu planlarına bakarsanız, neden oldukça açık hale gelir - herhangi bir unpivot veya agrega (veya cennet yasak STRING_SPLIT) ekleyerek ihtiyacınız olmayan her türlü ek işleçle karşılaşırsınız (ve planı diğer sorguların isteyebileceği kaynakları alıp paralellik gösterir). Sözleşmeyle, CASEtemel çözüm paralel gitmez, çok hızlı çalışır ve inanılmaz derecede basittir.

Bu durumda, sınırsız kaynağınız yoksa (yoksa), en basit ve en hızlı yaklaşımı seçmelisiniz.


Yeni sütunlar eklemeye ve vaka ifadesini genişletmeye devam etmeniz gerekiyorsa ne yapılacağı sorusu vardı. Evet, bu zorlaşıyor, ama diğer tüm çözümler de öyle. Bu aslında makul bir iş akışı ise, tablonuzu yeniden tasarlamanız gerekir. İstediğiniz şey muhtemelen şöyle görünür:

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;

Bu kesinlikle potansiyel performans sorunlarından bağımsız değildir ve dikkatli bir indeks ayarlaması gerektirir, ancak keyfi sayıda potansiyel zaman damgasıyla başa çıkmanın en iyi yoludur.


Herhangi bir yanıtın silinmesi durumunda, karşılaştırdığım sürümler (sırayla)

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

Bu harika bir dedektif çalışması +1 ve herhangi bir upvotes çekmekten kaçındığı için şaşırdım.
Tim Biegeleisen

çok yararlı cevap +1
Ahmed Alkhteeb

11

Bunu dene:

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. . . Bu en iyi cevap. Bu NULLs işler , iyi bir performans olmalı ve daha fazla sütun için kolayca genelleme.
Gordon Linoff

VALUES () ve GROUP BY içindeki MAX () gerekli değildir ve sorguyu yavaşlatır; daha iyisi SELECT i.call_case, (SELECT MAX (d.date) FROM (VALUES ((i.date1)), ((i.date2)), ((i.date3))) AS d (tarih)) AS max_date #Indebtedness AS i
Thomas Franz

8

SQL FIDDLE

kullanım MAX()

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

kullanım 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
Aşağı oylar hakkında bir ipucu değil, bence MAX kullanan örneğiniz kabul edilen çözümden çok daha zariftir (daha fazla sayıda tarih sütunu varsa çok hantallaşacaktır).
BarneyL

1
Katılıyorum, daha fazla değer kullanarak yöntem VALUESbüyük bir CASEifadeden çok daha ölçeklenebilir . Ben de seçmen SQL ile ilgili bir sorun olduğuna inanıyor gibi göründüğü için neden indirildiğini öğrenmek istiyorum ve bu nedenle bize bu sorunu söylersek hepimiz ondan öğrenebiliriz.
Larnu

1

Benim görüşüme göre, Pivot bu sorgu için en iyi ve etkili seçenektir. Kopyalayın ve MS SQL SUNUCUSU yapıştırın. Lütfen aşağıda yazılı kodu kontrol edin:

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

Bu, diğerlerinin de belirttiği gibi, tasarım düzeyinde yeniden değerlendirilmelidir. Aşağıda, sonuçlarınızda aradığınız şeyi daha iyi gerçekleştirmek için iki tablo kullanan farklı bir tasarım örneği verilmiştir. Bu büyümeyi çok daha elverişli hale getirecektir.

İşte bir örnek (kullanılan farklı tablo adları):

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

Bu, daha fazla vaka tipinin eklenmesine, daha birçok günlük girişinin eklenmesine izin verir ve daha iyi bir tasarım sağlar.

Bu sadece öğrenme amaçlı bir örnektir.


Kullanıcının durumuna bağlı olarak veritabanını yeniden tasarlamak bir seçenek olmayabilir. Verilerin yeniden yapılandırılmasını gerektirmeyen başka seçenekler de vardır.
DWRoelands

@DWRoelands Bunun bir seçenek olmayabileceğini kabul ediyorum ve belki de bunu daha açık hale getirmeliydim. Mümkünse yeniden tasarımın daha iyi bir çözüm olacağı ve bir örnek sunacağı diğer yorumlara dayanarak yanıt veriyordum . Ve bir veritabanının yeniden tasarlanamamasının birçok nedeni olduğunun farkındayım.
Enoch
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.