SQL“ select where not in subquery” ไม่แสดงผลลัพธ์


131

ข้อจำกัดความรับผิดชอบ: ฉันพบปัญหาแล้ว (ฉันคิดว่า) แต่ฉันต้องการเพิ่มปัญหานี้ใน Stack Overflow เนื่องจากฉันไม่สามารถ (หาได้ง่าย) ทุกที่ นอกจากนี้ใครบางคนอาจมีคำตอบที่ดีกว่าฉัน

ฉันมีฐานข้อมูลที่ตารางหนึ่ง "Common" ถูกอ้างอิงโดยตารางอื่น ๆ ฉันต้องการดูว่าระเบียนใดในตาราง Common ถูกทอดทิ้ง (กล่าวคือไม่มีการอ้างอิงจากตารางอื่น ๆ )

ฉันเรียกใช้แบบสอบถามนี้:

select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)

ฉันรู้ว่ามีบันทึกที่ถูกทอดทิ้ง แต่ไม่มีการส่งคืนบันทึกใด ๆ ทำไมจะไม่ล่ะ?

(นี่คือ SQL Server ถ้าเป็นเรื่องสำคัญ)


stackoverflow.com/a/129152/1667619นี้ตอบคำถามว่าทำไมได้ดี
Ruchan

คำตอบ:


235

ปรับปรุง:

บทความเหล่านี้ในบล็อกของฉันอธิบายความแตกต่างระหว่างวิธีการโดยละเอียดเพิ่มเติม:


มีสามวิธีในการค้นหาดังกล่าว:

  • LEFT JOIN / IS NULL:

    SELECT  *
    FROM    common
    LEFT JOIN
            table1 t1
    ON      t1.common_id = common.common_id
    WHERE   t1.common_id IS NULL
  • NOT EXISTS:

    SELECT  *
    FROM    common
    WHERE   NOT EXISTS
            (
            SELECT  NULL
            FROM    table1 t1
            WHERE   t1.common_id = common.common_id
            )
  • NOT IN:

    SELECT  *
    FROM    common
    WHERE   common_id NOT IN
            (
            SELECT  common_id
            FROM    table1 t1
            )

เมื่อtable1.common_idใดที่ไม่สามารถเป็นโมฆะได้แบบสอบถามเหล่านี้ทั้งหมดจะเหมือนกันอย่างมีความหมาย

เมื่อเป็นโมฆะNOT INจะแตกต่างกันเนื่องจากIN(และดังนั้นNOT IN) จะส่งคืนNULLเมื่อค่าไม่ตรงกับสิ่งใด ๆ ในรายการที่มี a NULL.

สิ่งนี้อาจทำให้สับสน แต่อาจชัดเจนมากขึ้นหากเราจำไวยากรณ์ทางเลือกสำหรับสิ่งนี้:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

ผลลัพธ์ของเงื่อนไขนี้เป็นผลคูณบูลีนของการเปรียบเทียบทั้งหมดภายในรายการ แน่นอนว่าNULLค่าเดียวให้NULLผลลัพธ์ซึ่งแสดงผลลัพธ์ทั้งหมดNULLด้วย

เราไม่เคยไม่สามารถพูดได้แน่นอนว่าจะไม่เท่ากับอะไรจากรายการนี้อย่างน้อยตั้งแต่ค่าใดค่าหนึ่งคือcommon_idNULL

สมมติว่าเรามีข้อมูลเหล่านี้:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLและNOT EXISTSจะกลับมา3, NOT INจะกลับไม่มีอะไร (เพราะมันมักจะประเมินผลอย่างใดอย่างหนึ่งFALSEหรือNULL)

ในMySQLในกรณีที่คอลัมน์ไม่ใช่ nullable, LEFT JOIN / IS NULLและNOT INมีนิด ๆ หน่อย ๆ (ร้อยละหลาย) NOT EXISTSที่มีประสิทธิภาพมากกว่า ถ้าคอลัมน์เป็นโมฆะNOT EXISTSจะมีประสิทธิภาพมากที่สุด (อีกครั้งไม่มาก)

ในOracleแบบสอบถามทั้งสามให้ผลแผนเดียวกัน (an ANTI JOIN)

ในSQL Server, NOT IN/ NOT EXISTSมีประสิทธิภาพมากขึ้นเนื่องจากLEFT JOIN / IS NULLไม่สามารถเพิ่มประสิทธิภาพให้กับANTI JOINโดยการเพิ่มประสิทธิภาพของมัน

ในPostgreSQL, LEFT JOIN / IS NULLและNOT EXISTSมีประสิทธิภาพมากขึ้นกว่าNOT INไซน์พวกเขาจะปรับไปยังAnti Joinในขณะที่NOT INการใช้งานhashed subplan(หรือแม้กระทั่งธรรมดาsubplanถ้าแบบสอบถามย่อยที่มีขนาดใหญ่เกินไปที่จะกัญชา)


8
ตอบโจทย์มาก! ขอบคุณ!
StevenMcD

มันยอดเยี่ยมและเป็นประโยชน์มาก
kavun

1
+1 เพราะสี่ปีครึ่งต่อมาคำตอบนี้ช่วยฉันแก้ปัญหาที่ทำให้ฉันนิ่งงัน!
Carson63000

@ Carson63000 งับ! ฉันคิดว่าฉันจะบ้าก่อนที่จะได้เห็นคำตอบนี้
Bobby

1
@IstiaqueAhmed: NOT EXISTSประเมินค่าเป็น TRUE หากแบบสอบถามภายในส่งกลับแถวใด ๆ SELECT NULLอาจเป็นSELECT *หรือSELECT 1หรืออย่างอื่นก็ได้เพรดิเคตNOT EXISTSไม่ได้ดูที่ค่าของแถว แต่จะนับเฉพาะค่าเหล่านี้
Quassnoi

36

หากคุณต้องการให้โลกเป็นสถานที่บูลีนที่มีค่าสองค่าคุณต้องป้องกันกรณีว่าง (ค่าที่สาม) ด้วยตัวคุณเอง

อย่าเขียนคำสั่ง IN ที่อนุญาตให้มีค่าว่างในด้านรายการ กรองออก!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)

6
nulls ใน in-clause-list เป็นสาเหตุทั่วไปที่ทำให้ผลลัพธ์คิวรีหายไป
Amy B

'เมื่อเปรียบเทียบกับค่าว่างจะไม่ทราบคำตอบ' - จากคำตอบของ @Jeremy Stein จากcommon_id not inนี้เรายังสามารถมีค่าที่common_id NULLปัญหาของการไม่ได้ผลลัพธ์ยังคงมีอยู่หรือไม่?
Istiaque Ahmed

5

Table1 หรือ Table2 มีค่า null บางค่าสำหรับ common_id ใช้คำค้นหานี้แทน:

select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)

1
จะเป็นอย่างไรหากมีข้อมูลในตารางเดียว แต่ไม่มีข้อมูลอีกตาราง คุณต้องการ "และ" หรือ "หรือ" ที่นั่นหรือไม่?
Philip Kelley

1
ฉันกำลังมองหาระเบียนที่ไม่ได้อ้างอิงในตารางใด ๆ ดังนั้นฉันจึงต้องการ AND ฉันจะชี้แจงคำถาม
Jeremy Stein

4
select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)

4

เพียงแค่ปิดหัวของฉัน ...

select c.commonID, t1.commonID, t2.commonID
from Common c
     left outer join Table1 t1 on t1.commonID = c.commonID
     left outer join Table2 t2 on t2.commonID = c.commonID
where t1.commonID is null 
     and t2.commonID is null

ฉันทำการทดสอบสองสามครั้งและนี่คือผลลัพธ์ของฉันคำตอบของ wrt @ patmortech และความคิดเห็นของ @ rexem

หาก Table1 หรือ Table2 ไม่ได้ถูกสร้างดัชนีบน commonID ​​คุณจะได้รับการสแกนตาราง แต่การสืบค้นของ @ patmortech ยังคงเร็วกว่าสองเท่า (สำหรับตารางหลัก 100K แถว)

หากไม่มีการจัดทำดัชนีบน commonID ​​คุณจะได้รับการสแกนสองตารางและความแตกต่างนั้นไม่สำคัญ

หากทั้งสองถูกจัดทำดัชนีบน commonID ​​การสืบค้น "ไม่มีอยู่" จะทำงานใน 1/3 ของเวลา


1
นั่นควรจะเป็น AND ในประโยค where มิฉะนั้นจะได้ผล
Jeremy Stein

1
เปลี่ยนแปลงตามความคิดเห็นของคุณ "หรือ" จะเลือกเด็กกำพร้าในตารางอย่างใดอย่างหนึ่ง
Austin Salonen

1
นั่นดีกว่า. อย่างไรก็ตามมีเหตุผลบางอย่างที่ฉันควรใช้การรวมภายนอกแทนการสืบค้นย่อยหรือไม่?
Jeremy Stein

3
ความสามารถในการอ่านเป็นหลัก ฉันสงสัยว่าจะมีการสร้างแผนการดำเนินการที่ดีกว่า แต่หากไม่มีแผนการสืบค้นฉันไม่สามารถยืนยันได้
Austin Salonen

2
วิธีนี้แย่กว่านั้นคือการใช้ NOT EXISTS - ผลการรวมในการดึงข้อมูลแถวมากกว่าที่ต้องการจากนั้นผลลัพธ์เมื่อเทียบกับคอลัมน์ที่เป็นโมฆะ และ NOT EXISTS สามารถอ่านได้มากขึ้นในการบูต
OMG Ponies

3
SELECT T.common_id
  FROM Common T
       LEFT JOIN Table1 T1 ON T.common_id = T1.common_id
       LEFT JOIN Table2 T2 ON T.common_id = T2.common_id
 WHERE T1.common_id IS NULL
   AND T2.common_id IS NULL

1
วิธีนี้แย่กว่านั้นคือการใช้ NOT EXISTS - ผลการรวมในการดึงข้อมูลแถวมากกว่าที่ต้องการจากนั้นผลลัพธ์เมื่อเทียบกับคอลัมน์ที่เป็นโมฆะ มันใช้งานได้ แต่ประสิทธิภาพจะไม่ดีเท่า - อาจแย่กว่านั้นเมื่อใช้ IN กับเคียวรีย่อยที่สัมพันธ์กัน
OMG Ponies

3

สมมติว่าค่าเหล่านี้สำหรับ common_id:

Common - 1
Table1 - 2
Table2 - 3, null

เราต้องการให้แถวใน Common ส่งคืนเนื่องจากไม่มีอยู่ในตารางอื่น ๆ อย่างไรก็ตามโมฆะพ่นด้วยประแจลิง

ด้วยค่าเหล่านี้แบบสอบถามจะเทียบเท่ากับ:

select *
from Common
where 1 not in (2)
and 1 not in (3, null)

ซึ่งเทียบเท่ากับ:

select *
from Common
where not (1=2)
and not (1=3 or 1=null)

นี่คือจุดเริ่มต้นของปัญหา เมื่อเปรียบเทียบกับ null เป็นคำตอบที่ไม่เป็นที่รู้จัก ดังนั้นแบบสอบถามจึงลดเป็น

select *
from Common
where not (false)
and not (false or unkown)

ไม่ทราบเท็จหรือไม่ทราบ:

select *
from Common
where true
and not (unknown)

จริงและไม่เปิดเผยก็ไม่รู้จัก:

select *
from Common
where unknown

เงื่อนไขที่ไม่ส่งคืนเรกคอร์ดที่ผลลัพธ์ไม่เป็นที่รู้จักดังนั้นเราจึงไม่ได้รับการบันทึกกลับ

วิธีหนึ่งในการจัดการกับปัญหานี้คือการใช้ตัวดำเนินการที่มีอยู่แทนที่จะใช้ใน Exists จะไม่ส่งคืน unkown เพราะมันทำงานในแถวแทนที่จะเป็นคอลัมน์ (แถวนั้นมีอยู่หรือไม่มีไม่มีความคลุมเครือเป็นโมฆะนี้ที่ระดับแถว!)

select *
from Common
where not exists (select common_id from Table1 where common_id = Common.common_id)
and not exists (select common_id from Table2 where common_id = Common.common_id)

2

สิ่งนี้ได้ผลสำหรับฉัน :)

เลือก * จาก Common

ที่ไหน

common_id ไม่อยู่ใน (เลือกISNULL (common_id, 'dummy-data')จาก Table1)

และ common_id ไม่อยู่ใน (เลือกISNULL (common_id, 'dummy-data')จาก Table2)


@marlar แบบสอบถามย่อยจะส่งคืน 1 หรือ 0 เสมอไม่ใช่รายการค่า แล้วการNOT INแสดงที่นั่นจะเป็นอย่างไร?
Istiaque Ahmed


0

ฉันมีตัวอย่างที่ฉันค้นหาและเนื่องจากตารางหนึ่งมีค่าเป็นสองเท่าอีกตารางหนึ่งเป็นสตริงจึงไม่ตรงกัน (หรือไม่จับคู่โดยไม่มีการโยน) แต่ไม่ได้อยู่ใน เนื่องจากSELECT ... IN ...ทำงาน แปลก แต่คิดว่าฉันจะแบ่งปันในกรณีที่มีใครพบวิธีแก้ไขง่ายๆนี้


0

โปรดปฏิบัติตามตัวอย่างด้านล่างเพื่อทำความเข้าใจหัวข้อข้างต้น:

นอกจากนี้คุณสามารถเยี่ยมชมลิงค์ต่อไปนี้เพื่อทราบAnti join

select department_name,department_id from hr.departments dep
where not exists 
    (select 1 from hr.employees emp
    where emp.department_id=dep.department_id
    )
order by dep.department_name;
DEPARTMENT_NAME DEPARTMENT_ID
Benefits    160
Construction    180
Contracting 190
.......

แต่ถ้าเราใช้NOT INในกรณีนั้นเราจะไม่ได้รับข้อมูลใด ๆ

select Department_name,department_id from hr.departments dep 
where department_id not in (select department_id from hr.employees );

ไม่พบข้อมูล

สิ่งนี้เกิดขึ้นเมื่อ ( select department_id from hr.employees) ส่งคืนค่าว่างและแบบสอบถามทั้งหมดถูกประเมินว่าเป็นเท็จ เราสามารถเห็นได้หากเราเปลี่ยน SQL เล็กน้อยเช่นด้านล่างและจัดการค่า null ด้วยฟังก์ชัน NVL

select Department_name,department_id from hr.departments dep 
where department_id not in (select NVL(department_id,0) from hr.employees )

ตอนนี้เรากำลังรับข้อมูล:

DEPARTMENT_NAME DEPARTMENT_ID
Treasury    120
Corporate Tax   130
Control And Credit  140
Shareholder Services    150
Benefits    160
....

อีกครั้งที่เราได้รับข้อมูลเมื่อเราจัดการค่า null ด้วยฟังก์ชัน NVL


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