2017-01-03 4 views
2

最近、私は過去に複数のステップを踏まなければ解決できなかった興味深いSQLの問題が現れました。特定の期間内に特定のステータスのアイテムの数を取得するクエリ

シンプルな「ItemStatus」テーブルがあるとします。これは、さまざまなアイテムのステータスを追跡するために存在します。列は「itemId」、「date status changed」、および「status」です。例:

ItemId  DateStatusChanged Status 
1   09/01/2016    New 
1   10/15/2016    Complete 
2   10/20/2016    New 
2   10/25/2016    Complete 

アイテムは、変更されるまで同じ状態であるとみなされます。したがって、アイテム1は9/1を開始する「新規」であり、「完了」に変更された10/15まで「新規」になりました。アイテム2は10/20から10/25までの「新規」でした。

2016年10月(この場合は「2」)にいつでも「新」ステータスを持つアイテムの数を取得するには、テーブルをクエリします。この結果を返すことができる単一のSQLクエリはありますか?

答えて

1

タイムラインに関連する一般的なSQLの問題の1つです。はい、解決策があります。レコードにも終了日がある場合は、より簡単で効率的なクエリを書くことができますが、もちろん1つのレコードだけを見るときにはあまり推測していないことになりますが、無効なシーケンスの問題(重複する状態またはタイムラインのギャップ)。だから... ...

select count(distinct ItemId) 
    from ItemStatus is1 
where status = 'New' 
    and DateStatusChanged < '2016-11-01' -- syntax may vary 
    and not exists 
     (select 1 
      from itemstatus is2 
     where is2.itemid = is1.itemid 
      and is2.status != 'New' 
      and is2.datestatuschanged > is1.datestatuschanged 
      and is2.datestatuschanged < '2016-10-01') 

あなたは正確に所望の論理、タイムスタンプの細かさなどに応じて、いくつかの<など、<=対を調整する必要があるかもしれ

あなたは、各行が持たせるために、条件付き集約を使用することができます
+0

ああ、はい、完璧な - それは私が擬似-SQLで考えていたものの線に沿ってのですが、かなり変換できませんでした。問題の項目の "新しい"状態変更レコードが存在するかどうかをチェックしたいので、status = 'New'のdatestatuschangedのためにある種の "プレースホルダ"変数が必要であることを知っていました*その日、10月1日以前。ありがとうございました! – Cmaso

+0

私は最終行がタイプミスであったと思いますが、そのクエリを実行すると1のカウントが返されます。2のカウントを取得するには(2016年10月中のいずれかの時点で両方の項目が「新規」でした)、クエリの最後の行は「and is2.datestatuschanged <'2016-10-01'」となります。 – Cmaso

+0

Ayup。修正しました。 –

0

新規および完全日付。そこからクエリは実際にはかなり簡単です。

このようなことは、正しい方向を指しているはずです。

select count(distinct ItemID) as ItemCount 
from 
(
    select ItemID 
     , max(case when Status = 'New' then DateStatusChanged end) as NewDate 
     , Max(case when Status = 'Complete' then DateStatusChanged end) as CompleteDate 
    from YourTable 
    group by ItemID 
) MyItems 
where NewDate >= '2016-10-01' 
    and CompleteDate >= '2016-10-01' 
    and CompleteDate >= NewDate --just to ensure that is wasn't marked complete before it was marked new 
1

あなたはそれぞれの次のステータス変更日はdatestatuschangedの昇順に基づいてITEMID取得するleadを使用することができます。次に、次の変更または既存の変更が指定された日付の間にあるかどうかを確認し、それらの項目を数えます。

select count(distinct ItemID) 
from (select i.* 
    ,lead(datestatuschanged) over(partition by itemid order by datestatuschanged) as next_change 
     from itemstatus i 
    ) x 
where status = 'New' 
and ((next_change >= '2016-10-01' and next_change <= '2016-10-31') 
     or 
     (datestatuschanged >= '2016-10-01' and datestatuschanged <= '2016-10-31') 
    ) 
+0

ウィンドウ関数['Lead()'](https://msdn.microsoft.com/en-us/library/hh213125.aspx)を使いましょう。私は日付の論理はあまりにも複雑だと思う。 "新しい"レコードがdateStatusChanged <= 2016-10-1で、next_Changeが2016-10-1の後に発生したことを確認するだけでいいですか? (おそらく 'coalesce(next_change、getDate())> '2016-10-1'?)(next_changeは日付ではない日付であると仮定します) – xQbert

+0

非常にうれしく、前にlead関数について知りませんでした。私はそれと一緒に遊ぶよ。 xQbert - 唯一の問題は、「新しい」レコードが2016-10-01の後にdateStatusChangedを持つことができることです(ItemId 2の場合のように)。私は基本的に10/31以前の「新しい」レコードを持つすべてのアイテムを必要とします。そこでは、アイテムの次の非「新しい」レコードは10/1の後に出現します(存在しない)。 – Cmaso

0

あなたはこの

select ItemId from (
select itemid, status, RowN = row_number() over (partition by itemid order by status) from youritem where MONTH(datestatuschanged) = 10 and year(datestatuschanged) = 2016 ) as SourceTable 
pivot(max(status) for RowN in ([1],[2])) p 
where [1] = 'Complete' and [2] = 'New' 

アイデアのようなクエリを試すことができますが

+0

これは私が過去にどのように解決したかと似ていますが、サーバー側のコードを使用してその場でクエリーオブジェクトを作成し、その後戻ってそのクエリーオブジェクトをクエリーしていました。すべてのSQLソリューションを表示していただきありがとうございます! – Cmaso

0

が簡単WHERE Status = 'New'COUNT(DISTINCT ...)に適応のみ必要な月と年の列に両方の状態を転置と比較して選択することがあります以下のCTEは、10月の各日の各アイテムのステータスを表示します:

;WITH DATE_CTE (aDate) AS (
    SELECT CAST('2016-10-01' AS DATETIME) 

    UNION ALL 

    SELECT DATEADD(d, 1, cte.aDate) 
     FROM DATE_CTE cte 
    WHERE cte.aDate < CAST('2016-10-31' AS DATETIME) 
) 
SELECT i.itemid, dates.aDate, i.status 
    FROM DATE_CTE dates 
     INNER JOIN itemstatus i 
      ON i.DateStatusChanged <= dates.aDate 
     LEFT OUTER JOIN itemstatus i2 
      ON i.ItemId = i2.ItemId 
      AND i.DateStatusChanged < i2.DateStatusChanged 
      AND dates.aDate >= i2.DateStatusChanged 
WHERE i2.DateStatusChanged IS NULL 
ORDER BY i.itemid, dates.aDate 

適切な答え:

;WITH DATE_CTE (aDate) AS (
    SELECT CAST('2016-10-01' AS DATETIME) 

    UNION ALL 

    SELECT DATEADD(d, 1, cte.aDate) 
     FROM DATE_CTE cte 
    WHERE cte.aDate < CAST('2016-10-31' AS DATETIME) 
) 
SELECT COUNT(DISTINCT i.itemid) 
    FROM DATE_CTE dates 
     INNER JOIN itemstatus i 
      ON i.DateStatusChanged <= dates.aDate 
     LEFT OUTER JOIN itemstatus i2 
      ON i.ItemId = i2.ItemId 
      AND i.DateStatusChanged < i2.DateStatusChanged 
      AND dates.aDate >= i2.DateStatusChanged 
WHERE i.Status = N'New' 
    AND i2.DateStatusChanged IS NULL 
関連する問題