EXISTS (CHỌN 1 VÒI) vs EXISTS (CHỌN * Rèn) Người này hay người kia?


37

Bất cứ khi nào tôi cần kiểm tra sự tồn tại của một số hàng trong bảng, tôi có xu hướng viết luôn một điều kiện như:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Một số người khác viết nó như:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Khi điều kiện NOT EXISTSthay vì EXISTS: Trong một số trường hợp, tôi có thể viết nó với một LEFT JOINvà một điều kiện bổ sung (đôi khi được gọi là antijoin ):

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

Tôi cố gắng tránh nó bởi vì tôi nghĩ rằng ý nghĩa không rõ ràng, đặc biệt khi những gì primary_keykhông rõ ràng hoặc khi khóa chính hoặc điều kiện tham gia của bạn là nhiều cột (và bạn có thể dễ dàng quên một trong các cột). Tuy nhiên, đôi khi bạn duy trì mã được viết bởi người khác ... và nó chỉ ở đó.

  1. Có sự khác biệt nào (ngoài phong cách) để sử dụng SELECT 1thay thế SELECT *không?
    Có trường hợp góc nào mà nó không hành xử giống như vậy không?

  2. Mặc dù những gì tôi đã viết là (AFAIK) tiêu chuẩn SQL: Có sự khác biệt như vậy đối với các cơ sở dữ liệu / phiên bản cũ hơn không?

  3. Có bất kỳ lợi thế về việc khám phá viết một antijoin?
    Các nhà hoạch định / tối ưu hóa đương đại có đối xử với nó khác với NOT EXISTSđiều khoản không?


5
Lưu ý rằng PostgreSQL hỗ trợ các lựa chọn không có cột, vì vậy bạn chỉ có thể viết EXISTS (SELECT FROM ...).
đúng

1
Tôi đã hỏi gần như cùng một câu hỏi về SO vài năm trước: stackoverflow.com/questions/7710153/ mẹo
Erwin Brandstetter

Câu trả lời:


45

Không, không có sự khác biệt về hiệu quả giữa (NOT) EXISTS (SELECT 1 ...)(NOT) EXISTS (SELECT * ...)trong tất cả các DBMS chính. Tôi thường thấy (NOT) EXISTS (SELECT NULL ...)được sử dụng là tốt.

Trong một số bạn thậm chí có thể viết (NOT) EXISTS (SELECT 1/0 ...)và kết quả là như nhau - không có bất kỳ lỗi (chia cho 0) nào, điều đó chứng tỏ rằng biểu thức thậm chí không được đánh giá.


Về LEFT JOIN / IS NULLphương pháp antijoin, một sự điều chỉnh: điều này tương đương với NOT EXISTS (SELECT ...).

Trong trường hợp này, NOT EXISTSvsLEFT JOIN / IS NULL, bạn có thể nhận được các kế hoạch thực hiện khác nhau. Trong MySQL chẳng hạn và chủ yếu ở các phiên bản cũ hơn (trước 5.7), các kế hoạch sẽ khá giống nhau nhưng không giống nhau. Các trình tối ưu hóa của DBMS khác (SQL Server, Oracle, Postgres, DB2) là - theo như tôi biết - ít nhiều có khả năng viết lại 2 phương thức này và xem xét cùng một kế hoạch cho cả hai. Tuy nhiên, không có gì đảm bảo như vậy và khi thực hiện tối ưu hóa, tốt nhất là kiểm tra các kế hoạch từ các cách viết tương đương khác nhau vì có thể có trường hợp mỗi trình tối ưu hóa không viết lại (ví dụ: các truy vấn phức tạp, có nhiều phép nối và / hoặc bảng dẫn xuất / truy vấn con bên trong truy vấn con, trong đó các điều kiện từ nhiều bảng, cột tổng hợp được sử dụng trong điều kiện nối) hoặc các lựa chọn và kế hoạch tối ưu hóa bị ảnh hưởng khác nhau bởi các chỉ mục, cài đặt có sẵn, v.v.

Cũng lưu ý rằng USINGkhông thể được sử dụng trong tất cả DBMS (ví dụ: Máy chủ SQL). Các JOIN ... ONcông trình phổ biến hơn ở khắp mọi nơi.
Và các cột cần được thêm tiền tố với tên bảng / bí danh SELECTđể tránh lỗi / sự mơ hồ khi chúng ta tham gia.
Tôi cũng thường thích đặt cột đã tham gia vào IS NULLkiểm tra (mặc dù PK hoặc bất kỳ cột không thể nào có thể ổn, nhưng nó có thể hữu ích cho hiệu quả khi kế hoạch LEFT JOINsử dụng chỉ mục không được phân cụm):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

Ngoài ra còn có một phương pháp thứ ba cho antijoins, sử dụng NOT INnhưng phương pháp này có ngữ nghĩa khác nhau (và kết quả!) Nếu cột của bảng bên trong là null. Nó có thể được sử dụng mặc dù bằng cách loại trừ các hàng với NULL, làm cho truy vấn tương đương với 2 phiên bản trước:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

Điều này cũng thường mang lại các kế hoạch tương tự trong hầu hết các DBMS.


1
Cho đến khi các phiên bản gần đây của MySQL [NOT] IN (SELECT ...), mặc dù tương đương, hoạt động rất kém. Tránh nó ra!
Rick James

3
Điều này không đúng với PostgreSQL . SELECT *chắc chắn là làm nhiều việc hơn Vì đơn giản tôi sẽ khuyên bạn nên sử dụngSELECT 1
Evan Carroll

11

Có một loại trường hợp SELECT 1SELECT *không thể thay thế cho nhau - cụ thể hơn, một trường hợp sẽ luôn được chấp nhận trong những trường hợp đó trong khi các trường hợp khác thì không.

Tôi đang nói về các trường hợp bạn cần kiểm tra sự tồn tại của các hàng của một tập hợp được nhóm . Nếu bảng Tcó các cột C1C2bạn đang kiểm tra sự tồn tại của các nhóm hàng khớp với một điều kiện cụ thể, bạn có thể sử dụng SELECT 1như sau:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

nhưng bạn không thể sử dụng SELECT *theo cùng một cách.

Đó chỉ đơn thuần là một khía cạnh cú pháp. Khi cả hai tùy chọn được chấp nhận về mặt cú pháp, rất có thể bạn sẽ không có sự khác biệt về hiệu suất hoặc kết quả trả về, như đã được giải thích trong câu trả lời khác .

Ghi chú bổ sung sau ý kiến

Dường như không có nhiều sản phẩm cơ sở dữ liệu thực sự hỗ trợ sự khác biệt này. Các sản phẩm như SQL Server, Oracle, MySQL và SQLite sẽ vui vẻ chấp nhận SELECT *trong truy vấn trên mà không có bất kỳ lỗi nào, điều đó có nghĩa là chúng xử lý một EXISTS SELECTtheo cách đặc biệt.

PostgreSQL là một RDBMS SELECT *có thể thất bại, nhưng vẫn có thể hoạt động trong một số trường hợp. Cụ thể, nếu bạn đang nhóm PK, SELECT *sẽ hoạt động tốt, nếu không, nó sẽ thất bại với thông báo:

LRI: cột "T.C2" phải xuất hiện trong mệnh đề GROUP BY hoặc được sử dụng trong hàm tổng hợp


1
Điểm tốt, mặc dù đây không phải là trường hợp tôi quan tâm. Điều này cho thấy một sự khác biệt về khái niệm . Bởi vì, khi bạn GROUP BY, khái niệm về *nó là vô nghĩa (hoặc, ít nhất, không rõ ràng).
joanolo

5

Một cách thú vị để viết lại EXISTSmệnh đề dẫn đến một truy vấn sạch hơn và có lẽ ít gây hiểu lầm hơn, ít nhất là trong SQL Server sẽ là:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

Phiên bản chống bán tham gia sẽ như sau:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

Cả hai thường được tối ưu hóa cho cùng một kế hoạch như WHERE EXISTShoặc WHERE NOT EXISTS, nhưng ý định là không thể nhầm lẫn, và bạn không có "lạ" 1hay *.

Thật thú vị, các vấn đề kiểm tra null liên quan đến NOT IN (...)vấn đề <> ALL (...), trong khi NOT EXISTS (...)đó không gặp phải vấn đề đó. Hãy xem xét hai bảng sau với một cột nullable:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

Chúng tôi sẽ thêm một số dữ liệu cho cả hai, với một số hàng khớp với nhau và một số không phù hợp:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
| ID | Một số giá trị |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
| ID | Một số giá trị |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

Các NOT IN (...)truy vấn:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

Có kế hoạch sau:

nhập mô tả hình ảnh ở đây

Truy vấn trả về không có hàng vì các giá trị NULL làm cho đẳng thức không thể xác nhận.

Truy vấn này, với <> ALL (...)cùng một kế hoạch và không trả về hàng nào:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

nhập mô tả hình ảnh ở đây

Biến thể sử dụng NOT EXISTS (...), hiển thị hình dạng kế hoạch hơi khác và trả về các hàng:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

Kế hoạch:

nhập mô tả hình ảnh ở đây

Kết quả của truy vấn đó:

+ -------- + ----------- +
| ID | Một số giá trị |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

Điều này làm cho việc sử dụng <> ALL (...)chỉ dễ dẫn đến kết quả có vấn đề như NOT IN (...).


3
Tôi phải nói rằng tôi không thấy *lạ: tôi đọc EXISTS (SELECT * FROM t WHERE ...) NHƯ there is a _row_ in table _t_ that.... Dù sao, tôi muốn có những lựa chọn thay thế, và bạn có thể đọc được rõ ràng. Một nghi ngờ / cảnh báo: nó sẽ hành xử thế nào nếu bkhông có giá trị? [Tôi đã có những trải nghiệm tồi tệ và một vài đêm ngắn ngủi khi cố gắng tìm ra một sai lầm gây ra bởi một x IN (SELECT something_nullable FROM a_table)]
joanolo

EXISTS cho bạn biết liệu một bảng có một hàng & trả về đúng hay sai. EXISTS (CHỌN x TỪ (giá trị (null)) là đúng. IN = BẤT K & & KHÔNG VÀO <> TẤT CẢ. 4 người này lấy một hàng RHS với NULL để có thể khớp. (X) = ANY (giá trị (null)) & (x) <> ALL (giá trị (null)) không xác định / null nhưng EXISTS (giá trị (null)) là đúng. (IN & = ANY có cùng "vấn đề kiểm tra null liên quan đến KHÔNG IN (...) [& ] <> TẤT CẢ (...) ". BẤT K & & TẤT CẢ lặp đi lặp lại HOẶC VÀ VÀ. Nhưng chỉ có" vấn đề "nếu bạn không tổ chức ngữ nghĩa như dự định.) Đừng khuyên dùng những điều này cho EXISTS. , không "ít gây hiểu lầm".
philipxy

@philliprxy - Nếu tôi sai, tôi không có vấn đề gì khi thừa nhận điều đó. Hãy thêm câu trả lời của riêng bạn nếu bạn cảm thấy thích nó.
Max Vernon

4

"Bằng chứng" rằng chúng giống hệt nhau (trong MySQL) là phải làm

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

sau đó lặp lại với SELECT 1. Trong cả hai trường hợp, đầu ra 'mở rộng' cho thấy rằng nó đã được chuyển đổi thành SELECT 1.

Tương tự, COUNT(*)được biến thành COUNT(0).

Một điều cần lưu ý: Cải tiến tối ưu hóa đã được thực hiện trong các phiên bản gần đây. Nó có thể là giá trị so sánh EXISTSvới chống tham gia. Phiên bản của bạn có thể làm một công việc tốt hơn với cái này so với cái kia.


4

Trong một số cơ sở dữ liệu, tối ưu hóa này không hoạt động. Giống như ví dụ trong PostgreSQL Kể từ phiên bản 9.6, điều này sẽ thất bại.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Và điều này sẽ thành công.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Thất bại vì những điều sau thất bại nhưng điều đó vẫn có nghĩa là có một sự khác biệt.

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

Bạn có thể tìm thêm thông tin về cách giải quyết cụ thể này và vi phạm thông số kỹ thuật trong câu trả lời của tôi cho câu hỏi, SQL Spec có yêu cầu NHÓM THEO trong EXISTS () không


Một trường hợp góc hiếm hoi, có thể hơi kỳ lạ , nhưng một lần nữa, một bằng chứng chứng minh rằng bạn phải thỏa hiệp khi thiết kế cơ sở dữ liệu ...
joanolo

-1

Tôi đã luôn luôn sử dụng select top 1 'x'(SQL Server)

Về mặt lý thuyết, select top 1 'x'sẽ hiệu quả hơn select *, vì cái trước sẽ hoàn thành sau khi chọn một hằng số về sự tồn tại của một hàng đủ điều kiện, trong khi cái sau sẽ chọn mọi thứ.

TUY NHIÊN, mặc dù rất sớm có thể có liên quan, tối ưu hóa đã tạo ra sự khác biệt không liên quan trong tất cả các RDBS chính.


Có ý nghĩa. Đó có thể là (hoặc có thể là) một trong số rất ít trường hợp top nkhông order bycó ý tưởng hay.
joanolo

3
"Về mặt lý thuyết, ...." Không, về mặt lý thuyết select top 1 'x'không nên hiệu quả hơn select *trong một Existbiểu thức. Thực tế có thể hiệu quả hơn nếu trình tối ưu hóa hoạt động dưới mức tối ưu nhưng về mặt lý thuyết cả hai biểu thức đều tương đương nhau.
phép lạ173

-4

IF EXISTS(SELECT TOP(1) 1 FROMlà một thói quen tốt hơn lâu dài và trên các nền tảng đơn giản chỉ vì bạn không cần phải bắt đầu lo lắng về việc nền tảng / phiên bản hiện tại của bạn tốt hay xấu; và SQL đang chuyển từ TOP nhướng tham số hóa TOP(n). Đây phải là một kỹ năng học một lần.


3
Bạn có ý nghĩa gì với "trên các nền tảng" ? TOPthậm chí không hợp lệ SQL.
ypercubeᵀᴹ

"SQL đang di chuyển .." là hoàn toàn sai. Không có TOP (n)"SQL" - ngôn ngữ truy vấn tiêu chuẩn. Có một trên T-SQL là phương ngữ Microsoft SQL Server đang sử dụng.
a_horse_with_no_name

Thẻ trên câu hỏi ban đầu là "Máy chủ SQL". Nhưng bạn có thể hạ thấp và tranh chấp những gì tôi đã nói - đó là mục đích của trang web này để cho phép hạ cấp dễ dàng. Tôi là ai để mưa trên cuộc diễu hành của bạn với sự chú ý nhàm chán đến từng chi tiết?
ajeh
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.