it-swarm.asia

EXISTS (SELECT 1 ...) vs EXISTS (SELECT * ...) Satu atau yang lain?

Setiap kali saya perlu memeriksa keberadaan beberapa baris dalam sebuah tabel, saya cenderung selalu menulis kondisi seperti:

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
       )

Beberapa orang lain menulisnya seperti:

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
       )

Ketika kondisinya NOT EXISTS bukannya EXISTS: Dalam beberapa kesempatan, saya mungkin menulisnya dengan LEFT JOIN dan kondisi tambahan (kadang-kadang disebut 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

Saya mencoba menghindarinya karena saya pikir maknanya kurang jelas, terutama ketika apa yang Anda primary_key tidak begitu jelas, atau ketika kunci utama Anda atau kondisi gabungan Anda adalah multi-kolom (dan Anda dapat dengan mudah melupakan salah satu kolom). Namun, kadang-kadang Anda mempertahankan kode yang ditulis oleh orang lain ... dan itu ada di sana.

  1. Apakah ada perbedaan (selain gaya) untuk menggunakan SELECT 1 dari pada SELECT *?
    Apakah ada sudut yang tidak berperilaku sama?

  2. Meskipun apa yang saya tulis adalah (AFAIK) SQL standar: Apakah ada perbedaan untuk database yang berbeda/versi yang lebih lama?

  3. Apakah ada keuntungan dari kejujuran menulis antijoin?
    Apakah perencana/pengoptimal kontemporer memperlakukannya secara berbeda dari NOT EXISTS klausa?

42
joanolo

Tidak, tidak ada perbedaan efisiensi antara (NOT) EXISTS (SELECT 1 ...) Dan (NOT) EXISTS (SELECT * ...) Di semua DBMS utama. Saya sering melihat (NOT) EXISTS (SELECT NULL ...) Digunakan juga.

Dalam beberapa Anda bahkan dapat menulis (NOT) EXISTS (SELECT 1/0 ...) Dan hasilnya adalah sama - tanpa kesalahan (pembagian dengan nol), yang membuktikan bahwa ekspresi di sana bahkan tidak dievaluasi.


Tentang metode antijoin LEFT JOIN / IS NULL, Koreksi: ini setara dengan NOT EXISTS (SELECT ...).

Dalam hal ini, NOT EXISTS Vs LEFT JOIN / IS NULL, Anda mungkin mendapatkan paket eksekusi yang berbeda. Dalam MySQL misalnya dan sebagian besar dalam versi yang lebih lama (sebelum 5.7) rencana akan sangat mirip tetapi tidak identik. Pengoptimal dari DBMS lain (SQL Server, Oracle, Postgres, DB2) adalah - sejauh yang saya tahu - lebih atau kurang mampu menulis ulang 2 metode ini dan mempertimbangkan rencana yang sama untuk keduanya. Namun, tidak ada jaminan seperti itu dan ketika melakukan optimasi, ada baiknya memeriksa rencana dari penulisan ulang ekuivalen yang berbeda karena mungkin ada kasus yang tidak ditulis ulang oleh setiap pengoptimal (mis. Kueri kompleks, dengan banyak gabungan dan/atau tabel turunan/subqueries di dalam subquery, di mana kondisi dari beberapa tabel, kolom komposit yang digunakan dalam kondisi penggabungan) atau pilihan dan rencana pengoptimal dipengaruhi secara berbeda oleh indeks, pengaturan, dll.

Perhatikan juga bahwa USING tidak dapat digunakan di semua DBMS (SQL Server misalnya). JOIN ... ON Yang lebih umum berfungsi di mana-mana.
Dan kolom harus diawali dengan nama tabel/alias di SELECT untuk menghindari kesalahan/ambiguitas ketika kita telah bergabung.
Saya juga biasanya lebih suka untuk memasukkan kolom yang bergabung dalam cek IS NULL (Meskipun PK atau kolom yang tidak dapat dibatalkan akan OK, mungkin berguna untuk efisiensi ketika rencana untuk LEFT JOIN Menggunakan indeks non-cluster):

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 ;

Ada juga metode ketiga untuk antijoin, menggunakan NOT IN Tetapi ini memiliki semantik yang berbeda (dan hasilnya!) Jika kolom tabel di dalam nullable. Itu dapat digunakan meskipun dengan mengecualikan baris dengan NULL, membuat kueri setara dengan 2 versi sebelumnya:

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

Ini juga biasanya menghasilkan rencana serupa di sebagian besar DBMS.

47
ypercubeᵀᴹ

Ada satu kategori kasus di mana SELECT 1 dan SELECT * tidak dapat dipertukarkan - lebih khusus, yang satu akan selalu diterima dalam kasus-kasus itu sementara yang lain sebagian besar tidak akan bisa dipertukarkan.

Saya berbicara tentang kasus di mana Anda perlu memeriksa keberadaan baris set yang dikelompokkan . Jika tabel T memiliki kolom C1 dan C2 dan Anda memeriksa keberadaan grup baris yang cocok dengan kondisi tertentu, Anda dapat menggunakan SELECT 1 seperti ini:

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

tetapi Anda tidak dapat menggunakan SELECT * di jalan yang sama.

Itu hanyalah aspek sintaksis. Jika kedua opsi diterima secara sintaksis, Anda kemungkinan besar tidak akan memiliki perbedaan dalam hal kinerja atau hasil yang dikembalikan, seperti yang telah dijelaskan dalam jawaban lain .

Catatan tambahan mengikuti komentar

Tampaknya tidak banyak produk database yang benar-benar mendukung perbedaan ini. Produk-produk seperti SQL Server, Oracle, MySQL dan SQLite akan dengan senang hati menerima SELECT * dalam kueri di atas tanpa kesalahan, yang mungkin berarti mereka memperlakukan EXISTS SELECT dengan cara khusus.

PostgreSQL adalah satu RDBMS tempat SELECT * mungkin gagal, tetapi mungkin masih berfungsi dalam beberapa kasus. Khususnya, jika Anda dikelompokkan berdasarkan PK, SELECT * akan berfungsi dengan baik, jika tidak maka akan gagal dengan pesan:

GALAT: kolom "T.C2" harus muncul dalam klausa GROUP BY atau digunakan dalam fungsi agregat

11
Andriy M

Cara yang bisa dibilang menarik untuk menulis ulang klausa EXISTS yang menghasilkan pembersih, dan mungkin kueri yang kurang menyesatkan, setidaknya dalam SQL Server adalah:

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

Versi anti-semi-join yang akan terlihat seperti:

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

Keduanya biasanya dioptimalkan untuk paket yang sama dengan WHERE EXISTS Atau WHERE NOT EXISTS, Tetapi maksudnya tidak salah lagi, dan Anda tidak memiliki "aneh" 1 Atau *.

Menariknya, masalah pemeriksaan nol yang terkait dengan NOT IN (...) bermasalah untuk <> ALL (...), sedangkan NOT EXISTS (...) tidak mengalami masalah tersebut. Pertimbangkan dua tabel berikut dengan kolom 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
);

Kami akan menambahkan beberapa data ke keduanya, dengan beberapa baris yang cocok, dan beberapa yang tidak:

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 | SomeValue | 
 + -------- + ----------- + 
 | 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 | SomeValue | 
 + -------- + ----------- + 
 | 1 | 1 | 
 | 2 | 2 | 
 | 3 | NULL | 
 | 4 | 4 | 
 + -------- + ----------- +

Permintaan NOT IN (...):

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

Memiliki rencana berikut:

enter image description here

Kueri tidak mengembalikan baris karena nilai NULL membuat kesetaraan tidak mungkin untuk dikonfirmasi.

Kueri ini, dengan <> ALL (...) menunjukkan paket yang sama dan tidak mengembalikan baris:

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

enter image description here

Varian menggunakan NOT EXISTS (...), menunjukkan bentuk rencana yang sedikit berbeda, dan mengembalikan baris:

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

Rencana:

enter image description here

Hasil kueri itu:

+ -------- + ----------- + 
 | ID | SomeValue | 
 + -------- + ----------- + 
 | 3 | 3 | 
 | 4 | NULL | 
 + -------- + ----------- +

Ini menjadikan penggunaan <> ALL (...) sama rentannya dengan hasil yang bermasalah seperti NOT IN (...).

5
Max Vernon

"Bukti" bahwa mereka identik (dalam MySQL) harus dilakukan

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

lalu ulangi dengan SELECT 1. Dalam kedua kasus, output 'extended' menunjukkan bahwa itu diubah menjadi SELECT 1.

Demikian pula, COUNT(*) diubah menjadi COUNT(0).

Hal lain yang perlu diperhatikan: Peningkatan optimasi telah dibuat dalam versi terbaru. Mungkin layak membandingkan EXISTS vs anti-gabung. Versi Anda dapat melakukan pekerjaan yang lebih baik dengan yang satu versus yang lain.

4
Rick James

Dalam beberapa basis data, optimasi ini belum berfungsi. Seperti misalnya pada PostgreSQL Pada versi 9.6, ini akan gagal.

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

Dan ini akan berhasil.

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

Gagal karena yang berikut gagal tetapi itu masih berarti ada perbedaan.

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

Anda dapat menemukan informasi lebih lanjut tentang kekhasan khusus ini dan pelanggaran spesifikasi dalam jawaban saya atas pertanyaan, Apakah SQL Spec memerlukan GROUP BY dalam EXISTS ()

4
Evan Carroll