CROSS ใช้ผลิตการรวมภายนอก


17

เพื่อเป็นการตอบสนองต่อการนับ SQL ที่แตกต่างกันบนพาร์ติชัน Erik Darling โพสต์รหัสนี้เพื่อหลีกเลี่ยงการขาดCOUNT(DISTINCT) OVER ():

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY (   SELECT COUNT(DISTINCT mt2.Col_B) AS dc
                FROM   #MyTable AS mt2
                WHERE  mt2.Col_A = mt.Col_A
                -- GROUP BY mt2.Col_A 
            ) AS ca;

แบบสอบถามใช้CROSS APPLY(ไม่OUTER APPLY) ดังนั้นเหตุใดจึงมีการรวมภายนอกในแผนการดำเนินการแทนการเข้าร่วมภายใน

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

ทำไมการไม่แสดงความคิดเห็นกลุ่มโดยประโยคส่งผลให้เข้าร่วมภายใน?

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

ฉันไม่คิดว่าข้อมูลมีความสำคัญ แต่คัดลอกจาก kevinwhat จากคำถามอื่น:

create table #MyTable (
Col_A varchar(5),
Col_B int
)

insert into #MyTable values ('A',1)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',3)

insert into #MyTable values ('B',4)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',5)

คำตอบ:


23

สรุป

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 (สูงกว่าแผนที่เลือก):

ดัชนี Spool ใช้แผน

โปรดทราบว่าแผนการดำเนินการโดยใช้ใช้ลูปซ้อนกันก่อให้เกิดผลลัพธ์ที่ถูกต้องโดยใช้ "ภายในเข้าร่วม" ความหมายโดยไม่คำนึงถึงการปรากฏตัวของGROUP BYประโยค

ในโลกแห่งความเป็นจริงเรามักจะมีดัชนีเพื่อรองรับการค้นหาด้านในของการใช้เพื่อสนับสนุนให้ SQL Server เลือกตัวเลือกนี้ตามธรรมชาติตัวอย่างเช่น:

CREATE INDEX i ON #MyTable (Col_A, Col_B);

db <> การสาธิตซอ


-3

Cross Apply เป็นการดำเนินการทางตรรกะกับข้อมูล เมื่อตัดสินใจเลือกวิธีรับข้อมูล SQL Server เลือกตัวดำเนินการทางกายภาพที่เหมาะสมเพื่อรับข้อมูลที่คุณต้องการ

ไม่มีตัวดำเนินการใช้ทางกายภาพและ SQL Server แปลเป็นตัวดำเนินการเข้าร่วมที่เหมาะสมและหวังว่าจะมีประสิทธิภาพ

คุณสามารถค้นหารายชื่อผู้ประกอบการทางกายภาพได้จากลิงค์ด้านล่าง

https://docs.microsoft.com/en-us/sql/relational-databases/showplan-logical-and-physical-operators-reference?view=sql-server-2017

เครื่องมือเพิ่มประสิทธิภาพแบบสอบถามสร้างแผนแบบสอบถามเป็นแผนผังที่มีตัวดำเนินการเชิงตรรกะ หลังจากตัวเพิ่มประสิทธิภาพคิวรีสร้างแผนตัวเพิ่มประสิทธิภาพคิวรีจะเลือกตัวดำเนินการทางกายภาพที่มีประสิทธิภาพสูงสุดสำหรับตัวดำเนินการทางตรรกะแต่ละตัว เครื่องมือเพิ่มประสิทธิภาพคิวรีใช้วิธีการตามต้นทุนเพื่อพิจารณาว่าผู้ให้บริการทางกายภาพรายใดจะใช้ตัวดำเนินการเชิงตรรกะ

โดยปกติแล้วการดำเนินการเชิงตรรกะสามารถดำเนินการโดยผู้ให้บริการหลายราย อย่างไรก็ตามในบางกรณีผู้ให้บริการทางกายภาพสามารถใช้การดำเนินการเชิงตรรกะหลายรายการได้เช่นกัน

แก้ไข / ดูเหมือนว่าฉันเข้าใจคำถามของคุณผิด โดยปกติแล้วเซิร์ฟเวอร์ SQL จะเลือกผู้ให้บริการที่เหมาะสมที่สุด แบบสอบถามของคุณไม่จำเป็นต้องส่งคืนค่าสำหรับชุดค่าผสมทั้งหมดของทั้งสองตารางซึ่งจะใช้เมื่อมีการเข้าร่วมแบบไขว้ เพียงแค่คำนวณค่าที่คุณต้องการสำหรับความพอเพียงของแต่ละแถวซึ่งเป็นสิ่งที่ทำที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.