สรุป
SQL Server ใช้ที่ถูกต้องเข้าร่วม (ภายในหรือภายนอก) และเพิ่มการคาดการณ์ที่จำเป็นที่จะต้องให้เกียรติความหมายทั้งหมดของแบบสอบถามเดิมเมื่อทำการแปลภายในระหว่างใช้และเข้าร่วม
ความแตกต่างในแผนทั้งหมดสามารถอธิบายได้โดยความหมายที่แตกต่างกันของการรวมที่มีและไม่มีกลุ่มตามข้อใน SQL Server
รายละเอียด
เข้าร่วมกับการสมัคร
เราจะต้องสามารถแยกแยะระหว่างการสมัครและการเข้าร่วม :
ใช้
อินพุตด้านใน (ด้านล่าง) ของการนำไปใช้จะถูกเรียกใช้สำหรับแต่ละแถวของอินพุตด้านนอก (ด้านบน) พร้อมกับพารามิเตอร์พารามิเตอร์ด้านในอย่างน้อยหนึ่งค่าที่ระบุโดยแถวด้านนอกปัจจุบัน ผลลัพธ์โดยรวมของการนำไปใช้คือการรวมกัน (รวมทั้งหมด) ของแถวทั้งหมดที่สร้างโดยการดำเนินการด้านในแบบพารามิเตอร์ การปรากฏตัวของพารามิเตอร์หมายถึงการใช้บางครั้งเรียกว่าการเข้าร่วมที่มีความสัมพันธ์
การใช้งานจะถูกนำไปใช้ในแผนการดำเนินการโดยผู้ประกอบการNested Loops ผู้ประกอบการจะมีคุณสมบัติการอ้างอิงภายนอกมากกว่าเข้าร่วมเพรดิเคต การอ้างอิงภายนอกคือพารามิเตอร์ที่ส่งผ่านจากด้านนอกไปยังด้านในของการวนซ้ำแต่ละครั้งของลูป
ร่วม
การเข้าร่วมประเมินการเข้าร่วมของกริยาที่ผู้ประกอบการเข้าร่วม โดยทั่วไปการเข้าร่วมอาจนำมาใช้โดยตัวดำเนินการHash Match , MergeหรือNested Loopsใน SQL Server
เมื่อเลือกลูปซ้อนกันมันสามารถแยกแยะได้จากการใช้โดยไม่มีการอ้างอิงภายนอก (และมักจะมีสถานะของการเข้าร่วมกริยา) อินพุตด้านในของการเข้าร่วมไม่เคยอ้างถึงค่าจากอินพุตด้านนอก - ด้านในยังคงถูกดำเนินการหนึ่งครั้งสำหรับแต่ละแถวด้านนอก แต่การดำเนินการด้านข้างไม่ได้ขึ้นอยู่กับค่าใด ๆ จากแถวนอกปัจจุบัน
สำหรับรายละเอียดเพิ่มเติมดูโพสต์ของฉันสมัครเมื่อเทียบกับการซ้อนลูปเข้าร่วม
... ทำไมจึงมีการเข้าร่วมด้านนอกในแผนการดำเนินการแทนการเข้าร่วมภายใน
การเข้าร่วมด้านนอกเกิดขึ้นเมื่อเครื่องมือเพิ่มประสิทธิภาพเปลี่ยนการใช้กับการเข้าร่วม (ใช้กฎที่เรียกว่าApplyHandler
) เพื่อดูว่าสามารถค้นหาแผนการเข้าร่วมที่ถูกกว่าได้หรือไม่ เข้าร่วมจะต้องเข้าร่วม outer สำหรับความถูกต้องเมื่อใช้มีรวมเกลา การเข้าร่วมภายในจะไม่รับประกันว่าจะให้ผลลัพธ์เช่นเดียวกับการสมัครดั้งเดิมที่เราจะเห็น
เกลาและเวกเตอร์มวลรวม
- การรวมโดยไม่มี
GROUP BY
ข้อที่สอดคล้องกันคือการรวมสเกลาร์
- การรวมกับ
GROUP BY
ประโยคที่สอดคล้องกันคือการรวมเวกเตอร์
ใน SQL Server การรวมสเกลาร์จะสร้างแถวทุกครั้งแม้ว่าจะไม่มีการรวมแถวก็ตาม ตัวอย่างเช่นการCOUNT
รวมสเกลาร์ของไม่มีแถวเป็นศูนย์ การรวมเวกเตอร์ COUNT
ของไม่มีแถวเป็นชุดว่าง (ไม่มีแถวเลย)
ข้อความค้นหาของเล่นต่อไปนี้แสดงให้เห็นถึงความแตกต่าง นอกจากนี้คุณยังสามารถอ่านข้อมูลเพิ่มเติมเกี่ยวกับสเกลาร์และเวกเตอร์มวลรวมในบทความของฉันสนุกกับสเกลาร์และเวกเตอร์ขันธ์
-- Produces a single zero value
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1;
-- Produces no rows
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1 GROUP BY ();
db <> การสาธิตซอ
การแปลงใช้เพื่อเข้าร่วม
ฉันกล่าวก่อนที่เข้าร่วมจะต้องเข้าร่วม outer สำหรับความถูกต้องเมื่อเดิมใช้มีรวมเกลา เพื่อแสดงสาเหตุของรายละเอียดในกรณีนี้ฉันจะใช้ตัวอย่างแบบสอบถามที่ง่ายขึ้น:
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
SELECT * FROM @A AS A
CROSS APPLY (SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A) AS CA;
ผลลัพธ์ที่ถูกต้องสำหรับคอลัมน์c
คือศูนย์เนื่องจากCOUNT_BIG
เป็นผลรวมสเกลาร์ เมื่อแปลแบบสอบถามนี้ใช้เพื่อเข้าร่วมแบบฟอร์ม, SQL Server สร้างทางเลือกภายในที่จะมีลักษณะคล้ายกับต่อไปนี้ถ้ามันถูกแสดงใน T-SQL:
SELECT A.*, c = COALESCE(J1.c, 0)
FROM @A AS A
LEFT JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
ในการเขียนการนำไปใช้ใหม่เป็นการเข้าร่วมแบบไม่มีส่วนร่วมเราต้องแนะนำGROUP BY
ตารางในตารางที่ได้รับ (มิฉะนั้นอาจไม่มีA
คอลัมน์ที่จะเข้าร่วม) การเข้าร่วมจะต้องเป็นการรวมภายนอกเพื่อให้แต่ละแถวจากตาราง@A
ยังคงสร้างแถวในเอาต์พุต การเข้าร่วมด้านซ้ายจะสร้างNULL
คอลัมน์สำหรับc
เมื่อภาคแสดงการเข้าร่วมไม่ได้ประเมินว่าเป็นจริง ที่NULL
ตอบสนองความต้องการที่จะได้รับการแปลเป็นศูนย์โดยการCOALESCE
ดำเนินการเปลี่ยนแปลงที่ถูกต้องจากใช้
ตัวอย่างด้านล่างแสดงให้เห็นว่าทั้งการรวมภายนอกและCOALESCE
จำเป็นต้องสร้างผลลัพธ์เดียวกันโดยใช้การเข้าร่วมเป็นแบบสอบถามแบบใช้ครั้งแรก
db <> การสาธิตซอ
กับ GROUP BY
... ทำไมการไม่แสดงความคิดเห็นกลุ่มโดยประโยคส่งผลให้ภายในเข้าร่วม?
ดำเนินการต่อตัวอย่างที่ง่ายต่อ แต่เพิ่มGROUP BY
:
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
-- Original
SELECT * FROM @A AS A
CROSS APPLY
(SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A GROUP BY B.A) AS CA;
COUNT_BIG
ตอนนี้เป็นเวกเตอร์รวมเพื่อให้ผลที่ถูกต้องสำหรับการตั้งค่าการป้อนข้อมูลที่ว่างเปล่าไม่เป็นศูนย์มันเป็นแถวไม่ได้ทั้งหมด กล่าวอีกนัยหนึ่งการรันข้อความสั่งด้านบนไม่สร้างผลลัพธ์
ความหมายเหล่านี้ง่ายต่อการให้เกียรติมากเมื่อแปลจากนำไปใช้ในการเข้าร่วมเนื่องจากCROSS APPLY
โดยปกติปฏิเสธแถวด้านนอกใด ๆ ที่สร้างไม่มีแถวด้านใน ดังนั้นเราสามารถใช้การเข้าร่วมภายในอย่างปลอดภัยในขณะนี้โดยไม่มีการฉายภาพเพิ่มเติม:
-- Rewrite
SELECT A.*, J1.c
FROM @A AS A
JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
ตัวอย่างด้านล่างแสดงให้เห็นว่าการเขียนการเข้าร่วมภายในนั้นให้ผลลัพธ์เหมือนกับที่ใช้กับการรวมเวกเตอร์:
db <> การสาธิตซอ
เครื่องมือเพิ่มประสิทธิภาพเกิดขึ้นเพื่อเลือกการรวมการผสานภายในกับตารางเล็ก ๆ เนื่องจากพบแผนการเข้าร่วมราคาถูกอย่างรวดเร็ว (พบแผนดีพอ) เครื่องมือเพิ่มประสิทธิภาพตามต้นทุนอาจทำการเขียนการเข้าร่วมกลับไปใช้ใหม่ - อาจค้นหาแผนการใช้ที่ถูกกว่าเพราะจะเกิดขึ้นเมื่อมีการใช้การรวมแบบวนรอบหรือการบังคับใช้คำแนะนำ - แต่ก็ไม่คุ้มค่ากับความพยายามในกรณีนี้
หมายเหตุ
ตัวอย่างง่าย ๆ ใช้ตารางที่แตกต่างกันซึ่งมีเนื้อหาต่างกันเพื่อแสดงความแตกต่างทางความหมายได้ชัดเจนยิ่งขึ้น
หนึ่งอาจโต้แย้งว่าเครื่องมือเพิ่มประสิทธิภาพควรจะสามารถให้เหตุผลเกี่ยวกับการเข้าร่วมด้วยตนเองไม่สามารถสร้างแถวที่ไม่ตรงกัน (ไม่เข้าร่วม) แต่มันไม่ได้มีตรรกะที่วันนี้ การเข้าถึงตารางเดียวกันหลาย ๆ ครั้งในแบบสอบถามไม่รับประกันว่าจะให้ผลลัพธ์เดียวกันโดยทั่วไปอย่างไรก็ตามขึ้นอยู่กับระดับการแยกและกิจกรรมที่เกิดขึ้นพร้อมกัน
เครื่องมือเพิ่มประสิทธิภาพกังวลเกี่ยวกับความหมายและขอบกรณีเหล่านี้ดังนั้นคุณไม่จำเป็นต้อง
โบนัส: แผนการสมัครภายใน
SQL Server สามารถสร้างแผนการใช้ภายใน(ไม่ใช่แผนการเข้าร่วมภายใน!) สำหรับแบบสอบถามตัวอย่างมันเลือกที่จะไม่ทำเพื่อเหตุผลด้านต้นทุน ค่าใช้จ่ายของแผนการเข้าร่วมด้านนอกที่แสดงในคำถามคือ0.02898หน่วยในอินสแตนซ์ SQL Server 2017 ของแล็ปท็อปของฉัน
คุณสามารถบังคับแผนนำไปใช้ (การรวมที่สัมพันธ์กัน) โดยใช้แฟล็กการติดตามที่ไม่มีเอกสารและไม่สนับสนุน 9114 (ซึ่งปิดใช้งานApplyHandler
และอื่น ๆ ) เพื่อแสดงภาพประกอบ:
SELECT *
FROM #MyTable AS mt
CROSS APPLY
(
SELECT COUNT_BIG(DISTINCT mt2.Col_B) AS dc
FROM #MyTable AS mt2
WHERE mt2.Col_A = mt.Col_A
--GROUP BY mt2.Col_A
) AS ca
OPTION (QUERYTRACEON 9114);
สิ่งนี้สร้างแผนลูปที่ซ้อนกันใช้กับสปูลดัชนีขี้เกียจ ค่าใช้จ่ายโดยประมาณทั้งหมดคือ0.0463983 (สูงกว่าแผนที่เลือก):
โปรดทราบว่าแผนการดำเนินการโดยใช้ใช้ลูปซ้อนกันก่อให้เกิดผลลัพธ์ที่ถูกต้องโดยใช้ "ภายในเข้าร่วม" ความหมายโดยไม่คำนึงถึงการปรากฏตัวของGROUP BY
ประโยค
ในโลกแห่งความเป็นจริงเรามักจะมีดัชนีเพื่อรองรับการค้นหาด้านในของการใช้เพื่อสนับสนุนให้ SQL Server เลือกตัวเลือกนี้ตามธรรมชาติตัวอย่างเช่น:
CREATE INDEX i ON #MyTable (Col_A, Col_B);
db <> การสาธิตซอ