複数の列から最後の日付を取得する


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、回避策として式を使用できます。

編集:

実際のテーブルでは、3つの日付列の1つ以上に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 1つ以上の日付列が含まれる可能性がある場合にも対応できるように、回答を編集しましたNULL
Tim Biegeleisen、

3
そうすると、ここで与えられた答えの多くは壊れてしまい、うまくいきません。正直なところ、4つの列でもこの比較を行う必要がある場合は、データベーステーブルの設計を見直し、代わりに各日付の値を個別のに取得することをお勧めします。それぞれの日付が別々の行にある場合、要件は簡単になります。なぜなら、MAX使用するだけだからGROUP BYです。ですから、あなたの質問に対する私の答えは「修正されません」です。おそらく、データベース設計を変更する必要があると思います。
Tim 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 INTOSSMSで結果の印刷が完了するのを待つ必要がないように調整されています)、次の結果が得られます。

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%

クエリプランを見ると、明らかにその理由が明らかになります。アンピボットまたは集計(または天国の禁止STRING_SPLIT)の種類を追加すると、不要なあらゆる種類の演算子が追加されてしまいます(これにより、プランは強制的に実行されます。他のクエリが必要とする可能性のあるリソースを取り除いて、並列化します)。契約上、CASEベースのソリューションは並列処理されず、非常に高速で実行され、信じられないほど単純です。

この場合、無制限のリソースがない場合(そうでない場合)は、最も単純で最速のアプローチを選択する必要があります。


新しい列を追加し続け、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であり、賛成票を集めることを回避していることに驚いています。
Tim Biegeleisen、

非常に役立つ回答です。+ 1
アーメドアルフティーブ

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

VALUES()内のMAX()およびGROUP BYは不要であり、クエリが遅くなります。SELECT i.call_case、(SELECT MAX(d.date)FROM(VALUES((i.date1))、((i.date2))、((i.date3)))AS d(date))ASを使用することをお勧めしますmax_date FROM #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に問題があると信じているように見えるため、私もそれがなぜ反対票だったのかを知りたいと思っています。
Larnu

1

私の考えでは、ピボットはこのクエリに最適で効率的なオプションです。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

他の人が指摘しているように、これは設計レベルで本当に再評価されるべきです。以下は、結果で探しているように見えるものをよりよく達成するために2つのテーブルを使用する異なる設計の例です。これにより、成長がはるかに有利になります。

次に例を示します(使用される異なるテーブル名)。

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

@DWRoelandsこれはオプションではないかもしれないと私は同意します。おそらくこれをもっと明確にすべきだったでしょう。私は、可能であれば再設計がより良い解決策であり、例を提供するという他のコメントに基づいて応答していました。また、データベースを再設計できない多くの理由があることもよく承知しています。
Enoch
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.