แนวปฏิบัติที่เหมาะสมระหว่างการใช้ LEFT JOIN หรือ EXISTS


67

มีวิธีปฏิบัติที่ดีที่สุดระหว่างการใช้ LEFT JOIN หรือรูปแบบ NOT EXISTS หรือไม่?

ประโยชน์ที่จะได้รับจากการใช้อย่างใดอย่างหนึ่งคืออะไร?

ถ้าไม่มีควรเลือกแบบไหนดี?

SELECT *
FROM tableA A
LEFT JOIN tableB B
     ON A.idx = B.idx
WHERE B.idx IS NULL

SELECT *
FROM tableA A
WHERE NOT EXISTS
(SELECT idx FROM tableB B WHERE B.idx = A.idx)

ฉันใช้คิวรีภายใน Access กับฐานข้อมูล SQL Server


2
นอกจากวิธีการที่ดูเหมือนWHERE A.idx NOT IN (...) จะไม่เหมือนกันเนื่องจากพฤติกรรม trivalent ของNULL(เช่นNULLไม่เท่ากับNULL(และไม่เท่ากัน) ดังนั้นหากคุณมีใด ๆ NULLในตัวtableBคุณจะได้รับผลลัพธ์ที่ไม่คาดคิด!)
Elaskanator

คำตอบ:


58

ความแตกต่างที่ใหญ่ที่สุดคือไม่ได้อยู่ในการเข้าร่วมเทียบไม่ได้ที่มีอยู่ก็คือ (เขียน) SELECT *ที่

ในตัวอย่างแรกคุณจะได้รับทุกคอลัมน์จากทั้งสอง Aและในขณะที่ในตัวอย่างที่สองคุณจะได้รับเพียงคอลัมน์จากBA

ใน SQL Server ตัวแปรที่สองนั้นเร็วกว่าเล็กน้อยในตัวอย่างที่ง่ายมาก

สร้างตารางตัวอย่างสองตาราง:

CREATE TABLE dbo.A
(
    A_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);

CREATE TABLE dbo.B
(
    B_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);
GO

แทรก 10,000 แถวในแต่ละตาราง:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

ลบทุกแถวที่ 5 ออกจากตารางที่สอง:

DELETE 
FROM dbo.B 
WHERE B_ID % 5 = 1;

SELECT COUNT(*) -- shows 10,000
FROM dbo.A;

SELECT COUNT(*) -- shows  8,000
FROM dbo.B;

ดำเนินการชุดSELECTคำสั่งทดสอบสองชุด:

SELECT *
FROM dbo.A
    LEFT JOIN dbo.B ON A.A_ID = B.B_ID
WHERE B.B_ID IS NULL;

SELECT *
FROM dbo.A
WHERE NOT EXISTS (SELECT 1
    FROM dbo.B
    WHERE b.B_ID = a.A_ID);

แผนการดำเนินการ:

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

ตัวแปรที่สองไม่จำเป็นต้องทำการดำเนินการตัวกรองเนื่องจากสามารถใช้ตัวดำเนินการต่อต้านการรวมกึ่งซ้ายได้


23

เหตุผลพวกเขาเหมือนกัน แต่NOT EXISTSใกล้ AntiSemiJoin ที่คุณขอและเป็นที่ต้องการโดยทั่วไป นอกจากนี้ยังเน้นที่ดีกว่าว่าคุณไม่สามารถเข้าถึงคอลัมน์ใน B ได้เนื่องจากใช้เป็นตัวกรองเท่านั้น

หลายปีที่ผ่านมา (SQL Server 6.0 ish) LEFT JOINนั้นเร็วขึ้น แต่นั่นก็ไม่ได้เกิดขึ้นนานนัก วันNOT EXISTSนี้เร็วขึ้นเล็กน้อย


ผลกระทบที่ยิ่งใหญ่ที่สุดใน Access คือJOINวิธีการนี้จะต้องทำการเชื่อมต่อให้เสร็จสิ้นก่อนที่จะทำการกรองโดยสร้างชุดการรวมในหน่วยความจำ ใช้NOT EXISTSมันตรวจสอบแถว แต่ไม่ได้จัดสรรพื้นที่สำหรับคอลัมน์ นอกจากนี้มันจะหยุดมองเมื่อพบแถว ประสิทธิภาพการทำงานแตกต่างกันเล็กน้อยใน Access แต่กฎทั่วไปของหัวแม่มือนั้นNOT EXISTSมีแนวโน้มที่จะเร็วขึ้นเล็กน้อย ฉันอยากจะบอกว่ามันเป็น "แนวปฏิบัติที่ดีที่สุด" เนื่องจากมีปัจจัยที่เกี่ยวข้องมากกว่า


6

มีข้อยกเว้นที่ฉันพบกับNOT EXISTSถูกกว่า ( แต่เล็กน้อย) ไปLEFT JOIN ... WHERE IS NULLเมื่อใช้เซิร์ฟเวอร์ที่เชื่อมโยง

จากการตรวจสอบแผนการดำเนินการปรากฏว่าNOT EXISTSตัวดำเนินการได้รับการดำเนินการในลักษณะวนซ้ำซ้อน เพราะมันจะถูกดำเนินการบนพื้นฐานต่อแถว (ซึ่งฉันคิดว่าเหมาะสม)

ตัวอย่างแผนการดำเนินการที่แสดงให้เห็นถึงพฤติกรรมนี้: ป้อนคำอธิบายรูปภาพที่นี่


1
เซิร์ฟเวอร์ที่เชื่อมโยงนั้นโหดร้ายสำหรับสิ่งนั้น แนวทางที่เป็นไปได้ในการแก้ปัญหานั้นคือการคัดลอกข้อมูลระยะไกลผ่านลิงค์เซิร์ฟเวอร์ที่เชื่อมโยงโดยใช้วิธีง่าย ๆINSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=yจากนั้นเรียกใช้NOT EXISTS (...)คำสั่งกับสำเนาชั่วคราวของฐานข้อมูลนั้น
Max Vernon

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

1
ไชโย @pimbrouwers - ขอบคุณสำหรับความคิดเห็นของคุณ!
Max Vernon

5

โดยทั่วไปแล้วเครื่องยนต์จะสร้างแผนการดำเนินการตาม:

  1. จำนวนแถวใน A และ B
  2. ไม่ว่าจะมีดัชนีใน A และ / หรือ B
  3. จำนวนแถวผลลัพธ์ที่คาดหวัง (และแถวกลาง)
  4. รูปแบบของแบบสอบถามป้อนข้อมูล (เช่นคำถามของคุณ)

สำหรับ (4):

แผน "ไม่มีอยู่" กระตุ้นให้เกิดแผนค้นหาตามตาราง B นี่เป็นตัวเลือกที่ดีเมื่อตาราง A มีขนาดเล็กและตาราง B มีขนาดใหญ่ (และดัชนีมีอยู่ใน B)

แผน "antijoin" เป็นตัวเลือกที่ดีเมื่อตาราง A มีขนาดใหญ่มากหรือตาราง B มีขนาดเล็กมากหรือไม่มีดัชนีใน B และส่งคืนชุดผลลัพธ์ขนาดใหญ่

อย่างไรก็ตามมันเป็นเพียง "กำลังใจ" เช่นน้ำหนักที่ป้อน ที่แข็งแกร่ง (1), (2), (3) มักจะทำให้ทางเลือกสำหรับ moot (4)

(ไม่สนใจผลกระทบของตัวอย่างของคุณที่ส่งคืนคอลัมน์ที่แตกต่างกันเนื่องจาก *, แก้ไขโดย @MaxVernon answer.)

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