2017-02-22 5 views
1

SQL Server 2012では、複数のチェックインが行われている間、1つのアイテムを1つの連続した期間にわたって1人の行を返すにはどうすればよいですか?アイテムと日付のチェックインとチェックアウト

我々は3つのテーブルがあります。人は、彼らが項目にチェックインをする時はいつでも関係表が更新された人、

Id, Fullname 

関係

PersonId, ItemId, DateTime 

項目

Id, ItemName 

を手元にある。

私はすべての結果を取得するために3つのテーブルを結合することができますが、私は後だが、以下の結果である:

フルネーム、アイテム、チェックイン、チェックアウト 例えば

Person1, Item1, 2016-10-25 20:00:00.000, 2016-10-29 20:00:00.000 
Person1, Item2, 2016-10-25 20:00:00.000, 2016-10-26 20:00:00.000 
Person1, Item2, 2016-10-28 20:00:00.000, 2016-10-29 20:00:00.000 
Person1, Item3, 2016-10-25 20:00:00.000, 2016-10-29 20:00:00.000 
... 
... 

注「PERSON1は」 'Person1'が 'Item2'なしで27日目にチェックインを行ったため、 'Person1'、 'Item2'の2つのエントリがあるため '2016-10-27 20:00:00.000'の 'Item2' '

テーブルを作成するためのtSQLコードは次のとおりです。

IF OBJECT_ID('tempdb.dbo.#Person', 'U') IS NOT NULL DROP TABLE tempdb.dbo.#Person 
IF OBJECT_ID('tempdb.dbo.#Relationship', 'U') IS NOT NULL DROP TABLE tempdb.dbo.#Relationship 
IF OBJECT_ID('tempdb.dbo.#Item', 'U') IS NOT NULL DROP TABLE tempdb.dbo.#Item 

CREATE TABLE #Person(
Id int IDENTITY, 
Fullname nvarchar(100) 
); 

CREATE TABLE #Relationship(
Id int IDENTITY, 
PersonId int, 
ItemId int, 
DateTime datetime 
); 


CREATE TABLE #Item(
Id int IDENTITY, 
ItemName nvarchar(100) 
); 

INSERT INTO #Person (Fullname) 
VALUES ('Person1'), 
('Person2'), 
('Person3'); 

INSERT INTO #Item (ItemName) VALUES 
('Item1'), 
('Item2'), 
('Item3'); 

INSERT INTO #Relationship (PersonId, ItemId, DateTime) VALUES 
(1, 1, '2016-10-25 20:00:00.000'), 
(1, 2, '2016-10-25 20:00:00.000'), 
(1, 3, '2016-10-25 20:00:00.000'), 
(1, 1, '2016-10-26 20:00:00.000'), 
(1, 2, '2016-10-26 20:00:00.000'), 
(1, 3, '2016-10-26 20:00:00.000'), 
(1, 1, '2016-10-27 20:00:00.000'), 
(1, 3, '2016-10-27 20:00:00.000'), 
(1, 1, '2016-10-28 20:00:00.000'), 
(1, 2, '2016-10-28 20:00:00.000'), 
(1, 3, '2016-10-28 20:00:00.000'), 
(1, 1, '2016-10-29 20:00:00.000'), 
(1, 2, '2016-10-29 20:00:00.000'), 
(1, 3, '2016-10-29 20:00:00.000'), 

(2, 1, '2016-10-25 20:00:00.000'), 
(2, 2, '2016-10-25 20:00:00.000'), 
(2, 3, '2016-10-25 20:00:00.000'), 
(2, 1, '2016-10-26 20:00:00.000'), 
(2, 2, '2016-10-26 20:00:00.000'), 
(2, 3, '2016-10-26 20:00:00.000'), 
(2, 1, '2016-10-27 20:00:00.000'), 
(2, 2, '2016-10-27 20:00:00.000'), 
(2, 3, '2016-10-27 20:00:00.000'); 



SELECT P.FullName, I.ItemName, R.DateTime As CheckInDate FROM #Person AS P 
INNER JOIN #Relationship AS R ON R.PersonId = P.Id 
INNER JOIN #Item AS I ON I.Id = R.ItemId 

解決策を見つけるお手伝いをしてくれてありがとう!

編集:すべての結果を返して、C#でRBARを処理できましたが、私は洗練されたSQLソリューションを望んでいました。あなたの考えは?

+0

を逃すことができるのであれば、あなたが 'GROUP BY'のフルネームとitemNameにしたいと' MIN'と 'MAX'日付を取得合うようにJOINS調整する必要があるかもしれませんか? – ZLK

+0

私は両方を使ってプレイしました MIN(R.DateTime)OVER()AS CheckInDate、MAX(R.DateTime)OVER()AS CheckOutDate' チェックイン時に行を分割することができませんでした特定の項目なしで発生しました。 – bsand

+0

'LEAD'と' LAG'関数はあなたのセットの前/次の値を得ることを可能にします。代わりに 'ROW_NUMBER'を使ってこれを解決できます。 – mheptinstall

答えて

0

問合せ:

WITH temp AS (
    SELECT 
     r1.*, 
     DATEDIFF(d, r1.[DateTime], LEAD(r1.[DateTime]) OVER(PARTITION BY r1.PersonId, r1.ItemId ORDER BY r1.[DateTime] ASC)) ddiff_next, 
     RANK() OVER (PARTITION BY r1.PersonId, r1.ItemId ORDER BY r1.[DateTime] ASC) rn 
    FROM 
     #Relationship r1 
    LEFT JOIN 
     #Relationship r2 ON r1.PersonId = r2.PersonId AND r1.ItemId = r2.ItemId AND r1.[DateTime] = DATEADD(d, -1, r2.[DateTime]) 
    LEFT JOIN 
     #Relationship r3 ON r1.PersonId = r3.PersonId AND r1.ItemId = r3.ItemId AND r1.[DateTime] = DATEADD(d, 1, r3.[DateTime]) 
    WHERE 
     -- get the intervals' first and last rows, ie filter the rows 
     -- where the date difference between it's and the next or preceding  
     -- row's dates greater than one day, or does not exists (first or 
     -- last row 
     r2.[DateTime] IS NULL OR r3.[DateTime] IS NULL 
) 

SELECT 
    p.FullName, 
    i.ItemName, 
    t1.[DateTime] AS DateFrom, 
    t2.[DateTime] AS DateTo 
FROM 
    temp t1 
LEFT JOIN 
    temp t2 ON t1.rn = (t2.rn - 1) AND t1.PersonId = t2.PersonId AND t1.ItemId = t2.ItemId 
INNER JOIN 
    #Item AS i ON i.Id = t1.ItemId 
INNER JOIN 
    #Person AS p ON p.Id = t1.PersonId 
WHERE 
    -- exclude the gaps 
    (t1.ddiff_next IS NOT NULL AND t2.ddiff_next IS NULL) 
    OR (t1.ddiff_next IS NOT NULL AND t2.ddiff_next IS NOT NULL AND t2.ddiff_next > 1) 
; 

出力:

+----------+----------+-------------------------+-------------------------+ 
| FullName | ItemName |  DateFrom   |   DateTo   | 
+----------+----------+-------------------------+-------------------------+ 
| Person1 | Item1 | 2016-10-25 20:00:00.000 | 2016-10-29 20:00:00.000 | 
| Person1 | Item2 | 2016-10-25 20:00:00.000 | 2016-10-26 20:00:00.000 | 
| Person1 | Item2 | 2016-10-28 20:00:00.000 | 2016-10-29 20:00:00.000 | 
| Person1 | Item3 | 2016-10-25 20:00:00.000 | 2016-10-29 20:00:00.000 | 
| Person2 | Item1 | 2016-10-25 20:00:00.000 | 2016-10-27 20:00:00.000 | 
| Person2 | Item2 | 2016-10-25 20:00:00.000 | 2016-10-27 20:00:00.000 | 
| Person2 | Item3 | 2016-10-25 20:00:00.000 | 2016-10-27 20:00:00.000 | 
+----------+----------+-------------------------+-------------------------+ 
+0

(コメントに改行を挿入するにはどうすればよいですか?返信が愚かに見えます) 'Person1'に 'Item2'がない場合、そのクエリは2つのエントリを返しません 'Person1 \t Item1 \t 2016-10-25 20:00 :00.000 \t 2016年10月29日20:00:00.000' 'PERSON1 \tアイテム2 \t 2016年10月25日20:00:00.000 \t 2016年10月29日20:00:00.000' ' PERSON1 \t ITEM3 \t 2016- 10-25 20:00:00。000 \t 2016年10月29日20:00:00.000' 'PERSON2 \tアイテム1 \t 2016年10月25日20:00:00.000 \t 2016年10月27日20:00:00.000' ' PERSON2 \tアイテム2 \t 2016から10 -25 20:00:00.000 \t 2016-10-27 20:00:00.000' 'Person2 \t Item3 \t 2016-10-25 20:00:00.000 \t 2016-10-27 20:00:00.000' – bsand

+0

これは機能します完璧、ありがとう。私はクエリを踏み出し、CTEの中でどのように動作するかを理解しています。どのようにして個々の小さなコンポーネントに問題を分解し、必要なステップを思いついたか尋ねることはできますか?私はいくつかのSQL学習をしたいです。 – bsand

+0

私は助けることができてうれしい!今はあまり時間がありませんが、週末には私の考えについて書くつもりです。 – banazs

0

は、以下のようないくつかは、あなたの希望する結果が得られていますか?あなたは、あなたのデータが

SELECT 
     * 
    , LAG(R.DateTime) OVER(PARTITION BY R.PersonId, R.ItemId 
      ORDER BY R.DateTime) AS NextDatetime 
    , LEAD(R.DateTime) OVER(PARTITION BY R.PersonId, R.ItemId 
      ORDER BY R.DateTime) AS PreviousDatetime 

    FROM 
     #Person AS P 
     JOIN #Relationship AS R 
      ON P.Id = R.PersonId 
     JOIN #Item AS I 
      ON R.ItemId = I.Id 

http://sqlfiddle.com/#!6/2c01c/4

+0

偉大な返信、私はLAGとLEADについて知りませんでした。これで、NULLに当たるまで連続した時間を与えるようにしてみましょう。ありがとう! – bsand

+1

それはあなたが何を意味するのかは、継続的な期間によって決まります。これはギャップや島の問題のように思えますが、問題と解決策を読むことができる非常に良いPDFがあります。https://manning-content.s3.amazonaws.com/download/3/e589652-7171-4310-a714-e84dd0f14090 /SampleChapter5.pdf – mheptinstall

+0

類推は雇用期間のようなものです。 Worker Xは10日間の連続した仕事をした後、5日間連続して休み、その後10日間連続して仕事をしました。毎日の仕事の記録があり、このクエリが2行を返すように、私はそれを説明するのに困っている。 私は今PDFを読んでいただき、ありがとうございます – bsand

関連する問題