2017-08-04 10 views
2

この問題の解決方法を探しています。私はLogEntryと呼ばれるテーブルを持っていて、複数のオフィスで使用されている情報を保管しています。そこには、任意の日にオフィスに来る訪問者をログに記録する必要があります。訪問者が来ていない場合、その日の「訪問者なし」を記録する必要があります。オフィスで「訪問者なし」のログを作成できなかったすべての日付を取得するクエリを実行するにはどうすればよいですか?SQL Serverにエントリのない日付のリストを取得

私はthis question(とその中にリンクされている記事)を見てきましたが、そのクエリを適合さえしても、オフィスに日付のエントリがない場合は空白の行を作成できますエントリを作成していない実際のオフィス。私がやろうとしていることをする方法はありますか?

declare @temp table (

CDate datetime, 
loc_id varchar(50) 
) 

insert into @temp SELECT DISTINCT entryDate, locationID FROM LogEntry WHERE entryDate >= '05/01/2017' AND entryDate <= '07-31-2017' 

;with d(date) as (
    select cast('05/01/2017' as datetime) 
    union all 
    select date+1 
    from d 
    where date < '07/31/2017' 
) 

select DISTINCT t.loc_id, CONVERT(date, d.date) 
    FROM d LEFT OUTER JOIN @temp t ON d.date = t.CDate 
    GROUP BY t.loc_id, d.date 
    ORDER BY t.loc_id 

私が言ったように、このクエリは私に日付範囲内の日付のリストを返し、その日にエントリを提出したすべての場所が、私は基本的に反対の情報を抽出する方法を見つけるしたいと思います:オフィス(locationIDで指定)が特定の日にエントリーを提出しなかった場合は、それらのlocationIDと欠席した日付のみを返します。

Sample data 

EntryID | locationID | entryDate 
================================= 
1   1   07-01-2017 
2   1   07-02-2017 
3   2   07-02-2017 
4   1   07-04-2017 

Expected Result (for date range of 07-01 to 07-04) 

locationID | missedEntryDate 
============================ 
1   07-03-2017 
2   07-01-2017 
2   07-03-2017 
2   07-04-2017 
+0

'DISTINCT'は無関係で、LEFT JOINの代わりに' RIGHT JOIN'を使います。 – Siyual

+0

場所は特定の日付の複数のエントリを作成できるため、別名を追加しました。私は彼らが少なくとも1つを作成したことを知ることに興味があります。 –

+2

私が言っていることは、 'GROUP BY'は本質的に' DISTINCT'というクエリを作ることです。 – Siyual

答えて

0

データセットは、あなたがこれを試すことができますあまりにも大きくない場合:

select t.loc_id, CONVERT(date, d.date) 
FROM d 
    -- Cross join dates to all available locs 
    CROSS JOIN (SELECT DISTINCT loc_id FROM @temp) AS Locs 
    LEFT JOIN 
     (SELECT loc_id, t.CDate 
     FROM @temp 
     GROUP BY loc_id, d.date) AS t ON d.date = t.CDate AND Locs.loc_id = t.loc_id 
ORDER BY Locs.loc_id 

これは少し速くする必要があります:

;WITH cte AS (
    SELECT a.LocID, RangeStart.CDate, (CASE WHEN Input.LocID IS NULL THEN 1 ELSE 0 END) AS IsMissing 
    FROM (SELECT DISTINCT LocID FROM #temp) AS a 
    CROSS JOIN (SELECT CONVERT(DATETIME, '2017-05-01') AS CDate) AS RangeStart 
    LEFT JOIN 
      (SELECT LocID, MIN(CDate) AS CDate 
      FROM #temp 
      WHERE CDate = '2017-05-01' 
      GROUP BY LocID) AS Input ON a.LocID = Input.LocID AND RangeStart.CDate = Input.CDate 
    UNION ALL 
    SELECT a.LocID, a.CDate + 1 AS CDate, 
     ISNULL(ItExists, 0) AS IsMissing 
    FROM cte AS a 
     OUTER APPLY(SELECT LocID, 1 AS ItExists FROM #temp AS b WHERE a.LocID = b.LocID AND a.CDate + 1 = b.CDate) AS c 
    WHERE a.CDate < '2017-07-01' 
) 
SELECT * FROM cte OPTION(MAXRECURSION 0) 

また、インデックスを追加することができます

CREATE INDEX IX_tmp_LocID_CDate ON #temp(LocID, CDate) 

サンプルデータセットcondクエリ:

CREATE TABLE #temp(LocID VARCHAR(50), CDate DATETIME) 
INSERT INTO #temp 
VALUES 
('1', '2017-05-01'), ('1', '2017-05-02'), ('1', '2017-05-03'), ('1', '2017-05-04'), ('1', '2017-05-05'), 
('2', '2017-05-01'), ('2', '2017-05-02'), ('2', '2017-05-03'), ('2', '2017-05-04'), ('2', '2017-05-05') 

;WITH d AS (
    SELECT CAST('05/01/2017' AS DATETIME) AS date 
    UNION ALL 
    SELECT date + 2 
    FROM d 
    WHERE date < '2018-07-31' 
) 
INSERT INTO #temp 
SELECT LocID, d.date 
FROM (SELECT DISTINCT LocID FROM #temp) AS a 
    CROSS JOIN d 
OPTION(MAXRECURSION 0) 
+0

フォーマットのため、最初はクエリが理解できませんでした。私と非常に似ています。しかし、2つのこと。 ** A)**あなたは 'GROUP BY'を必要としません** B)** LEFT JOINからNULL値だけを返して、どの日付にエントリがないのかを示す必要があります。 –

+0

@JuanCarlosOropeza - 好きなフォーマットは主観的です - 私はあなたのものが好きではありません。 Re ** A **:元のポスターが** B **のために重要な重複を削除するためにクエリにそれを持っていたので、私は 'Group By'を使いました。 Re ** B **:OPが出力を比較できるように 'WHERE'節は含んでいませんでした。私が思った質問に基づいて、彼はこれを自分自身で理解するのに十分なスキルを持っています。 – Alex

+0

私はあなたのフォーマットを批判していませんでしたが、最初は分かりませんでした;) –

2

最初のステップは良かったです。すべての日付のリストを作成しますが、すべての場所のリストも必要です。次に、クロス結合を作成してすべての組み合わせを作成し、次にleft joinを実行して欠落しているものを見つけます。

;with allDates(date) as (
    select cast('05/01/2017' as datetime) 
    union all 
    select date+1 
    from d 
    where date < '07/31/2017' 
), allLocations as (
    SELECT DISTINCT loc_id 
    FROM @temp 
), allCombinations as ( 
    SELECT date, loc_id 
    FROM allDates 
    CROSS JOIN allLocations 
) 
SELECT AC.loc_id, AC.date 
FROM allCombinations AC 
LEFT JOIN @temp t 
    ON AC.date = t.CDate 
AND AC.loc_id = t.loc_id 
WHERE t.loc_id IS NULL -- didnt find a match on @temp 
+0

複数のCTEが必要ないことに注意してください。@Alexの回答としてより直接的なクエリを使用できますが、各ステップを分けたいので、終わり。 –

+0

なぜあなたは 'AC.loc_id'と' t.CDate'をjoinにマッチさせますか? –

+0

Typo、私はコピーペーストをしました..ただそれを修正してください。 –

関連する問題