web-dev-qa-db-ja.com

1つのテーブルに存在しない行を含む行を表示するSQL結合クエリ

私は従業員の時間記録についていくつかのレポートを作成しようとしています。

この質問に特化した2つの表があります。従業員はMembersテーブルにリストされており、毎日、彼らが実行した作業の時間エントリを入力し、Time_Entryテーブルに保存されます。

SQL Fiddleの設定例: http://sqlfiddle.com/#!3/e3806/7

私がしようとしている最終結果は、[〜#〜] all [〜#〜]を示すテーブルですMembers列リストには、他の列で照会された日付の合計時間が表示されます。

問題は、特定のメンバーのTime_Entryテーブルに行がない場合、そのメンバーの行があることです。いくつかの異なる結合タイプ(左、右、内部、外部、完全外部など)を試しましたが、(SQL Fiddleの最後の例に基づいて)希望どおりの結果が得られないようです。

/*** Desired End Result ***/

Member_ID   | COUNTTime_Entry | TIMEENTRYDATE | SUMHOURS_ACTUAL | SUMHOURS_BILL
ADavis      | 0               | 11-10-2013    | 0               | 0
BTronton    | 0               | 11-10-2013    | 0               | 0
CJones      | 0               | 11-10-2013    | 0               | 0
DSmith      | 0               | 11-10-2013    | 0               | 0
EGirsch     | 1               | 11-10-2013    | 0.92            | 1
FRowden     | 0               | 11-10-2013    | 0               | 0

11-1の特定の日付をクエリしたときに私が現在得ているもの:

Member_ID   | COUNTTime_Entry | TIMEENTRYDATE | SUMHOURS_ACTUAL | SUMHOURS_BILL
EGirsch     | 1               | 11-10-2013    | 0.92            | 1

これはEGirschの2013年11月10日付けの1つのタイムエントリ行に基づいて正しいですが、レポートを取得し、最終的にこの情報のWebダッシュボード/レポートを取得するには、他のメンバーのゼロを表示する必要があります。

これは私の最初の質問です。Joinクエリなどを検索しましたが、この関数が何と呼ばれるかは正直わかりません。これが重複しておらず、他の人の役に立つことを願っています同様の問題の解決策を見つけようとしています。

12
farewelldave

SQLfiddleとサンプルデータをありがとう!もっと多くの質問がこのように始まったことを望みます。

その日付のエントリがあるかどうかに関係なく、すべてのメンバーが必要な場合は、LEFT OUTER JOINが必要です。 このバージョン に非常に近かったが、外部結合の小さなトリックは、WHERE句で外部テーブルにフィルタを追加すると、これは、その側でNULLである行をすべて除外するためです(NULLがフィルターに一致するかどうかがわからないため)。

最初のクエリを変更して、すべてのメンバーの行を取得します。

SELECT Members.Member_ID
      ,Time_Entry.Date_Start
      ,Time_Entry.Hours_Actual
      ,Time_Entry.Hours_Bill
FROM dbo.Members
  LEFT OUTER JOIN dbo.Time_Entry
--^^^^ changed from FULL to LEFT
  ON Members.Member_ID = Time_Entry.Member_ID
  AND Time_Entry.Date_Start = '20131110';
--^^^ changed from WHERE to AND

読者がそこから取り出して他の列、フォーマット、COALESCEなどを追加するための演習として残しておきます。

その他の注意事項:

11
Aaron Bertrand

過去にこの種の問題に直面したことがある場合は、不足している行の処理に役立つ "numbers" テーブルを作成しました。

私は特に日付を処理するために数値テーブルを作成しました:

_CREATE TABLE Dates
(
    dDate DATETIME NOT NULL CONSTRAINT PK_Dates PRIMARY KEY CLUSTERED
);

INSERT INTO Dates (dDate)
SELECT TOP(73049) DATEADD(d, -1, ROW_NUMBER() OVER (ORDER BY o.object_id)) AS dDate
FROM master.sys.objects o, master.sys.objects o1, master.sys.objects o2
_

これにより、1900-01-01から2099-12-31までの日付ごとに1行の表が作成されます。私はTOP(73049)を使用して、この例で生成される日付範囲をこれらの日付に制限します-別の日付範囲で作業する場合は、その数値を調整できます。

次に、dDatesテーブルをクエリに追加して、すべての_member_id_の目的の範囲内のすべての日付の行が返されるようにします。次に、結果が_Time_Entry_テーブルに結合されます。

_SELECT MD.Member_ID,
    MD.dDate,
    T.Date_Start,
    T.Hours_Actual,
    T.Hours_Bill
FROM 
    (
        SELECT M.Member_ID, D.dDate
        FROM dbo.Dates D, dbo.Members M
        WHERE D.dDate >= '20131110' AND D.dDate < '20131112'
    ) AS MD
    LEFT JOIN dbo.Time_Entry T ON MD.Member_ID = T.Member_ID AND MD.dDate = T.Date_Start
ORDER BY MD.Member_ID, MD.dDate
_

これにより、レポートの日付範囲を指定できます。

次のようにCOALESCE(...)およびSUM(...)を追加することにより、結果をさらに絞り込むことができます。

_SELECT MD.Member_ID,
    MD.dDate,
    T.Date_Start,
    SUM(COALESCE(T.Hours_Actual, 0)) AS TotalHoursActual,
    SUM(COALESCE(T.Hours_Bill, 0)) AS TotalHoursBill
FROM 
    (
        SELECT M.Member_ID, D.dDate
        FROM dbo.Dates D, dbo.Members M
        WHERE D.dDate >= '20131110' AND D.dDate < '20131112'
    ) AS MD
    LEFT JOIN dbo.Time_Entry T ON MD.Member_ID = T.Member_ID AND MD.dDate = T.Date_Start
GROUP BY MD.Member_ID, MD.dDate, T.Date_Start
ORDER BY MD.Member_ID, MD.dDate
_

これにより、サンプルデータの出力は次のようになります。

enter image description here

4
Max Vernon