SQL join query untuk menampilkan baris dengan baris yang tidak ada dalam satu tabel

12

Saya mencoba menyelesaikan pelaporan untuk catatan waktu karyawan.

Kami memiliki dua tabel khusus untuk pertanyaan ini. Karyawan tercantum dalam Memberstabel dan setiap hari mereka memasukkan entri waktu dari pekerjaan yang telah mereka lakukan dan disimpan dalam Time_Entrytabel.

Contoh pengaturan dengan SQL Fiddle: http://sqlfiddle.com/#!3/e3806/7

Hasil akhir yang saya tuju adalah tabel yang menunjukkan SEMUA di Membersdalam daftar kolom dan kemudian akan menampilkan jumlah jam mereka untuk tanggal yang ditanyakan di kolom lainnya.

Masalahnya adalah bahwa jika tidak ada baris dalam Time_Entrytabel untuk anggota tertentu, sekarang ada baris untuk anggota tersebut. Saya sudah mencoba beberapa tipe gabungan yang berbeda (Kiri, Kanan, Dalam, Luar, Luar Penuh, dll.) Tetapi sepertinya tidak ada yang memberi saya apa yang saya inginkan, yang akan menjadi (berdasarkan contoh terakhir dalam 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

Apa yang saya dapatkan saat saya meminta tanggal tertentu 11-1:

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

Yang benar berdasarkan pada satu baris Entri Waktu yang bertanggal 11-10-2013 untuk EGirsch, tetapi saya perlu melihat nol untuk anggota lain untuk mendapatkan laporan dan akhirnya dasbor web / laporan untuk informasi ini.

Ini adalah pertanyaan pertama saya, dan sementara saya mencari pertanyaan Gabung, dll. Saya jujur ​​tidak yakin apa fungsi ini dipanggil, jadi saya harap ini bukan duplikat dan akan membantu orang lain juga mencoba mencari solusi untuk masalah serupa.

perpisahan
sumber

Jawaban:

11

Terima kasih atas SQLfiddle dan data sampel! Saya berharap lebih banyak pertanyaan dimulai dengan cara ini.

Jika Anda ingin semua anggota terlepas dari apakah mereka memiliki entri untuk tanggal tersebut, Anda ingin a LEFT OUTER JOIN. Anda sangat dekat dengan versi ini, tetapi sedikit trik dengan gabungan luar adalah bahwa jika Anda menambahkan filter ke tabel luar dalam WHEREklausa, Anda mengubah gabungan luar ke gabungan dalam, karena itu akan mengecualikan setiap baris yang ada NULLdi sisi itu (karena tidak tahu apakah NULLakan cocok dengan filter atau tidak).

Saya memodifikasi kueri pertama untuk mendapatkan baris untuk setiap anggota:

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

Saya akan meninggalkannya sebagai latihan bagi pembaca untuk mengambilnya dari sana dan menambahkan kolom lain, pemformatan, COALESCEdll.

Beberapa catatan lain:

Aaron Bertrand
sumber
Aaron, terima kasih banyak atas umpan baliknya. SQL pemula di sini, dan tidak tahu perbedaan antara WHEREdan AND. Awalnya saya menggunakan alias, tetapi sqlfiddle sepertinya tidak menyukainya, jadi saya hanya memformat penuh. Terima kasih untuk tips SQL lainnya juga. Apakah Anda merekomendasikan ISNULLatau COALESCEmembuat data sebagai ganti 0NULL ? Terima kasih lagi!
perpisahan
1
@ Farewelldave Saya lebih suka COALESCE karena standar dan tidak menyimpang dari fungsinya dalam bahasa lain (bandingkan bagaimana ISNULL bekerja di SQL Server vs VB, misalnya). Dalam hampir semua kasus, perbedaan kinerja tidak penting, kecuali satu. Lebih banyak detail di sini .
Aaron Bertrand
4

Ketika saya dihadapkan dengan masalah jenis ini di masa lalu, saya telah membuat tabel "angka" untuk membantu menangani baris yang hilang.

Saya membuat tabel angka saya secara khusus untuk menangani tanggal sebagai berikut:

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

Ini membuat tabel dengan satu baris untuk setiap tanggal antara 1900-01-01 dan 2099-12-31. Saya menggunakan TOP(73049)untuk membatasi rentang tanggal yang dihasilkan dalam contoh saya ke tanggal - jika Anda bekerja dengan rentang tanggal yang berbeda, Anda dapat menyesuaikan angka itu.

Selanjutnya, saya menambahkan dDatestabel ke kueri saya sehingga baris dikembalikan untuk setiap tanggal dalam rentang yang diinginkan untuk setiap member_id. Hasilnya kemudian bergabung ke Time_Entrytabel seperti itu:

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

Ini memungkinkan Anda menentukan rentang tanggal untuk laporan.

Anda dapat lebih mempertajam hasil dengan menambahkan COALESCE(...)dan SUM(...)sesuai:

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

Ini menghasilkan output berikut untuk data sampel Anda:

masukkan deskripsi gambar di sini

Max Vernon
sumber
Terima kasih, Max. Anda dapat menemukan banyak informasi tentang teknik ini dengan mencari "tabel penghitungan" dan bukan "tabel angka". Mereka bagus untuk meningkatkan kinerja dengan mengubah operasi menggunakan kursor / loop menjadi operasi menggunakan set. Database relasional lebih suka set.
Suncat2000
1
@ Suncat2000 - setuju, meskipun saya lebih suka nama "tabel angka" karena penghitungan menyiratkan penambahan, dan dalam pengalaman saya, pola ini jarang digunakan untuk operasi matematika. Mereka bagus untuk banyak hal, tetapi tentu saja salah satu peningkatan kinerja terbesar yang bisa Anda dapatkan adalah beralih dari pendekatan RBAR, ke pendekatan berbasis set, dengan menggunakan tabel angka.
Max Vernon