KHÔNG VÀO KHÔNG EXIST


538

Những truy vấn nào là nhanh hơn?

KHÔNG TỒN TẠI:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

Hoặc KHÔNG VÀO:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

Kế hoạch thực hiện truy vấn cho biết cả hai đều làm điều tương tự. Nếu đó là trường hợp, đó là hình thức đề nghị?

Điều này dựa trên cơ sở dữ liệu NorthWind.

[Biên tập]

Chỉ cần tìm thấy bài viết hữu ích này: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Tôi nghĩ rằng tôi sẽ gắn bó với KHÔNG EXISTS.


3
Bạn đã thử kế hoạch với việc sử dụng một tham gia trái ở đâu là null?
Sabas

1
KHÔNG VÀO KHÔNG CÓ EXISTS không giống nhau. Hãy xem liên kết này để biết sự khác biệt giữa chúng: weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Ameya Gokhale

2
Tôi tự hỏi liệu Cơ sở dữ liệu có khác nhau không, nhưng trong điểm chuẩn mới nhất của tôi so với PostgreSQL, NOT INtruy vấn này : SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)nhanh gấp gần 30 lần NOT EXISTS:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phương Nguyễn


1
@rcdmk Bạn đã kiểm tra ngày trên các câu hỏi?
ilitrite

Câu trả lời:


693

Tôi luôn luôn mặc định NOT EXISTS.

Các kế hoạch thực hiện có thể giống nhau vào lúc này nhưng nếu một trong hai cột được thay đổi trong tương lai để cho phép NULLs các NOT INphiên bản sẽ cần phải làm việc nhiều hơn (ngay cả khi không NULLs đang thực sự hiện diện trong dữ liệu) và ngữ nghĩa của NOT INnếu NULLs hiện tại không chắc là người bạn muốn

Khi không Products.ProductIDhoặc [Order Details].ProductIDcho phép NULLs NOT INsẽ được xử lý giống hệt với truy vấn sau.

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId) 

Kế hoạch chính xác có thể khác nhau nhưng đối với dữ liệu ví dụ của tôi, tôi nhận được như sau.

Cả NULL

Một quan niệm sai lầm khá phổ biến dường như là các truy vấn phụ tương quan luôn "xấu" so với các phép nối. Chúng chắc chắn có thể là khi chúng buộc một kế hoạch các vòng lặp lồng nhau (truy vấn phụ được đánh giá theo từng hàng) nhưng kế hoạch này bao gồm một toán tử logic chống bán tham gia. Các phép nối bán không bị giới hạn trong các vòng lặp lồng nhau nhưng cũng có thể sử dụng hàm băm hoặc hợp nhất (như trong ví dụ này).

/*Not valid syntax but better reflects the plan*/ 
SELECT p.ProductID,
       p.ProductName
FROM   Products p
       LEFT ANTI SEMI JOIN [Order Details] od
         ON p.ProductId = od.ProductId 

Nếu [Order Details].ProductIDcó thể NULLtruy vấn thì trở thành

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL) 

Lý do cho điều này là ngữ nghĩa chính xác nếu [Order Details]chứa bất kỳ NULL ProductIds nào là không trả về kết quả. Xem thêm bộ đệm chống bán tham gia và số đếm hàng để xác minh điều này được thêm vào kế hoạch.

Một NULL

Nếu Products.ProductIDcũng được thay đổi để trở thành có thể NULLtruy vấn thì trở thành

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL)
       AND NOT EXISTS (SELECT *
                       FROM   (SELECT TOP 1 *
                               FROM   [Order Details]) S
                       WHERE  p.ProductID IS NULL) 

Lý do cho điều đó là vì NULL Products.ProductIdkhông nên trả về kết quả trừ khi NOT INtruy vấn phụ không trả về kết quả nào cả (tức là [Order Details]bảng trống). Trong trường hợp đó nên. Trong kế hoạch cho dữ liệu mẫu của tôi, điều này được thực hiện bằng cách thêm một liên kết chống bán như dưới đây.

Cả hai

Hiệu quả của việc này được thể hiện trong bài đăng trên blog đã được liên kết bởi Buckley . Trong ví dụ này, số lần đọc logic tăng từ khoảng 400 đến 500.000.

Ngoài ra, thực tế là một đơn NULLcó thể giảm số hàng xuống 0 khiến việc ước tính cardinality rất khó khăn. Nếu SQL Server giả định rằng điều này sẽ xảy ra nhưng thực tế không có NULLhàng nào trong dữ liệu thì phần còn lại của kế hoạch thực hiện có thể tồi tệ hơn, nếu đây chỉ là một phần của truy vấn lớn hơn, với các vòng lặp lồng nhau không phù hợp gây ra việc thực hiện lặp đi lặp lại của một phụ đắt tiền cây chẳng hạn .

Đây không phải là duy nhất kế hoạch thực hiện có thể cho một NOT INtrên NULLtuy nhiên cột -able. Bài viết này cho thấy một cái khác cho một truy vấn đối với AdventureWorks2008cơ sở dữ liệu.

Đối với NOT INtrên một NOT NULLcột hoặc NOT EXISTSchống lại hoặc là một cột nullable nullable hoặc không nó mang lại cho những kế hoạch tiếp theo.

Không tồn tại

Khi cột thay đổi thành NULL-able, NOT INkế hoạch bây giờ trông giống như

Không trong - Không

Nó thêm một toán tử tham gia bên trong thêm vào kế hoạch. Bộ máy này được giải thích ở đây . Đó là tất cả để chuyển đổi chỉ số tương quan duy nhất trước đó tìm kiếm Sales.SalesOrderDetail.ProductID = <correlated_product_id>thành hai lần tìm kiếm trên mỗi hàng ngoài. Một bổ sung là trên WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Vì đây là dưới một tham gia chống bán nếu người đó trả về bất kỳ hàng nào, việc tìm kiếm thứ hai sẽ không xảy ra. Tuy nhiên, nếu Sales.SalesOrderDetailkhông chứa bất kỳ NULL ProductIDs, nó sẽ tăng gấp đôi số lượng hoạt động tìm kiếm cần thiết.


4
Tôi có thể hỏi làm thế nào bạn có được biểu đồ hồ sơ như được hiển thị?
xis

5
@xis Đây là các kế hoạch thực hiện được mở trong trình thám hiểm kế hoạch SQL Sentry. Bạn cũng có thể xem các kế hoạch thực hiện bằng đồ họa trong SSMS.
Martin Smith

Tôi đánh giá cao điều này vì lý do duy nhất là: NOT EXISTShoạt động theo cách tôi mong đợi NOT INđể hoạt động (mà, nó không hoạt động).
levininja

Với KHÔNG EXISTS, tôi cố gắng sử dụng CHỌN 1 như KHÔNG EXISTS (CHỌN 1 TỪ đôi khi có gì đó) để cơ sở dữ liệu không thực sự cần phải trả về các cột từ đĩa. Sử dụng EXPLAIN để xác định xem điều này có tạo ra sự khác biệt trong trường hợp của bạn hay không có lẽ là một ý tưởng tốt.
Mayur Patel

4
@Mayur Không cần điều này trong SQL Server. stackoverflow.com/questions/1597442/ trộm
Martin Smith

84

Ngoài ra, hãy lưu ý rằng KHÔNG IN không tương đương với KHÔNG EXISTS khi nói đến null.

Bài đăng này giải thích nó rất tốt

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

Khi truy vấn con trả về ngay cả một null, KHÔNG IN sẽ không khớp với bất kỳ hàng nào.

Lý do cho điều này có thể được tìm thấy bằng cách xem xét các chi tiết về ý nghĩa của hoạt động KHÔNG IN.

Giả sử, với mục đích minh họa rằng có 4 hàng trong bảng được gọi là t, có một cột được gọi là ID với các giá trị 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

tương đương với

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

Chúng ta hãy nói thêm rằng AVal là NULL trong đó ID = 4. Do đó! = So sánh trả về UNKNOWN. Bảng chân lý logic cho AND nói rằng UNKNOWN và TRUE là UNKNOWN, UNKNOWN và FALSE là FALSE. Không có giá trị nào có thể AND với UNKNOWN để tạo ra kết quả TRUE

Do đó, nếu bất kỳ hàng nào của truy vấn con đó trả về NULL, toàn bộ toán tử NOT IN sẽ đánh giá thành FALSE hoặc NULL và không có bản ghi nào được trả về


24

Nếu người lập kế hoạch thực hiện nói rằng họ giống nhau, thì họ giống nhau. Sử dụng cái nào sẽ làm cho ý định của bạn rõ ràng hơn - trong trường hợp này, cái thứ hai.


3
thời gian thực hiện kế hoạch có thể giống nhau nhưng kết quả thực hiện có thể khác nhau do đó có sự khác biệt. KHÔNG IN sẽ tạo ra kết quả bất ngờ nếu bạn có NULL trong tập dữ liệu của mình (xem câu trả lời của buckley). Tốt nhất để sử dụng KHÔNG EXISTS làm mặc định.
nanonerd

15

Trên thực tế, tôi tin rằng đây sẽ là nhanh nhất:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null

2
Có thể không phải là nhanh nhất khi trình tối ưu hóa thực hiện công việc của nó, nhưng chắc chắn sẽ nhanh hơn khi không.
Cade Roux

2
Anh ta có thể đã đơn giản hóa truy vấn của mình cho bài đăng này
Kip

1
Đồng ý Tham gia bên ngoài bên trái thường nhanh hơn truy vấn con.
HLGEM

7
@HLGEM Không đồng ý. Theo kinh nghiệm của tôi, trường hợp tốt nhất cho LOJ là chúng giống nhau và SQL Server chuyển đổi LOJ thành chống tham gia. Trong trường hợp xấu nhất, SQL Server LEFT THAM GIA mọi thứ và lọc các NULL sau đó có thể không hiệu quả hơn nhiều. Ví dụ về điều đó ở dưới cùng của bài viết này
Martin Smith

12

Tôi có một bảng có khoảng 120.000 bản ghi và chỉ cần chọn những bảng không tồn tại (khớp với cột varchar) trong bốn bảng khác với số lượng hàng khoảng 1500, 4000, 40000, 200. Tất cả các bảng có liên quan đều có chỉ mục duy nhất trên Varcharcột liên quan .

NOT INmất khoảng 10 phút, NOT EXISTSmất 4 giây.

Tôi có một truy vấn đệ quy có thể có một số phần chưa được xử lý có thể đã đóng góp trong 10 phút, nhưng tùy chọn khác mất 4 giây giải thích, ít nhất là với tôi NOT EXISTStốt hơn hoặc ít nhất là như vậy INEXISTSkhông hoàn toàn giống nhau và luôn luôn có giá trị kiểm tra trước khi đi trước với mã.


8

Trong ví dụ cụ thể của bạn, chúng giống nhau, bởi vì trình tối ưu hóa đã tìm ra những gì bạn đang cố gắng làm giống nhau trong cả hai ví dụ. Nhưng có thể trong các ví dụ không tầm thường, trình tối ưu hóa có thể không làm điều này, và trong trường hợp đó, có những lý do để thích cái này hơn cái kia.

NOT INnên được ưu tiên nếu bạn đang kiểm tra nhiều hàng trong lựa chọn bên ngoài của bạn. Truy vấn con bên trong NOT INcâu lệnh có thể được đánh giá khi bắt đầu thực hiện và bảng tạm thời có thể được kiểm tra theo từng giá trị trong lựa chọn bên ngoài, thay vì chạy lại phần chọn phụ mỗi lần như yêu cầu vớiNOT EXISTS câu lệnh.

Nếu truy vấn con phải tương quan với lựa chọn bên ngoài, thì NOT EXISTScó thể tốt hơn, vì trình tối ưu hóa có thể phát hiện ra sự đơn giản hóa ngăn chặn việc tạo bất kỳ bảng tạm thời nào để thực hiện cùng chức năng.


6

tôi đang sử dụng

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

và thấy rằng nó đã cho kết quả sai (ý tôi là không có kết quả). Như đã có một NULL trong TABLE2.Col1.

Trong khi thay đổi truy vấn thành

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

đã cho tôi kết quả chính xác.

Kể từ đó, tôi đã bắt đầu sử dụng KHÔNG EXISTS mọi lúc mọi nơi.


5

Chúng rất giống nhau nhưng không thực sự giống nhau.

Về hiệu quả, tôi đã thấy tham gia bên trái là câu lệnh null hiệu quả hơn (khi có rất nhiều hàng được chọn)


2

Nếu trình tối ưu hóa nói rằng chúng giống nhau thì hãy xem xét yếu tố con người. Tôi thích xem KHÔNG EXISTS :)


1

Đây là một câu hỏi rất hay, vì vậy tôi quyết định viết một bài viết rất chi tiết về chủ đề này trên blog của mình.

Mô hình bảng cơ sở dữ liệu

Giả sử chúng ta có hai bảng sau trong cơ sở dữ liệu của mình, tạo thành mối quan hệ một-nhiều bảng.

Bảng SQL EXISTS

Các student bảng là phụ huynh, vàstudent_grade là bảng con vì nó có một cột nước ngoài chính student_id tham khảo id cột khóa chính trong bảng học sinh.

Các student table chứa hai bản ghi sau:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

student_grade bảng lưu trữ các lớp học sinh nhận được:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

EXISTS SQL

Giả sử chúng tôi muốn có được tất cả các học sinh đã nhận được một lớp 10 trong lớp Toán.

Nếu chúng ta chỉ quan tâm đến định danh sinh viên, thì chúng ta có thể chạy một truy vấn như thế này:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Nhưng, ứng dụng quan tâm đến việc hiển thị tên đầy đủ của a student, không chỉ là mã định danh, vì vậy chúng tôi cũng cần thông tin từ studentbảng.

Để lọc các studentbản ghi có điểm 10 môn Toán, chúng ta có thể sử dụng toán tử EXISTS SQL, như sau:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Khi chạy truy vấn trên, chúng ta có thể thấy rằng chỉ có hàng Alice được chọn:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Truy vấn bên ngoài chọn các studentcột hàng chúng tôi quan tâm để trả về máy khách. Tuy nhiên, mệnh đề WHERE đang sử dụng toán tử EXISTS với truy vấn con bên trong có liên quan.

Toán tử EXISTS trả về true nếu truy vấn con trả về ít nhất một bản ghi và false nếu không có hàng nào được chọn. Công cụ cơ sở dữ liệu không phải chạy hoàn toàn truy vấn con. Nếu một bản ghi được khớp, toán tử EXISTS trả về true và hàng truy vấn khác được liên kết được chọn.

Truy vấn con bên trong có tương quan vì cột student_id của student_gradebảng được khớp với cột id của bảng sinh viên bên ngoài.

SQL KHÔNG EXIST

Hãy xem xét chúng tôi muốn chọn tất cả các học sinh không có điểm nào thấp hơn 9. Đối với điều này, chúng tôi có thể sử dụng KHÔNG EXISTS, điều này phủ nhận logic của toán tử EXISTS.

Do đó, toán tử NOT EXISTS trả về true nếu truy vấn con bên dưới không trả về bản ghi. Tuy nhiên, nếu một bản ghi được khớp với truy vấn con bên trong, toán tử NOT EXISTS sẽ trả về false và việc thực hiện truy vấn phụ có thể bị dừng.

Để khớp tất cả các hồ sơ học sinh không có student_THER liên quan với giá trị thấp hơn 9, chúng ta có thể chạy truy vấn SQL sau:

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

Khi chạy truy vấn trên, chúng ta có thể thấy rằng chỉ bản ghi Alice được khớp:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Vì vậy, lợi thế của việc sử dụng các toán tử SQL EXISTS và KHÔNG EXISTS là việc thực thi truy vấn con bên trong có thể được dừng lại miễn là tìm thấy một bản ghi phù hợp.


-1

Nó phụ thuộc ..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

sẽ không tương đối chậm, không giới hạn kích thước của kiểm tra truy vấn để xem liệu chúng có khóa hay không. EXISTS sẽ thích hợp hơn trong trường hợp này.

Nhưng, tùy thuộc vào trình tối ưu hóa của DBMS, điều này có thể không khác.

Như một ví dụ khi EXISTS tốt hơn

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria

1
INEXISTS nhận cùng một kế hoạch trong SQL Server . Câu hỏi là về NOT INvs NOT EXISTSanyway.
Martin Smith
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.