Bagaimana cara kerja pernyataan SQL EXISTS?

89

Saya mencoba untuk belajar SQL dan mengalami kesulitan untuk memahami pernyataan EXISTS. Saya menemukan kutipan tentang "ada" dan tidak memahami sesuatu:

Dengan menggunakan operator existing, subkueri Anda bisa mengembalikan nol, satu, atau banyak baris, dan ketentuannya hanya memeriksa apakah subkueri mengembalikan baris apa pun. Jika Anda melihat klausa pilih dari subkueri, Anda akan melihat bahwa itu terdiri dari satu literal (1); karena kondisi dalam kueri penampung hanya perlu mengetahui berapa banyak baris yang dikembalikan, data aktual yang dikembalikan subkueri tidak relevan.

Yang tidak saya mengerti adalah bagaimana kueri luar mengetahui baris mana yang diperiksa subkueri? Sebagai contoh:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

Saya memahami bahwa jika id dari tabel pemasok dan pesanan cocok, subkueri akan mengembalikan nilai true dan semua kolom dari baris yang cocok di tabel pemasok akan dikeluarkan. Apa yang tidak saya dapatkan adalah bagaimana subquery mengkomunikasikan baris spesifik mana (katakanlah baris dengan supplier id 25) harus dicetak jika hanya benar atau salah yang dikembalikan.

Tampak bagi saya bahwa tidak ada hubungan antara kueri luar dan subkueri.

Dan
sumber

Jawaban:

100

Pikirkan seperti ini:

Untuk 'setiap' baris dari Suppliers, periksa apakah ada 'ada' baris dalam Ordertabel yang memenuhi kondisi Suppliers.supplier_id(ini berasal dari 'baris' kueri luar saat ini) = Orders.supplier_id. Ketika Anda menemukan baris pertama yang cocok, berhenti di situ - WHERE EXISTStelah puas.

Tautan ajaib antara kueri luar dan subkueri terletak pada fakta yang Supplier_idditeruskan dari kueri luar ke subkueri untuk setiap baris yang dievaluasi.

Atau, dengan kata lain, subkueri dijalankan untuk setiap baris tabel dari kueri luar.

Ini TIDAK seperti subkueri dieksekusi secara keseluruhan dan mendapatkan 'benar / salah' dan kemudian mencoba mencocokkan kondisi 'benar / salah' ini dengan kueri luar.

sojin
sumber
7
Terima kasih! "Ini TIDAK seperti subkueri yang dieksekusi secara keseluruhan dan mendapatkan 'benar / salah', lalu mencoba mencocokkan kondisi 'benar / salah' ini dengan permintaan luar." adalah apa yang benar-benar menjelaskannya untuk saya, saya terus berpikir begitulah cara kerja subkueri (dan berkali-kali mereka melakukannya), tetapi apa yang Anda katakan masuk akal karena subkueri bergantung pada kueri luar dan karena itu harus dijalankan sekali per baris
Clarence Liu
32

Tampak bagi saya bahwa tidak ada hubungan antara kueri luar dan subkueri.

Menurut Anda, apa yang dilakukan klausa WHERE di dalam contoh EXISTS? Bagaimana Anda sampai pada kesimpulan itu jika referensi SUPPLIERS tidak ada dalam klausul FROM atau JOIN dalam klausul EXISTS?

EXISTS menilai TRUE / FALSE, dan keluar sebagai TRUE pada kecocokan pertama kriteria - inilah mengapa bisa lebih cepat dari IN. Ketahuilah juga bahwa klausa SELECT di EXISTS diabaikan - YAITU:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

... harus mencapai kesalahan divisi dengan nol, tetapi tidak akan. Klausa WHERE adalah bagian terpenting dari klausa EXISTS.

Ketahuilah juga bahwa JOIN bukanlah pengganti langsung untuk EXISTS, karena akan ada catatan induk duplikat jika ada lebih dari satu catatan anak yang terkait dengan induk.

OMG Ponies
sumber
1
Saya masih melewatkan sesuatu. Jika keluar pada pertandingan pertama, bagaimana hasilnya menjadi semua hasil di mana o.supplierid = s.supplierid? Bukankah itu hanya menampilkan hasil pertama saja?
Dan
3
@Dan: EXISTSKeluar, mengembalikan TRUE pada pertandingan pertama - karena pemasok ada setidaknya satu kali dalam tabel ORDERS. Jika Anda ingin melihat duplikasi data SUPPLIER karena memiliki lebih dari satu hubungan anak di ORDERS, Anda harus menggunakan GABUNG. Tetapi sebagian besar tidak menginginkan duplikasi itu, dan menjalankan GROUP BY / DISTINCT dapat menambah overhead ke kueri. EXISTSlebih efisien daripada SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...di SQL Server, belum pernah diuji di Oracle atau MySQL belakangan ini.
OMG Ponies
Saya punya pertanyaan, apakah pencocokan dilakukan untuk setiap catatan yang DIPILIH di kueri luar. Seperti yang kita ambil dari Pesanan 5 kali jika ada 5 baris yang dipilih dari Pemasok.
Rahul Kadukar
24

Anda dapat menghasilkan hasil yang identik baik menggunakan JOIN, EXISTS, IN, atau INTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o
Anthony Faull
sumber
1
jawaban yang bagus, tetapi juga ingat bahwa lebih baik tidak menggunakan ada untuk menghindari korelasi
Florian Fröhlich
1
Kueri mana yang menurut Anda akan berjalan lebih cepat jika pemasok memiliki 10 juta baris dan pesanan memiliki 100 juta baris dan mengapa?
Teja
7

Jika Anda memiliki klausa where yang terlihat seperti ini:

WHERE id in (25,26,27) -- and so on

Anda dapat dengan mudah memahami mengapa beberapa baris dikembalikan dan beberapa tidak.

Ketika klausa where seperti ini:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

itu hanya berarti: mengembalikan baris yang memiliki catatan yang ada di tabel pesanan dengan id yang sama.

Menahem
sumber
2

Ini adalah pertanyaan yang sangat bagus, jadi saya memutuskan untuk menulis artikel yang sangat rinci tentang topik ini di blog saya.

Model tabel database

Mari kita asumsikan kita memiliki dua tabel berikut dalam database kita, yang membentuk hubungan tabel satu-ke-banyak.

Tabel SQL EXISTS

The studenttabel orangtua, dan student_gradeadalah tabel anak karena memiliki kolom student_id Key Foreign referensi id Primary Key kolom dalam tabel mahasiswa.

The student tableberisi dua catatan berikut:

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

Dan, student_gradetabel tersebut menyimpan nilai yang diterima siswa:

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

SQL SUDAH ADA

Katakanlah kita ingin mendapatkan semua siswa yang telah menerima nilai 10 di kelas Matematika.

Jika kita hanya tertarik pada pengenal siswa, maka kita dapat menjalankan kueri seperti ini:

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

Namun, aplikasi tertarik untuk menampilkan nama lengkap a student, bukan hanya pengenalnya, jadi kami juga membutuhkan info dari studenttabel.

Untuk memfilter studentrecord yang memiliki nilai 10 dalam Matematika, kita dapat menggunakan operator SQL EXISTS, seperti ini:

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

Saat menjalankan query di atas, kita dapat melihat bahwa hanya baris Alice yang dipilih:

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

Kueri luar memilih studentkolom baris yang ingin kami kembalikan ke klien. Namun, klausa WHERE menggunakan operator EXISTS dengan subkueri bagian dalam yang terkait.

Operator EXISTS mengembalikan nilai true jika subkueri mengembalikan setidaknya satu record dan false jika tidak ada baris yang dipilih. Mesin database tidak harus menjalankan subquery seluruhnya. Jika satu rekaman cocok, operator EXISTS mengembalikan nilai true, dan baris kueri terkait lainnya dipilih.

Subkueri bagian dalam berkorelasi karena kolom student_id dari student_grade tabel cocok dengan kolom id dari tabel mahasiswa luar.

Vlad Mihalcea
sumber
Jawaban yang bagus. Saya pikir saya tidak mendapatkan konsep karena saya menggunakan contoh yang salah. Apakah EXISThanya berfungsi dengan subkueri berkorelasi? Saya bermain-main dengan query yang hanya berisi 1 tabel, seperti SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Saya tahu apa yang saya tulis dapat dicapai dengan satu kueri WHERE sederhana tetapi saya hanya menggunakannya untuk memahami ADA. Saya mendapatkan semua baris. Apakah memang karena saya tidak menggunakan subkueri berkorelasi? Terima kasih.
Bowen Liu
Masuk akal hanya untuk subkueri yang berkorelasi karena Anda ingin memfilter rekaman dari kueri luar. Dalam kasus Anda, kueri bagian dalam dapat diganti dengan WHERE TRUE
Vlad Mihalcea
Terima kasih Vlad. Itulah yang saya pikir. Itu hanya ide aneh yang muncul saat aku mengotak-atiknya. Sejujurnya saya tidak tahu konsep subquery berkorelasi. Dan sekarang lebih masuk akal untuk memfilter baris kueri luar dengan kueri dalam.
Bowen Liu
0

EXISTS berarti subkueri mengembalikan setidaknya satu baris, itu benar-benar. Dalam kasus tersebut, ini adalah subkueri yang berkorelasi karena memeriksa supplier_id dari tabel luar ke supplier_id dari tabel dalam. Kueri ini sebenarnya mengatakan:

PILIH semua pemasok Untuk setiap ID pemasok, lihat apakah ada pesanan untuk pemasok ini Jika pemasok tidak ada di tabel pesanan, hapus pemasok dari hasil KEMBALIKAN semua pemasok yang memiliki baris yang sesuai di tabel pesanan

Anda dapat melakukan hal yang sama dalam kasus ini dengan INNER JOIN.

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

Komentar kuda poni benar. Anda perlu melakukan pengelompokan dengan gabungan itu, atau memilih berbeda tergantung pada data yang Anda butuhkan.

David Fells
sumber
4
Gabungan dalam akan menghasilkan hasil yang berbeda dari EXISTS jika lebih dari satu rekaman turunan dikaitkan dengan induk - mereka tidak identik.
OMG Ponies
Saya rasa kebingungan saya mungkin karena saya telah membaca bahwa subquery dengan EXISTS mengembalikan true atau false; tapi ini tidak bisa menjadi satu-satunya hal yang kembali, bukan? Apakah subquery juga mengembalikan semua "pemasok yang memiliki baris yang sesuai di tabel pesanan"? Tetapi jika ya, bagaimana pernyataan EXISTS mengembalikan hasil boolean? Semua yang saya baca di buku teks mengatakan bahwa itu hanya mengembalikan hasil boolean, jadi saya mengalami kesulitan menyesuaikan hasil kode dengan apa yang saya diberitahu bahwa itu kembali.
Dan
Baca EXISTS seperti fungsi ... EXISTS (resultset). Fungsi EXISTS kemudian akan mengembalikan nilai true jika kumpulan hasil memiliki baris, false jika kosong. Itu pada dasarnya.
David Fells
3
@Dan, pertimbangkan bahwa EXISTS () dievaluasi secara logis untuk setiap baris sumber secara independen - ini bukan nilai tunggal untuk seluruh kueri.
Arvo
-1

Apa yang Anda gambarkan adalah apa yang disebut kueri dengan subkueri berkorelasi .

(Secara umum) itu adalah sesuatu yang harus Anda coba hindari dengan menulis kueri dengan menggunakan gabungan sebagai gantinya:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

Karena jika tidak, subkueri akan dieksekusi untuk setiap baris di kueri luar.

Wouter van Nifterick
sumber
2
Kedua solusi tersebut tidak setara. GABUNG memberikan hasil yang berbeda dari subkueri EXISTS jika ada lebih dari satu baris ordersyang cocok dengan kondisi penggabungan.
a_horse_with_no_name
1
terima kasih atas solusi alternatifnya. tetapi apakah Anda menyarankan bahwa jika diberi opsi antara subkueri berkorelasi dan bergabung, saya harus menggunakan bergabung karena lebih efisien?
sunny_dev