2015-12-10 10 views
6

特定のオブジェクトの更新を取得するヒストリテーブルがあり、他の情報に加えて、この更新が行われた時刻が取得されます。私がしたいのは、ActionTakenの列に対応するSELECTMIN(LogDate)です。SQL Serverで日付のグループを選択する

具体的には、履歴テーブルには、他の(より最新の)行ActionTaken = 1を持っているかもしれないが、私は日付ActionTakenをキャプチャしたい例1

となりました:これはうまく機能

SELECT MIN(LogDate) AS FirstActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND FirstActionTaken = 1 

SELECT MIN(LogDate) AS SecondActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND SecondActionTaken = 1 

SELECT MIN(LogDate) AS ThirdActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND ThirdActionTaken = 1 

私は問題なく適切な日付を受け取ります。

SELECT MAX(LogDate) AS LastActionDate 
FROM HistoryTable 
WHERE ID = 123 
    AND LogDate IN 
    (
      ( SELECT MIN(LogDate) AS FirstActionDate 
       FROM HistoryTable 
       WHERE ID = 123 
        AND FirstActionTaken = 1 ), 

      ( SELECT MIN(LogDate) AS SecondActionDate 
       FROM HistoryTable 
       WHERE ID = 123 
        AND SecondActionTaken = 1 ), 

      ( SELECT MIN(LogDate) AS ThirdActionDate 
       FROM HistoryTable 
       WHERE ID = 123 
        AND ThirdActionTaken = 1 ) 
    ) 

はこれも動作しますが、私はそれをこのようにやって嫌い:私はトラブルに実行している場合は、このグループからMAX(LogDate)selectうとしています。私は前のステートメントを変数に保存して、それらからはただSELECT MAX()にすることができました。確かに読みやすくなりますが、JOINの構文はこのクエリのように見えますか?

最初の3つのSELECTステートメントを、3つの日付をすべて返すものに結合する方法はありますか?私はSELECT文を繰り返し、この結果セットからと(一見不要)なし(別の列など)の最新のLogDateをつかむことができますどのように

編集:ここでは

は、私がこれまでに与えられた答えに関連して発見したいくつかのリンクです:

Hoこれらは似たような問題の解決策を探している人たちに役立ちます!

+0

UNIONを使用し、次にIN – JamieD77

答えて

2

EDIT 2

問合せができ、(最新のアクションの日付を定義する方法について)OP自身の答えから収集することができる新たな情報に基づいて、

select coalesce(
     min(case when ThirdActionTaken = 1 then LogDate end), 
     min(case when SecondActionTaken = 1 then LogDate end), 
     min(case when FirstActionTaken = 1 then LogDate end) 
     ) as LastActionDate 
    from HistoryTable 
where id = 123 

アンピボットを使用することもできる:

さらに、単にこのように簡略化すること210
select max(ActionDate) 
    from (select min(case when FirstActionTaken = 1 then LogDate end) as FirstActionDate, 
       min(case when SecondActionTaken = 1 then LogDate end) as SecondActionDate, 
       min(case when ThirdActionTaken = 1 then LogDate end) as ThirdActionDate 
      from HistoryTable 
      where id = 123) t 
unpivot (ActionDate for ActionDates in (FirstActionDate, SecondActionDate, ThirdActionDate)) unpvt 

EDIT:短い説明

この答えは、それは1つのクエリで3つの最小の日付を取得するには、条件付き集約を使用しているゴードンの中に非常によく似ています。

だから、クエリの次の部分:

select min(case when FirstActionTaken = 1 then LogDate end) as FirstActionDate, 
     min(case when SecondActionTaken = 1 then LogDate end) as SecondActionDate, 
     min(case when ThirdActionTaken = 1 then LogDate end) as ThirdActionDate 
    from HistoryTable 
where id = 123 

...何かなどを返すことがあります...

FirstActionDate SecondActionDate ThirdActionDate 
--------------- ---------------- --------------- 
    2015-01-01   2015-12-01   (null) 

その後、unpivot句がどのような "アンピボット" 3列にあります結果は3行で1列に設定されます。

ActionDate 
---------- 
2015-01-01 
2015-12-01 
    (null) 

結果がこの形式になると、単純なmax集合関数(select max(ActionDate))を適用して3行の最大値を得ることができます。

+0

を使用することができます。これはすばらしいことですが、自分の試みよりも素晴らしい感じです:)ここで起こっていることについていくつかの説明を追加してもよろしいですか?特に「アンピボット(UNPIVOT)」に関して。答えをありがとう! – levelonehuman

+0

優秀、回答を更新していただきありがとうございます。 – levelonehuman

1

UNIONを使用して、INステートメントの3つのクエリを結合できます。これは、正規化されたデータ構造を持つ容易になるだろう

SELECT 
    MAX(ht1.LogDate) AS LastActionDate 
FROM 
    HistoryTable ht1 
WHERE 
    ht1.ID = 123 
    AND ht1.LogDate IN (SELECT 
         MIN(LogDate) AS FirstActionDate 
        FROM 
         HistoryTable ht2 
        WHERE 
         ht2.ID = ht1.ID 
         AND ht2.FirstActionTaken = 1 
        UNION 
        SELECT 
         MIN(LogDate) AS FirstActionDate 
        FROM 
         HistoryTable ht2 
        WHERE 
         ht2.ID = ht1.ID 
         AND ht2.SecondActionTaken = 1 
        UNION 
        SELECT 
         MIN(LogDate) AS FirstActionDate 
        FROM 
         HistoryTable ht2 
        WHERE 
         ht2.ID = ht1.ID 
         AND ht2.ThirdActionTaken = 1) 
3

よう

何か。条件付き集計を使用して3つの最小日を計算する1つの方法があります。そして、それは、それらの値の最大値をとります。

SELECT v.dt 
FROM (SELECT MIN(CASE WHEN FirstActionTaken = 1 THEN LogDate END) AS d1, 
      MIN(CASE WHEN SecondActionTaken = 1 THEN LogDate END) AS d2, 
      MIN(CASE WHEN ThirdActionTaken = 1 THEN LogDate END) AS d3  
    FROM HistoryTable 
    WHERE ID = 123 
    ) ht OUTER APPLY 
    (SELECT MAX(dt) as dt 
    FROM (VALUES (d1), (d2), (d3)) v(dt) 
    ) v; 
0

PIVOTを使用せずにこの問題を解決できます。次のコードは、変数にMIN値を格納するために、あなたの最初のコードを拡張して、それらの間の最大値を計算します。

DECLARE @FirstActionDate DATETIME = NULL; 
DECLARE @SecondActionDate DATETIME = NULL; 
DECLARE @ThirdActionDate DATETIME = NULL; 
DECLARE @LastActionDate DATETIME = NULL; 

SELECT @FirstActionDate = MIN(LogDate) 
FROM HistoryTable 
WHERE ID = 123 
    AND FirstActionTaken = 1 

SELECT @SecondActionDate = MIN(LogDate) 
FROM HistoryTable 
WHERE ID = 123 
    AND SecondActionTaken = 1 

SELECT @ThirdActionDate = MIN(LogDate) 
FROM HistoryTable 
WHERE ID = 123 
    AND ThirdActionTaken = 1 

-- calculate @LastActionDate as the greater from @FirstActionDate, @SecondActionDate and @ThirdActionDate. 
SET @LastActionDate = @FirstActionDate; 
IF (@SecondActionDate > @LastActionDate) SET @LastActionDate = @SecondActionDate; 
IF (@ThirdActionDate > @LastActionDate) SET @LastActionDate = @ThirdActionDate; 

SELECT @FirstActionDate AS [FirstActionDate] 
, @SecondActionDate  AS [SecondActionDate] 
, @ThirdActionDate  AS [ThirdActionDate] 
, @LastActionDate  AS [LastActionDate] 

あなたは絶対的な最後のアクションの日付をしたい場合は、あなただけの単一のステートメントに元のコードを変更することができます、次のように最終SELECT文をリファクタリングで

SELECT MAX(LogDate) AS [LastActionDate] 
, MIN(CASE WHEN FirstActionTaken = 1 THEN LogDate ELSE NULL END) AS [FirstActionDate] 
, MIN(CASE WHEN SecondActionTaken = 1 THEN LogDate ELSE NULL END) AS [SecondActionDate] 
, MIN(CASE WHEN ThirdActionTaken = 1 THEN LogDate ELSE NULL END) AS [ThirdActionDate] 
FROM HistoryTable 
WHERE ID = 123 
0

私自身の試み:背面012に

SELECT MIN(ht2.LogDate) AS FirstActionDate, 
     MIN(ht3.LogDate) AS SecondActionDate, 
     MIN(ht4.LogDate) AS ThirdActionDate, 
     COALESCE (
      MIN(ht4.LogDate), 
      MIN(ht3.LogDate), 
      MIN(ht2.LogDate) 
     ) AS LastActionDate 
FROM HistoryTable ht 
    INNER JOIN HistoryTable ht2 
     ON ht2.ID = ht.ID AND ht2.FirstActionTaken = 1 
    INNER JOIN HistoryTable ht3 
     ON ht3.ID = ht.ID AND ht3.SecondActionTaken = 1 
    INNER JOIN HistoryTable ht4 
     ON ht4.ID = ht.ID AND ht4.ThirdActionTaken = 1 
WHERE ht.ID = 123 
GROUP BY ht.ID 

このJOINSを各xActionTaken列についてはであり、の場合にはSELECTSである。その後、結果(ThirdActionSecondActionFirstAction)を後方に移動し、最初に見つかったものをLastActionTakenとして返します。

確かにこれはちょっと面倒ですが、同じデータを取得するための別の方法を提示するとよいでしょう。パフォーマンスのために注目に値する。また

UNPIVOTOUTER APPLY方法に対する私の答えを実行した後

SSMS Execution PlanUNPIVOTOUTER APPLYが約取って(ほぼ同じであることを示しています。実行時間はそれぞれ50%)。

私の方法をこれらのいずれかと比較すると、私の方法は約1時間かかる。実行時間の88%UNPIVOT/OUTER APPLYのみが12% - UNPIVOTOUTER APPLYの両方が(少なくともこの場合は)より高速に実行されます。

私の方法がそれほど時間がかかる理由は、は、4のスキャンのために、それに戻って参加するたびにHistoryTableのテーブルスキャンを実行するということです。他の2つの方法では、このアクションは1回だけ実行されます。

+0

あなたの質問は、 'ThirdActionDate> SecondActionDate> FirstActionDate'を一度も指定していませんが、あなたの答えはこれが常に真であることを意味します。その場合、ピボット解除、外側の適用、またはジョインは必要ありません。照会には、合体を伴う条件付き集計のみが必要です。私は私の答えを編集しました。それはあなたに最高のパフォーマンスを与えるはずです。 – sstan