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

38

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 EXISTSbukan EXISTS: Dalam beberapa kesempatan, saya mungkin menulisnya dengan LEFT JOINdan 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 artinya kurang jelas, khususnya ketika apa yang Anda primary_keytidak 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 digunakan, SELECT 1bukan 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 EXISTSklausa?

joanolo
sumber
5
Perhatikan bahwa PostgreSQL mendukung pemilihan tanpa kolom, jadi Anda bisa menulis EXISTS (SELECT FROM ...).
sayap kanan
1
Saya telah mengajukan pertanyaan yang hampir sama pada SO beberapa tahun yang lalu: stackoverflow.com/questions/7710153/…
Erwin Brandstetter

Jawaban:

45

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 sama - tanpa kesalahan (pembagian dengan nol), yang membuktikan bahwa ekspresi di sana bahkan tidak dievaluasi.


Tentang LEFT JOIN / IS NULLmetode antijoin, koreksi: ini setara dengan NOT EXISTS (SELECT ...).

Dalam hal ini, NOT EXISTSvs.LEFT JOIN / IS NULL, Anda mungkin mendapatkan rencana 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 untuk memeriksa rencana dari penulisan ulang ekuivalen yang berbeda karena mungkin ada kasus-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 USINGtidak dapat digunakan di semua DBMS (SQL Server misalnya). Semakin umum JOIN ... ONbekerja di mana-mana.
Dan kolom harus diawali dengan nama tabel / alias di SELECTuntuk menghindari kesalahan / ambiguitas ketika kita telah bergabung.
Saya juga biasanya lebih suka untuk memasukkan kolom yang bergabung dalam IS NULLcek (walaupun PK atau kolom yang tidak dapat dibatalkan akan OK, mungkin berguna untuk efisiensi ketika rencana untuk LEFT JOINmenggunakan indeks non-clustered):

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

ypercubeᵀᴹ
sumber
1
Sampai versi MySQL terbaru [NOT] IN (SELECT ...), meskipun setara, berkinerja sangat buruk. Hindari itu!
Rick James
4
Ini tidak benar untuk PostgreSQL . SELECT *tentu melakukan lebih banyak pekerjaan. Demi kesederhanaan, saya sarankan menggunakanSELECT 1
Evan Carroll
11

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

Saya berbicara tentang kasus di mana Anda perlu memeriksa keberadaan barisan kumpulan yang dikelompokkan . Jika tabel Tmemiliki kolom C1dan C2dan Anda memeriksa keberadaan grup baris yang cocok dengan kondisi tertentu, Anda dapat menggunakan SELECT 1seperti ini:

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

tetapi Anda tidak dapat menggunakan SELECT *dengan cara yang sama.

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

Catatan tambahan mengikuti komentar

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

PostgreSQL adalah salah satu RDBMS yang SELECT *mungkin gagal, tetapi masih dapat berfungsi dalam beberapa kasus. Secara khusus, jika Anda dikelompokkan oleh 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

Andriy M
sumber
1
Poin bagus, meskipun ini bukan masalah yang saya khawatirkan. Yang ini menunjukkan perbedaan konseptual . Karena, ketika Anda GROUP BY, konsep *tidak berarti (atau, setidaknya, tidak begitu jelas).
joanolo
5

Cara yang bisa dibilang menarik untuk menulis ulang EXISTSklausa yang menghasilkan permintaan yang lebih bersih, dan mungkin 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-gabung itu akan terlihat seperti:

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

Keduanya biasanya dioptimalkan untuk rencana yang sama dengan WHERE EXISTSatau WHERE NOT EXISTS, tetapi maksudnya tidak salah lagi, dan Anda tidak memiliki "aneh" 1atau *.

Menariknya, masalah cek nol yang terkait dengannya NOT IN (...)bermasalah untuk <> ALL (...), sedangkan yang NOT EXISTS (...)tidak menderita dari masalah itu. 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 |
+ -------- + ----------- +

The NOT IN (...)query:

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

Memiliki rencana berikut:

masukkan deskripsi gambar di sini

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

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

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

masukkan deskripsi gambar di sini

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

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

Rencana:

masukkan deskripsi gambar di sini

Hasil kueri itu:

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

Ini membuat penggunaan <> ALL (...)sama rentan terhadap hasil bermasalah seperti NOT IN (...).

Max Vernon
sumber
3
Saya harus mengatakan saya tidak merasa *aneh: Saya membaca EXISTS (SELECT * FROM t WHERE ...) SA there is a _row_ in table _t_ that.... Bagaimanapun, saya suka punya alternatif, dan milik Anda jelas bisa dibaca. Satu keraguan / peringatan: bagaimana sikapnya jika bnullable? [Saya punya pengalaman buruk dan beberapa malam pendek ketika mencoba mencari tahu kesalahan yang disebabkan oleh x IN (SELECT something_nullable FROM a_table)]
joanolo
EXISTS memberi tahu Anda apakah tabel memiliki baris & mengembalikan benar atau salah. EXISTS (SELECT x FROM (values ​​(null)) adalah true. IN is = ANY & NOT IN adalah <> ALL. Keempat ini mengambil baris RHS dengan NULL agar mungkin cocok. (X) = ANY (nilai (null)) & (x) <> SEMUA (nilai (nol)) tidak diketahui / null tetapi ADA (nilai (nol)) benar. (IN & = APA PUN memiliki masalah "pemeriksaan nol yang sama dengan yang terkait dengan BUKAN DALAM (...) [& ] <> ALL (...) ". SETIAP & SEMUA iterate ATAU & DAN. Tetapi hanya ada" masalah "jika Anda tidak mengatur semantik seperti yang dimaksudkan.) Jangan menyarankan menggunakan ini untuk EXISTS. Mereka menyesatkan , bukan "kurang menyesatkan"
philipxy
@ philliprxy - Jika saya salah, saya tidak punya masalah mengakuinya. Jangan ragu untuk menambahkan jawaban Anda sendiri jika Anda menginginkannya.
Max Vernon
4

"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 'diperpanjang' 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 ada baiknya membandingkan EXISTSvs anti-bergabung. Versi Anda dapat melakukan pekerjaan yang lebih baik dengan yang satu versus yang lain.

Rick James
sumber
4

Di beberapa database, optimasi ini belum berfungsi. Seperti misalnya di 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 ()

Evan Carroll
sumber
Kasing sudut yang langka, agak aneh mungkin, tapi sekali lagi, membuktikan bahwa Anda harus membuat banyak kompromi saat merancang basis data ...
joanolo
-1

Saya selalu menggunakan select top 1 'x'(SQL Server)

Secara teoritis, select top 1 'x'akan lebih efisien bahwa select *, karena yang pertama akan lengkap setelah memilih konstan pada keberadaan baris kualifikasi, sedangkan yang terakhir akan memilih semuanya.

NAMUN, meskipun sangat awal mungkin relevan, optimisasi telah membuat perbedaan tidak relevan dalam semua RDBS utama.

G DeMasters
sumber
Masuk akal. Itu mungkin (atau mungkin) salah satu dari sedikit kasus di mana top ntanpa order byadalah ide yang baik.
joanolo
3
"Secara teoritis, ...." Tidak, secara teori select top 1 'x'seharusnya tidak lebih efisien daripada select *dalam Existekspresi. Secara praktis mungkin lebih efisien jika optimizer berfungsi suboptimal tetapi secara teoritis kedua ekspresi tersebut setara.
miracle173
-4

IF EXISTS(SELECT TOP(1) 1 FROMadalah kebiasaan jangka panjang dan lintas platform yang lebih baik hanya karena Anda bahkan tidak perlu mulai khawatir tentang seberapa baik atau buruk platform / versi Anda saat ini; dan SQL bergerak dari TOP nmenuju parameterizable TOP(n). Ini harus menjadi keterampilan belajar-sekali.

ajeh
sumber
3
Apa yang Anda maksud dengan "lintas platform" ? TOPbahkan tidak valid SQL.
ypercubeᵀᴹ
"SQL bergerak .." jelas salah. Tidak ada TOP (n)dalam "SQL" - bahasa permintaan standar. Ada satu di T-SQL yang merupakan dialek yang digunakan Microsoft SQL Server.
a_horse_with_no_name
Tag pada pertanyaan awal adalah "SQL Server". Tapi boleh-boleh saja untuk downvote dan membantah apa yang saya katakan - itu tujuan situs ini untuk memungkinkan downvoting mudah. Siapakah saya yang turun hujan di parade Anda dengan memperhatikan detail yang membosankan?
ajeh