รับวันที่ล่าสุดจากหลายคอลัมน์


18

รู้สึกเช่นนี้ควรเป็นเรื่องง่าย ฉันจะได้รับวันที่ล่าสุดที่อยู่ในคอลัมน์ต่าง ๆ ได้อย่างไร

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 

ฉันต้องการผลลัพธ์ที่จะเป็น:

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

คำตอบ:


20

ใช้CASEนิพจน์:

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;

การสาธิต

โปรดทราบว่าฐานข้อมูลบางอย่างเช่น MySQL, SQL Server และ SQLite สนับสนุนฟังก์ชันที่ยิ่งใหญ่ที่สุดแบบสเกลาร์ SQL Server ไม่ดังนั้นเราสามารถใช้CASEนิพจน์เป็นวิธีแก้ปัญหา

แก้ไข:

ดูเหมือนว่าในตารางจริงของคุณคอลัมน์วันที่อย่างน้อยหนึ่งคอลัมน์อาจมีNULLค่าได้ เราสามารถปรับคิวรีด้านบนดังนี้:

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;

การสาธิต


มันไม่ทำงานมันได้รับ date3 เท่านั้นไม่ได้รับวันสุดท้ายใน 3 คอลัมน์
Ahmed Alkhteeb

1
@AhmedAlkhteeb NULLฉันแก้ไขคำตอบของฉันยังจัดการกรณีที่หนึ่งหรือมากกว่าหนึ่งคอลัมน์วันที่อาจจะ
ทิม Biegeleisen

3
จากนั้นคำตอบมากมายที่ให้ไว้ที่นี่จะแตกและจะไม่ทำงาน สุจริตถ้าคุณต้องการที่จะทำการเปรียบเทียบนี้ข้ามสี่คอลัมน์คุณอาจต้องการที่จะคิดใหม่ออกแบบตารางฐานข้อมูลของคุณและแทนที่จะได้รับค่าแต่ละวันบนแยกแถว ความต้องการของคุณจะเป็นที่น่ารำคาญถ้าคุณมีวันที่แถวแยกต่างหากแต่ละแล้วเพราะเราก็สามารถใช้การใช้MAX GROUP BYดังนั้นคำตอบสำหรับคำถามของคุณคือ "จะไม่แก้ไข" เพราะฉันคิดว่าบางทีการออกแบบฐานข้อมูลของคุณต้องเปลี่ยน
ทิม Biegeleisen

1
Tim อยู่ตรงนี้ @AhmedAlkhteeb ถ้าคุณมีคอลัมน์วันที่ 10 แห่งคุณน่าจะมีข้อมูลที่ผิดปกติ คู่ในแถวเดียวก็โอเคนั่นหมายถึงสิ่งต่าง ๆ (สมมติว่าเริ่มต้นและสิ้นสุดและวันเดือนปีเกิดและวันที่ที่บุคคลถูกเพิ่มในระบบ) แต่วันที่หลายคน (10 คนในนั้น) แสดงให้เห็นว่าคุณ การเพิ่มวันที่ใหม่ลงในคอลัมน์ทุกครั้งที่มีการเปลี่ยนแปลง ไม่แทรกแถวใหม่เพื่อรักษาประวัติ ตัวอย่างเช่นหากเป็นฐานข้อมูลของ บริษัท ผู้ให้บริการจัดส่งจะไม่มีคอลัมน์วันที่สำหรับทุกขั้นตอนที่เป็นไปได้ของการเดินทาง คุณต้องการแทรกแถวใหม่สำหรับแต่ละแถว
Larnu

1
@AhmedAlkhteeb ในกรณีนั้น Larnu ถูกต้อง - คุณควรมีตารางที่มีแอคชั่น ( call_case) และการประทับเวลา ไม่ใช่ตารางเดียวที่มี 50 คอลัมน์
Dannnno

13

คำตอบที่ได้รับการยอมรับในขณะนี้เป็นคำตอบที่ดีที่สุด แต่ผมไม่คิดว่ามันไม่ได้งานที่ดีมากพอที่จะอธิบายว่าทำไม คำตอบอื่น ๆ นั้นดูสะอาดตากว่ามาก (ผู้ที่ต้องการเขียนคำแถลงกรณีที่น่าเกลียด) แต่มีแนวโน้มที่จะแย่ลงมากเมื่อคุณเริ่มใช้งานเครื่องชั่ง

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

นี่คือวิธีที่ฉันตั้งค่าทุกอย่าง

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;

ในระบบของฉันนี่ทำให้ฉันได้ 12,872,738 แถวในตาราง หากฉันลองใช้ข้อความค้นหาแต่ละข้อด้านบน (ปรับให้เป็นSELECT INTOดังนั้นฉันไม่จำเป็นต้องรอให้พิมพ์ผลลัพธ์ใน SSMS ให้เสร็จ) ฉันจะได้ผลลัพธ์ต่อไปนี้:

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%

หากคุณดูที่แผนแบบสอบถามจะเห็นได้ชัดว่าทำไม - เพิ่มประเภท unpivot หรือรวม (หรือสวรรค์ห้ามSTRING_SPLIT) ชนิดใด ๆคุณจะจบลงด้วยตัวดำเนินการเพิ่มเติมทุกประเภทที่คุณไม่ต้องการ (และบังคับให้แผน ไปแบบขนานโดยไม่ต้องค้นหาทรัพยากรอื่น ๆ อาจต้องการ) โดยการทำสัญญาCASEโซลูชันที่ใช้ไม่ได้ขนานกันทำงานเร็วมากและเรียบง่ายอย่างไม่น่าเชื่อ

ในกรณีนี้หากคุณไม่มีทรัพยากรไม่ จำกัด (คุณไม่มี) คุณควรเลือกวิธีที่ง่ายที่สุดและเร็วที่สุด


มีคำถามว่าจะทำอย่างไรถ้าคุณต้องการเพิ่มคอลัมน์ใหม่และขยายคำสั่งเคส ใช่สิ่งนี้ไม่ได้ผล แต่ก็มีวิธีแก้ปัญหาอื่น ๆ ถ้านี่เป็นเวิร์กโฟลว์ที่น่าเชื่อถือคุณควรออกแบบตารางของคุณใหม่ สิ่งที่คุณต้องการอาจมีลักษณะเช่นนี้:

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;

นี่ไม่ใช่ปัญหาประสิทธิภาพที่อาจเกิดขึ้นได้อย่างแน่นอนและจะต้องมีการปรับแต่งดัชนีอย่างระมัดระวัง แต่เป็นวิธีที่ดีที่สุดในการจัดการกับจำนวนเวลาที่อาจเกิดขึ้นโดยพลการ


ในกรณีที่คำตอบใด ๆ ถูกลบนี่คือรุ่นที่ฉันเปรียบเทียบ (เรียงตามลำดับ)

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

นี่เป็นงานนักสืบที่ยอดเยี่ยม +1 และฉันก็ประหลาดใจที่มันหลีกเลี่ยงการดึงดูดผู้อัปโหลดใด ๆ
ทิม Biegeleisen

มันเป็นคำตอบที่เป็นประโยชน์มาก +1
Ahmed Alkhteeb

11

ลองสิ่งนี้:

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 . . นี่คือคำตอบที่ดีที่สุด มันจัดการกับNULLs ควรมีประสิทธิภาพที่ดีและง่ายต่อการสรุปคอลัมน์เพิ่มเติม
Gordon Linoff

MAX () ใน VALUES () และ GROUP BY ไม่จำเป็นและทำให้การสืบค้นช้าลง ดีกว่าเพียงใช้ SELECT i.call_case (SELECT สูงสุด (d.date)) จาก (ค่า ((i.date1)), ((i.date2)), ((i.date3))) AS d (วันที่)) AS max_date จาก #Indebtedness AS i
Thomas Franz

8

SQL FIDDLE

ใช้ MAX()

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

ใช้ 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
ไม่ใช่เบาะแสเกี่ยวกับการลงคะแนนเสียงในความคิดของฉันตัวอย่างของคุณที่ใช้ MAX นั้นดีกว่าวิธีที่ได้รับการยอมรับ (ซึ่งจะยุ่งยากมากถ้ามีคอลัมน์วันที่จำนวนมาก)
BarneyL

1
ฉันเห็นด้วยกับค่าที่มากกว่าวิธีที่ใช้VALUESนั้นสามารถปรับขนาดได้มากกว่าการCASEแสดงออกขนาดใหญ่ ฉันก็ต้องการที่จะเรียนรู้ว่าทำไมมันจึงถูกลดระดับลงเนื่องจากผู้ลงคะแนนดูเหมือนจะเชื่อว่ามีปัญหากับ SQL ดังนั้นถ้าพวกเขาบอกเราว่าปัญหาที่เราทุกคนสามารถเรียนรู้ได้จากมัน
ลาร์นู

1

ในมุมมองของฉัน Pivot เป็นตัวเลือกที่ดีที่สุดและมีประสิทธิภาพสำหรับแบบสอบถามนี้ คัดลอกและวางใน MS SQL SERVER โปรดตรวจสอบรหัสที่เขียนด้านล่าง:

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

สิ่งนี้ควรได้รับการประเมินใหม่ในระดับการออกแบบตามที่คนอื่น ๆ ระบุไว้ ด้านล่างนี้เป็นตัวอย่างของการออกแบบที่แตกต่างกันโดยใช้ตารางสองตารางเพื่อให้บรรลุผลที่ดียิ่งขึ้นตามที่คุณต้องการในผลลัพธ์ของคุณ สิ่งนี้จะทำให้การเจริญเติบโตดีขึ้นมาก

นี่คือตัวอย่าง (ใช้ชื่อตารางอื่น):

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

สิ่งนี้ช่วยให้สามารถเพิ่มประเภทเคสได้มากขึ้นเพิ่มรายการบันทึกจำนวนมากขึ้นและให้การออกแบบที่ดีขึ้น

นี่เป็นเพียงตัวอย่างสำหรับวัตถุประสงค์ในการเรียนรู้


การออกแบบฐานข้อมูลอาจไม่ใช่ตัวเลือกขึ้นอยู่กับสถานการณ์ของผู้ใช้ มีตัวเลือกอื่น ๆ ที่ไม่จำเป็นต้องปรับโครงสร้างข้อมูล
DWRoelands

@ DWRoelands ฉันจะยอมรับว่ามันอาจไม่ใช่ตัวเลือกและบางทีฉันควรทำให้ชัดเจนยิ่งขึ้น ฉันแค่ตอบสนองตามความคิดเห็นอื่น ๆ ที่การออกแบบถ้าเป็นไปได้จะเป็นทางออกที่ดีกว่าและให้ตัวอย่าง และฉันทราบดีว่ามีหลายสาเหตุที่ฐานข้อมูลจะไม่สามารถออกแบบใหม่ได้
เอนอ็อค
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.