Nhận ngày cuối cùng từ nhiều cột


18

Điều này cảm thấy như nó nên là một dễ dàng. Làm cách nào để có được ngày mới nhất trong các cột khác nhau

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 

Tôi muốn kết quả là:

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

Câu trả lời:


20

Sử dụng một CASEbiểu thức:

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;

Bản giới thiệu

Lưu ý rằng một số cơ sở dữ liệu, chẳng hạn như MySQL, SQL Server và SQLite, hỗ trợ một hàm lớn nhất vô hướng. SQL Server thì không, vì vậy chúng ta có thể sử dụng một CASEbiểu thức như một cách giải quyết.

Biên tập:

Dường như trong bảng thực tế của bạn, một hoặc nhiều trong ba cột ngày có thể có NULLcác giá trị. Chúng ta có thể điều chỉnh truy vấn trên như sau:

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;

Bản giới thiệu


nó không hoạt động, nó nhận được ngày 3 chỉ không nhận được ngày cuối cùng trong 3 cột
Ahmed Alkhteeb

1
@AhmedAlkhteeb Tôi đã chỉnh sửa câu trả lời của mình để xử lý trường hợp có thể có một hoặc nhiều cột ngày NULL.
Tim Biegeleisen

3
Sau đó, nhiều câu trả lời được đưa ra ở đây sẽ bị phá vỡ, và sẽ không hoạt động. Thành thật mà nói, nếu bạn cần thực hiện so sánh này qua bốn cột, bạn có thể muốn suy nghĩ lại về thiết kế bảng cơ sở dữ liệu của mình và thay vào đó, hãy lấy từng giá trị ngày trên một hàng riêng biệt . Yêu cầu của bạn sẽ không đáng kể nếu bạn có mỗi ngày trên một hàng riêng biệt, bởi vì sau đó chúng tôi chỉ có thể MAXsử dụng GROUP BY. Vì vậy, câu trả lời của tôi cho câu hỏi của bạn là "sẽ không sửa chữa", vì tôi nghĩ có lẽ thiết kế cơ sở dữ liệu của bạn cần thay đổi.
Tim Biegeleisen

1
Tim ở ngay đây, @AhmedAlkhteeb nếu bạn có 10 cột ngày, bạn có thể có dữ liệu không chuẩn. Một cặp trong một hàng là ổn, điều đó có nghĩa là những điều khác nhau (Giả sử bắt đầu và kết thúc, và Ngày sinh và một ngày mà một người được thêm vào hệ thống), nhưng nhiều ngày (10 trong số đó) cho thấy bạn thêm một ngày mới vào một cột mỗi khi có gì đó thay đổi; không chèn một hàng mới để duy trì một lịch sử. Ví dụ, nếu đó là cơ sở dữ liệu của một dịch vụ giao hàng, nó sẽ không có cột ngày cho mỗi bước có thể của hành trình; bạn sẽ chèn một hàng mới cho mỗi cái.
Larnu

1
@AhmedAlkhteeb trong trường hợp đó, Larnu là chính xác - bạn nên có một bảng có hành động ( call_case) và dấu thời gian. Không phải là một bảng duy nhất với 50 cột
Dannnno

13

Các câu trả lời chấp nhận hiện nay là câu trả lời tốt nhất, nhưng tôi không nghĩ rằng nó làm một công việc tốt, đủ để giải thích tại sao. Các câu trả lời khác chắc chắn trông gọn gàng hơn trong nháy mắt (ai muốn viết tuyên bố trường hợp xấu xí đó), nhưng có khả năng tồi tệ hơn nhiều khi bạn bắt đầu hoạt động ở quy mô.

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

Đây là cách tôi thiết lập mọi thứ

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;

Trên hệ thống của tôi, điều này giúp tôi có 12.872.738 hàng trong bảng. Nếu tôi thử từng truy vấn ở trên (được điều chỉnh để SELECT INTOtôi không cần đợi nó hoàn thành việc in kết quả trong SSMS), tôi sẽ nhận được kết quả sau:

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%

Nếu bạn nhìn vào các kế hoạch truy vấn, điều đó trở nên khá rõ ràng tại sao - thêm bất kỳ loại không liên kết hoặc tổng hợp nào (hoặc trời cấm STRING_SPLIT) bạn sẽ kết thúc với tất cả các loại toán tử bổ sung mà bạn không cần (và nó buộc kế hoạch phải đi song song, lấy đi các tài nguyên mà các truy vấn khác có thể muốn). Theo hợp đồng, CASEgiải pháp dựa trên không đi song song, chạy rất nhanh và cực kỳ đơn giản.

Trong trường hợp này, trừ khi bạn có tài nguyên không giới hạn (bạn không), bạn nên chọn cách tiếp cận đơn giản và nhanh nhất.


Có một câu hỏi phải làm gì nếu bạn cần tiếp tục thêm các cột mới và mở rộng báo cáo trường hợp. Vâng, điều này trở nên khó sử dụng, nhưng mọi giải pháp khác cũng vậy. Nếu đây thực sự là một quy trình công việc hợp lý, thì bạn nên thiết kế lại bảng của mình. Những gì bạn muốn có thể trông giống như thế này:

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;

Điều này chắc chắn không có vấn đề về hiệu suất tiềm năng và sẽ yêu cầu điều chỉnh chỉ số cẩn thận, nhưng là cách tốt nhất để xử lý số lượng dấu thời gian tiềm năng tùy ý


Trong trường hợp bất kỳ câu trả lời nào bị xóa, đây là các phiên bản tôi đang so sánh (theo thứ tự)

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

Đây là công việc thám tử tuyệt vời +1, và tôi ngạc nhiên vì nó đã tránh thu hút bất kỳ sự ủng hộ nào.
Tim Biegeleisen

đó là câu trả lời rất hữu ích +1
Ahmed Alkhteeb

11

Thử cái này:

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. . . Đây là câu trả lời tốt nhất. Nó xử lý NULLs, nên có hiệu suất tốt và dễ dàng khái quát hóa cho nhiều cột hơn.
Gordon Linoff

MAX () trong VALUES () và GROUP BY là không cần thiết và làm cho truy vấn chậm hơn; tốt hơn là chỉ sử dụng SELECT i.call_case, (SELECT MAX (d.date) TỪ (VALUES ((i.date1)), ((i.date2)), ((i.date3))) AS d (ngày)) AS max_date TỪ # Entebtedness NHƯ i
Thomas Franz

8

FIDDLE SQL

Sử dụng MAX()

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

Sử dụng 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
Không phải là một đầu mối về số phiếu giảm, theo tôi, ví dụ của bạn sử dụng MAX thanh lịch hơn nhiều so với giải pháp được chấp nhận (sẽ rất cồng kềnh nếu có số lượng cột ngày lớn hơn).
BarneyL

1
Tôi đồng ý, với nhiều giá trị hơn, phương thức sử dụng VALUEScó khả năng mở rộng hơn nhiều so với CASEbiểu thức lớn . Tôi cũng muốn tìm hiểu lý do tại sao nó bị hạ cấp, vì cử tri dường như tin rằng có vấn đề với SQL, và do đó nếu họ nói với chúng tôi rằng vấn đề đó tất cả chúng ta có thể học hỏi từ nó.
Larnu

1

Theo quan điểm của tôi, Pivot là tùy chọn tốt nhất và hiệu quả cho truy vấn này. Sao chép và Dán trong MS SQL SERVER. Vui lòng kiểm tra mã được viết dưới đây:

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

Điều này thực sự cần được đánh giá lại ở mức thiết kế như những người khác đã chỉ ra. Dưới đây là một ví dụ về một thiết kế khác nhau sử dụng hai bảng để hoàn thành tốt hơn những gì nó xuất hiện trong kết quả của bạn. Điều này sẽ làm cho tăng trưởng thuận lợi hơn nhiều.

Dưới đây là một ví dụ (tên bảng khác nhau được sử dụng):

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

Điều này cho phép thêm nhiều loại trường hợp được thêm vào, nhiều mục nhật ký được thêm vào và cung cấp một thiết kế tốt hơn.

Đây chỉ là một ví dụ cho mục đích học tập.


Thiết kế lại cơ sở dữ liệu có thể không phải là một tùy chọn, tùy thuộc vào tình huống của người dùng. Có các tùy chọn khác có sẵn mà không yêu cầu cơ cấu lại dữ liệu.
DWRoelands

@DWRoelands Tôi đồng ý rằng nó có thể không phải là một lựa chọn và có lẽ tôi nên làm rõ hơn điều này. Tôi chỉ trả lời dựa trên các ý kiến ​​khác rằng thiết kế lại, nếu có thể , sẽ là giải pháp tốt hơn và cung cấp một ví dụ. Và tôi nhận thức rõ rằng có nhiều lý do cơ sở dữ liệu sẽ không thể được thiết kế lại.
Enoch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.