แบบสอบถาม SQL เข้าร่วมเพื่อแสดงแถวที่ไม่มีแถวในหนึ่งตาราง


12

ฉันกำลังพยายามทำให้การรายงานบางอย่างเสร็จสิ้นเพื่อบันทึกเวลาของพนักงาน

เรามีสองตารางสำหรับคำถามนี้โดยเฉพาะ พนักงานมีการระบุไว้ในMembersตารางและในแต่ละวันพวกเขาป้อนรายการเวลาของงานที่พวกเขาทำและถูกเก็บไว้ในTime_Entryตาราง

ตัวอย่างการตั้งค่าด้วย SQL Fiddle: http://sqlfiddle.com/#!3/e3806/7

ผลลัพธ์ที่ฉันจะให้เป็นตารางซึ่งแสดงให้เห็นทั้งหมด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

ซึ่งถูกต้องตามแถวเวลาหนึ่งรายการที่ลงวันที่ 11-10-2013 สำหรับ EGirsch แต่ฉันต้องดูค่าศูนย์สำหรับสมาชิกคนอื่น ๆ เพื่อรับรายงานและท้ายที่สุดเว็บแดชบอร์ด / รายงานสำหรับข้อมูลนี้

นี่เป็นคำถามแรกของฉันและในขณะที่ฉันค้นหาคิวรีเข้าร่วม ฯลฯ ฉันไม่แน่ใจว่าจะเรียกฟังก์ชั่นนี้อย่างไรฉันหวังว่านี่จะไม่ซ้ำกันและจะช่วยให้ผู้อื่นพยายามหาวิธีแก้ปัญหา ปัญหาที่คล้ายกัน

คำตอบ:


11

ขอบคุณสำหรับ 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อื่น ๆ

หมายเหตุอื่น ๆ :


แอรอนขอบคุณมากสำหรับความคิดเห็น SQL มือใหม่ที่นี่และมีความคิดที่แตกต่างระหว่างและWHERE ANDฉันเคยใช้นามแฝงมาก่อน แต่ sqlfiddle ดูเหมือนจะไม่ชอบดังนั้นฉันจึงเปลี่ยนเป็นแบบเต็มรูปแบบ ขอบคุณสำหรับเคล็ดลับ SQL อื่น ๆ เช่นกัน คุณอยากจะแนะนำISNULLหรือCOALESCEการให้ข้อมูลที่0แทนNULL? ขอบคุณอีกครั้ง!
อำลาเดฟ

1
@farewelldave ฉันชอบ COALESCE เพราะเป็นมาตรฐานและไม่เบี่ยงเบนจากการทำงานในภาษาอื่น (เปรียบเทียบการทำงานของ ISNULL ใน SQL Server vs. VB ตัวอย่าง) ในเกือบทุกกรณีความแตกต่างด้านประสิทธิภาพนั้นไม่สำคัญยกเว้นเพียงอย่างเดียว จำนวนมากรายละเอียดเพิ่มเติมได้ที่นี่
Aaron Bertrand

4

เมื่อก่อนหน้านี้ฉันเคยเจอปัญหาประเภทนี้ฉันได้สร้างตาราง"ตัวเลข"เพื่อช่วยจัดการกับแถวที่หายไป

ฉันสร้างตารางตัวเลขโดยเฉพาะเพื่อจัดการกับวันที่:

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 ฉันใช้TOP(73049)เพื่อ จำกัด ช่วงวันที่ที่สร้างในตัวอย่างของฉันไปยังวันที่ thos - ถ้าคุณทำงานกับช่วงวันที่ที่แตกต่างกันคุณสามารถปรับจำนวนที่

ต่อไปฉันเพิ่ม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

ผลลัพธ์นี้เป็นผลลัพธ์ต่อไปนี้สำหรับข้อมูลตัวอย่างของคุณ:

ป้อนคำอธิบายรูปภาพที่นี่


ขอบคุณสูงสุด คุณสามารถหาข้อมูลเกี่ยวกับเทคนิคนี้ได้อย่างดีโดยค้นหา "ตารางคะแนน" แทน "ตารางตัวเลข" เหมาะสำหรับการปรับปรุงประสิทธิภาพโดยการแปลงการดำเนินการโดยใช้เคอร์เซอร์ / ลูปเป็นการดำเนินการโดยใช้ชุด ฐานข้อมูลเชิงสัมพันธ์ชอบชุด
Suncat2000

1
@ Suncat2000 - เห็นด้วยแม้ว่าฉันชอบชื่อ "ตารางตัวเลข" เนื่องจากนับรวมถึงการเพิ่มและจากประสบการณ์ของฉันรูปแบบนี้ไม่ค่อยได้ใช้สำหรับการดำเนินการทางคณิตศาสตร์ มันยอดเยี่ยมสำหรับหลาย ๆ สิ่ง แต่แน่นอนว่าหนึ่งในการปรับปรุงประสิทธิภาพที่ใหญ่ที่สุดที่คุณจะได้รับคือการเปลี่ยนจากวิธี RBAR ไปเป็นแบบ set-based โดยใช้ตารางตัวเลข
Max Vernon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.