SQL chọn ở nơi không trong truy vấn con không trả về kết quả


130

Tuyên bố miễn trừ trách nhiệm: Tôi đã tìm ra vấn đề (tôi nghĩ), nhưng tôi muốn thêm vấn đề này vào Stack Overflow vì tôi không thể (dễ dàng) tìm thấy nó ở bất cứ đâu. Ngoài ra, ai đó có thể có một câu trả lời tốt hơn tôi.

Tôi có một cơ sở dữ liệu trong đó một bảng "Chung" được tham chiếu bởi một số bảng khác. Tôi muốn xem những bản ghi nào trong bảng Chung đã mồ côi (nghĩa là không có tài liệu tham khảo từ bất kỳ bảng nào khác).

Tôi đã chạy truy vấn này:

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

Tôi biết rằng có những hồ sơ mồ côi, nhưng không có hồ sơ nào được trả lại. Tại sao không?

(Đây là SQL Server, nếu có vấn đề.)


Điều này stackoverflow.com/a/129152/1667619 trả lời câu hỏi TẠI SAO khá tốt.
Ruchan

Câu trả lời:


234

Cập nhật:

Những bài viết trong blog của tôi mô tả sự khác biệt giữa các phương pháp chi tiết hơn:


Có ba cách để thực hiện một truy vấn như vậy:

  • 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
            )

Khi table1.common_idkhông thể rỗng, tất cả các truy vấn này đều giống nhau về mặt ngữ nghĩa.

Khi nó là null, NOT INthì khác, vì IN(và, do đó, NOT IN) trả về NULLkhi một giá trị không khớp với bất cứ thứ gì trong danh sách chứa a NULL.

Điều này có thể gây nhầm lẫn nhưng có thể trở nên rõ ràng hơn nếu chúng ta nhớ lại cú pháp thay thế cho điều này:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

Kết quả của điều kiện này là một sản phẩm boolean của tất cả các so sánh trong danh sách. Tất nhiên, một NULLgiá trị duy nhất mang lại NULLkết quả cũng thể hiện toàn bộ kết quả NULL.

Chúng tôi không bao giờ không thể nói chắc chắn rằng common_idnó không bằng bất cứ thứ gì trong danh sách này, vì ít nhất một trong các giá trị là NULL.

Giả sử chúng ta có những dữ liệu này:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLNOT EXISTSsẽ trở lại 3, NOT INsẽ trở lại không có gì (vì nó sẽ luôn luôn đánh giá cho một trong hai FALSEhoặc NULL).

Trong MySQLtrường hợp trên cột không nullable LEFT JOIN / IS NULLNOT INhiệu quả hơn một chút (vài phần trăm) so với NOT EXISTS. Nếu cột là nullable, NOT EXISTSlà hiệu quả nhất (một lần nữa, không nhiều).

Trong Oracle, cả ba truy vấn đều có cùng một kế hoạch (an ANTI JOIN).

Trong SQL Server, NOT IN/ NOT EXISTSlà hiệu quả hơn, vì LEFT JOIN / IS NULLkhông thể được tối ưu hóa thành một ANTI JOINbởi trình tối ưu hóa của nó.

Trong PostgreSQL, LEFT JOIN / IS NULLNOT EXISTScó hiệu quả hơn NOT IN, sin họ được tối ưu hóa để một Anti Join, trong khi NOT INsử dụng hashed subplan(hoặc thậm chí là một đồng bằng subplannếu subquery là quá lớn để băm)


8
Câu trả lời chính xác! Cảm ơn!
StevenMcD

điều này thật tuyệt vời và rất hữu ích
kavun

1
+1 bởi vì, bốn năm rưỡi sau, câu trả lời này đã giúp tôi giải quyết vấn đề khiến tôi bối rối!
Carson63000

@ Carson63000 Chụp! Tôi nghĩ rằng tôi đã phát điên trước khi tôi thấy câu trả lời này
Bobby

1
@IstiaqueAhmed: ước tính NOT EXISTSthành TRUE nếu truy vấn bên trong nó trả về bất kỳ hàng nào. SELECT NULLcũng có thể SELECT *hoặc SELECT 1bất cứ điều gì khác, NOT EXISTSvị ngữ không nhìn vào các giá trị của các hàng, chỉ đếm chúng.
Quassnoi

36

Nếu bạn muốn thế giới trở thành một nơi boolean hai giá trị, bạn phải tự mình ngăn chặn trường hợp null (giá trị thứ ba).

Đừng viết các mệnh đề IN cho phép null trong danh sách. Lọc chúng ra!

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

6
null trong danh sách trong mệnh đề là một lý do phổ biến cho kết quả truy vấn bị thiếu.
Amy B

'Khi so sánh với null, câu trả lời là không xác định' - từ câu trả lời của @Jeremy Stein. Từ common_id not in, chúng ta vẫn có thể có common_idgiá trị đó là NULL. Vì vậy, vấn đề không nhận được kết quả vẫn còn tồn tại?
Istiaque Ahmed

5

Bảng 1 hoặc Bảng2 có một số giá trị null cho common_id. Sử dụng truy vấn này thay thế:

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
Điều gì xảy ra nếu có dữ liệu trong một bảng nhưng không phải bảng khác? Bạn có muốn "và" hoặc "hoặc" ở đó không?
Philip Kelley

1
Tôi đang tìm kiếm các bản ghi không được tham chiếu trong bất kỳ bảng nào, vì vậy tôi muốn VÀ. Tôi sẽ làm rõ câu hỏi.
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

Chỉ cần ra khỏi đỉnh đầu của tôi ...

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

Tôi đã chạy một vài bài kiểm tra và đây là kết quả của tôi với câu trả lời của @ patmortech và nhận xét của @ rexem.

Nếu Table1 hoặc Table2 không được lập chỉ mục trên commonID, bạn có thể quét bảng nhưng truy vấn của @ patmortech vẫn nhanh gấp đôi (đối với bảng chính hàng 100K).

Nếu không được lập chỉ mục trên commonID, bạn sẽ có hai lần quét bảng và sự khác biệt là không đáng kể.

Nếu cả hai được lập chỉ mục trên commonID, truy vấn "không tồn tại" sẽ chạy trong 1/3 thời gian.


1
Đó phải là một AND trong mệnh đề where. Nếu không, điều đó làm việc.
Jeremy Stein

1
thay đổi theo nhận xét của bạn. "Hoặc" chọn ra trẻ mồ côi ở một trong hai bảng.
Austin Salonen

1
Cái đó tốt hơn. Nhân tiện, có một số lý do tôi nên sử dụng tham gia bên ngoài hơn là truy vấn phụ?
Jeremy Stein

3
Khả năng đọc là chính. Tôi nghi ngờ một kế hoạch thực hiện tốt hơn sẽ được tạo ra nhưng không có kế hoạch truy vấn, tôi không thể xác nhận.
Austin Salonen

2
Cách tiếp cận này tệ hơn khi sử dụng KHÔNG EXISTS - kết quả tham gia trong việc tìm nạp nhiều hàng hơn mức cần thiết, sau đó kết quả so với các cột là null. Và KHÔNG EXISTS dễ đọc hơn để khởi động.
Ngựa vằn OMG

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
Cách tiếp cận này tệ hơn khi sử dụng KHÔNG EXISTS - kết quả tham gia trong việc tìm nạp nhiều hàng hơn mức cần thiết, sau đó kết quả so với các cột là null. Nó hoạt động, nhưng hiệu suất sẽ không tốt bằng - có thể tệ hơn khi sử dụng IN với các truy vấn con tương quan.
Ngựa vằn OMG

3

Hãy giả sử những giá trị này cho common_id:

Common - 1
Table1 - 2
Table2 - 3, null

Chúng tôi muốn hàng trong Common quay trở lại, vì nó không tồn tại trong bất kỳ bảng nào khác. Tuy nhiên, null ném trong một cái mỏ lết khỉ.

Với các giá trị đó, truy vấn tương đương với:

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

Điều đó tương đương với:

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

Đây là nơi vấn đề bắt đầu. Khi so sánh với một null, câu trả lời là không rõ . Vì vậy, truy vấn giảm xuống

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

sai hoặc không biết là không rõ:

select *
from Common
where true
and not (unknown)

đúng và không unkown cũng là unkown:

select *
from Common
where unknown

Điều kiện nơi không trả về các bản ghi trong đó kết quả không được xác định, vì vậy chúng tôi không nhận được bản ghi nào.

Một cách để giải quyết vấn đề này là sử dụng toán tử tồn tại chứ không phải trong. Tồn tại không bao giờ trả về không xác định vì nó hoạt động trên các hàng thay vì các cột. (Một hàng tồn tại hoặc không tồn tại; không có sự mơ hồ null nào ở cấp hàng này!)

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

cái này hiệu quả với tôi :)

chọn * từ Chung

Ở đâu

common_id không có trong (chọn ISNULL (common_id, 'dummy-data') từ Bảng1)

và common_id không có trong (chọn ISNULL (common_id, 'dummy-data') từ Bảng2)


@marlar, các truy vấn phụ luôn trả về 1 hoặc 0, không phải là danh sách các giá trị. Vậy làm thế nào để NOT INthực hiện ở đó?
Istiaque Ahmed

0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster

0

Tôi đã có một ví dụ khi tôi đang tìm kiếm và bởi vì một bảng giữ giá trị là gấp đôi, bảng kia là một chuỗi, chúng sẽ không khớp (hoặc không khớp nếu không có biểu diễn). Nhưng chỉ KHÔNG VÀO . Như CHỌN ... VÀO ... đã hoạt động. Lạ, nhưng nghĩ rằng tôi sẽ chia sẻ trong trường hợp bất kỳ ai khác gặp phải sửa chữa đơn giản này.


0

Hãy làm theo ví dụ dưới đây để hiểu chủ đề trên:

Ngoài ra, bạn có thể truy cập liên kết sau để biết Anti tham gia

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
.......

Nhưng nếu chúng ta sử dụng NOT INtrong trường hợp đó, chúng ta sẽ không nhận được bất kỳ dữ liệu nào.

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

không tìm thấy dữ liệu nào

Điều này xảy ra vì ( select department_id from hr.employees) đang trả về giá trị null và toàn bộ truy vấn được đánh giá là sai. Chúng ta có thể thấy nó nếu chúng ta thay đổi SQL một chút như bên dưới và xử lý các giá trị null bằng hàm NVL.

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

Bây giờ chúng tôi đang nhận được dữ liệu:

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

Một lần nữa, chúng tôi nhận được dữ liệu vì chúng tôi đã xử lý giá trị null bằng hàm NVL.


Kết quả SQl không xuất hiện ở dạng bảng, xin vui lòng trần với tôi.
Rajesh Sarkar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.