Bergabunglah dengan sub-kueri

837

Saya adalah pengguna MySQL jadul dan selalu lebih disukai JOINdaripada sub-kueri. Tetapi saat ini semua orang menggunakan sub-kueri, dan saya benci; Saya tidak tahu kenapa.

Saya tidak memiliki pengetahuan teoretis untuk menilai sendiri apakah ada perbedaan. Apakah sub-kueri sebagus a JOINdan karena itu tidak ada yang perlu dikhawatirkan?

Akal Sehat Anda
sumber
23
Subquery terkadang bagus. Mereka menyedot kinerja-bijaksana di MySQL. Jangan gunakan itu.
runrig
8
Saya selalu mendapat kesan bahwa sub-query secara implisit dieksekusi sebagai gabungan jika tersedia dalam teknologi DB tertentu.
Kezzer
18
Sub kueri tidak selalu payah, saat bergabung dengan tabel yang cukup besar, cara yang disukai adalah dengan melakukan sub-pilih dari tabel besar itu (membatasi jumlah baris) dan kemudian bergabung.
ovais.tariq
136
"saat ini semua orang menggunakan sub-permintaan" [rujukan?]
Piskvor meninggalkan gedung
3
Terkait secara potensial (walaupun jauh lebih spesifik): stackoverflow.com/questions/141278/subqueries-vs-joins/…
Leigh Brenecki

Jawaban:

191

Diambil dari manual MySQL ( 13.2.10.11 Subqueries Penulisan Ulang sebagai Bergabung ):

LEFT [OUTER] BERGABUNG bisa lebih cepat daripada subquery yang setara karena server mungkin dapat mengoptimalkannya lebih baik — fakta yang tidak khusus untuk MySQL Server saja.

Jadi subqueries bisa lebih lambat daripada LEFT [OUTER] JOIN, tapi menurut saya kekuatan mereka sedikit lebih mudah dibaca.

simhumileco
sumber
45
@ user1735921 IMO itu tergantung ... Secara umum, sangat penting keterbacaan kode, karena sangat penting untuk pengelolaan nanti ... Mari kita ingat pernyataan terkenal Donald Knuth: "Optimalisasi prematur adalah akar dari semua jahat (atau paling tidak sebagian besar) dalam pemrograman " . Namun, tentu saja ada area pemrograman di mana kinerja adalah yang terpenting ... Idealnya, ketika seseorang berhasil merekonsiliasi satu sama lain :)
simhumileco
31
Dalam kueri yang lebih kompleks, saya menemukan penggabungan jauh lebih mudah dibaca daripada sub-kueri. sub-pertanyaan berubah menjadi semangkuk mie di kepala saya.
Zahra
6
@ user1735921 yakin, terutama ketika kueri menjadi sangat rumit sehingga ia melakukan hal yang salah dan Anda menghabiskan satu hari memperbaikinya ... ada keseimbangan di antaranya, seperti biasa.
fabio.sussetto
6
@ user1735921 Hanya jika peningkatan kinerja sebanding dengan peningkatan waktu perawatan yang diperlukan di masa depan
Joshua Schlichting
3
Pendapat saya Joindan sub querymemiliki sintaks yang berbeda, sehingga keterbacaan kita tidak dapat membandingkan, keduanya memiliki keterbacaan yang lebih tinggi selama Anda baik dalam sintaks SQL. Kinerja lebih penting.
Thavaprakash Swaminathan
842

Sub-kueri adalah cara yang benar secara logis untuk menyelesaikan masalah dalam bentuk, "Dapatkan fakta dari A, tergantung pada fakta dari B". Dalam kasus seperti itu, lebih logis untuk memasukkan B dalam sub-kueri daripada melakukan join. Ini juga lebih aman, dalam arti praktis, karena Anda tidak perlu berhati-hati dalam mendapatkan duplikasi fakta dari A karena beberapa pertandingan melawan B.

Namun secara praktis, jawabannya biasanya turun ke kinerja. Beberapa pengoptimal menghisap lemon saat diberi gabungan vs sub-kueri, dan beberapa menghisap lemon dengan cara lain, dan ini adalah pengoptimal-spesifik, versi-DBMS-spesifik, dan khusus-kueri.

Secara historis, bergabung secara eksplisit biasanya menang, oleh karena itu kebijaksanaan yang ditetapkan yang bergabung lebih baik, tetapi optimis semakin baik setiap saat, dan jadi saya lebih suka menulis pertanyaan terlebih dahulu dengan cara yang masuk akal secara logis, dan kemudian merestrukturisasi jika kendala kinerja menuntut hal ini.

Marcelo Cantos
sumber
105
Jawaban yang bagus Saya juga menambahkan bahwa pengembang (terutama yang amatir) tidak selalu mahir dalam SQL.
Álvaro González
4
+1 Mencari beberapa penjelasan logis untuk masalah ini untuk waktu yang lama, ini hanya jawaban yang menurut saya logis
Ali Umair
1
@Marcelo Cantos, bisakah Anda memberikan contoh pernyataan Anda "Ini juga lebih aman, dalam arti praktis, karena Anda tidak perlu berhati-hati tentang mendapatkan duplikasi fakta dari A karena beberapa pertandingan melawan B."? Saya menemukan ini sangat mendalam namun sedikit terlalu abstrak. Terima kasih.
Jinghui Niu
6
@JinghuiNiu Pelanggan yang membeli barang-barang mahal: select custid from cust join bought using (custid) where price > 500. Jika pelanggan membeli beberapa barang mahal, Anda akan mendapatkan dobel. Untuk memperbaiki ini select custid from cust where exists (select * from bought where custid = cust.custid and price > 500),. Anda dapat menggunakannya select distinct …sebagai gantinya, tetapi seringkali lebih banyak pekerjaan, baik untuk pengoptimal atau evaluator.
Marcelo Cantos
1
@ MatTheWhale ya saya menggunakan jawaban yang disederhanakan karena saya malas. Dalam skenario nyata Anda akan menarik lebih banyak kolom daripada hanya custid dari cust.
Marcelo Cantos
357

Umumnya JOIN s lebih cepat daripada sub-kueri dan sangat jarang sub-kueri menjadi lebih cepat.

Di JOIN RDBMS dapat membuat rencana eksekusi yang lebih baik untuk permintaan Anda dan dapat memprediksi data apa yang harus dimuat untuk diproses dan menghemat waktu, tidak seperti sub-query di mana ia akan menjalankan semua permintaan dan memuat semua data mereka untuk melakukan pemrosesan .

Hal yang baik dalam sub-kueri adalah bahwa mereka lebih mudah dibaca daripada JOINs: itu sebabnya kebanyakan orang SQL baru lebih menyukainya; itu adalah cara yang mudah; tetapi ketika datang ke kinerja, GABUNG lebih baik dalam banyak kasus meskipun mereka tidak sulit dibaca juga.

Kronass
sumber
14
Ya, sebagian besar basis data memasukkannya sebagai langkah pengoptimalan untuk mengonversi subqueries menjadi gabungan saat menganalisis kueri Anda.
Cine
16
Jawaban ini agak terlalu disederhanakan untuk pertanyaan yang diajukan. Seperti yang Anda nyatakan: subqueries tertentu ok dan tertentu tidak. Jawabannya tidak terlalu membantu membedakan keduanya. (juga 'sangat jarang' sangat tergantung pada data / aplikasi Anda).
masuk akal
21
dapatkah Anda membuktikan salah satu poin Anda dengan referensi dokumentasi atau hasil tes?
Uğur Gümüşhan
62
Saya membuat pengalaman yang sangat baik dengan sub-kueri yang berisi referensi-kembali ke kueri atas, terutama ketika menyangkut jumlah baris di atas 100.000. Masalahnya tampaknya penggunaan memori dan paging ke file swap. Gabungan akan menghasilkan jumlah data yang sangat besar, yang mungkin tidak sesuai dengan memori dan harus di-paged ke file swap. Setiap kali hal ini terjadi, waktu kueri dari sub-pilihan kecil seperti select * from a where a.x = (select b.x form b where b.id = a.id)sangat kecil dibandingkan dengan bergabung. Ini adalah masalah yang sangat spesifik, tetapi dalam beberapa kasus ini membawa Anda dari jam ke menit.
zuloo
13
Saya berpengalaman dengan Oracle dan saya dapat mengatakan, sub-kueri jauh lebih baik di tabel besar jika Anda tidak memiliki penyaringan atau mengurutkannya.
Amir Pashazadeh
130

Gunakan EXPLAIN untuk melihat bagaimana database Anda mengeksekusi kueri pada data Anda. Ada "itu tergantung" besar dalam jawaban ini ...

PostgreSQL dapat menulis ulang sebuah subquery untuk bergabung atau bergabung ke subquery ketika dianggap satu lebih cepat daripada yang lain. Itu semua tergantung pada data, indeks, korelasi, jumlah data, permintaan, dll.

Frank Heikens
sumber
6
inilah tepatnya mengapa postgresql sangat baik dan berguna sehingga memahami apa tujuannya dan akan memperbaiki kueri berdasarkan apa yang menurutnya lebih baik dan postgresql sangat pandai mengetahui cara melihat datanya
WojonsTech
heww. Saya kira tidak perlu menulis ulang banyak pertanyaan untuk saya! postgresql untuk menang.
Daniel Shin
77

Pada tahun 2010 saya akan bergabung dengan penulis pertanyaan ini dan akan sangat memilih JOIN, tetapi dengan lebih banyak pengalaman (terutama di MySQL) saya dapat menyatakan: Ya, subquery bisa lebih baik. Saya sudah membaca banyak jawaban di sini; beberapa subqueries lain lebih cepat, tetapi tidak memiliki penjelasan yang baik. Saya harap saya bisa memberikan jawaban terlambat ini:

Pertama-tama, izinkan saya mengatakan yang paling penting: Ada berbagai bentuk sub-kueri

Dan pernyataan penting kedua: Ukuran itu penting

Jika Anda menggunakan sub-kueri, Anda harus mengetahui bagaimana DB-Server mengeksekusi sub-kueri. Terutama jika sub-kueri dievaluasi sekali atau untuk setiap baris! Di sisi lain, DB-Server modern mampu mengoptimalkan banyak hal. Dalam beberapa kasus, subquery membantu mengoptimalkan kueri, tetapi versi yang lebih baru dari DB-Server mungkin membuat optimisasi menjadi usang.

Sub-kueri di Bidang-Pilih

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

Perlu diketahui bahwa sub-kueri dijalankan untuk setiap baris yang dihasilkan dari foo.
Hindari ini jika memungkinkan; mungkin secara drastis memperlambat permintaan Anda pada kumpulan data besar. Namun, jika sub-kueri tidak memiliki referensi untuk fooitu dapat dioptimalkan oleh DB-server sebagai konten statis dan dapat dievaluasi hanya sekali.

Sub-pertanyaan dalam pernyataan Dimana

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

Jika Anda beruntung, DB mengoptimalkan ini secara internal menjadi JOIN. Jika tidak, kueri Anda akan menjadi sangat, sangat lambat pada kumpulan data besar karena akan mengeksekusi sub-kueri untuk setiap baris foo, bukan hanya hasil seperti pada tipe-pilih.

Sub-pertanyaan dalam pernyataan Bergabung

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

Ini menarik. Kami menggabungkan JOINdengan sub-permintaan. Dan di sini kita mendapatkan kekuatan sebenarnya dari sub-kueri. Bayangkan sebuah dataset dengan jutaan baris wilcotetapi hanya sedikit yang berbeda me. Alih-alih bergabung dengan meja besar, kami memiliki meja sementara yang lebih kecil untuk bergabung. Ini dapat menghasilkan pertanyaan yang jauh lebih cepat tergantung pada ukuran basis data. Anda dapat memiliki efek yang sama dengan CREATE TEMPORARY TABLE ...dan INSERT INTO ... SELECT ..., yang mungkin memberikan keterbacaan yang lebih baik pada pertanyaan yang sangat kompleks (tetapi dapat mengunci dataset dalam tingkat isolasi baca berulang).

Sub-kueri bersarang

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

Anda dapat membuat sub-kueri di berbagai tingkatan. Ini dapat membantu pada kumpulan data besar jika Anda harus mengelompokkan atau mengurutkan hasilnya. Biasanya DB-Server membuat tabel sementara untuk ini, tetapi kadang-kadang Anda tidak perlu menyortir seluruh tabel, hanya di resultset. Ini mungkin memberikan kinerja yang jauh lebih baik tergantung pada ukuran tabel.

Kesimpulan

Sub-kueri bukan pengganti untuk JOINdan Anda tidak boleh menggunakannya seperti ini (walaupun mungkin). Menurut pendapat saya yang sederhana, penggunaan yang benar dari sub-permintaan adalah penggunaan sebagai pengganti cepat CREATE TEMPORARY TABLE .... Sub-kueri yang baik mengurangi dataset dengan cara yang tidak dapat Anda capai dalam ONpernyataan a JOIN. Jika sub-kueri memiliki salah satu kata kunci GROUP BYatau DISTINCTdan lebih disukai tidak terletak di bidang pilih atau pernyataan di mana, maka mungkin banyak meningkatkan kinerja.

Trendfischer
sumber
3
Untuk Sub-queries in the Join-statement: (1) menghasilkan tabel turunan dari sub-kueri itu sendiri bisa memakan waktu yang sangat lama. (2) tabel turunan yang dihasilkan tidak diindeks. keduanya sendiri secara signifikan dapat memperlambat SQL.
jxc
@ jxc Saya hanya bisa berbicara untuk MySQL (1) Itu tabel sementara yang mirip dengan join. Waktu tergantung pada jumlah data. Jika Anda tidak dapat mengurangi data dengan subquery, gunakan gabungan. (2) Ini benar, itu tergantung pada faktor Anda dapat mengurangi data dalam tabel sementara. Saya memiliki kasus dunia nyata, di mana saya bisa mengurangi bentuk ukuran bergabung beberapa juta menjadi beberapa ratus dan mengurangi waktu kueri dari beberapa detik (dengan penggunaan indeks penuh) menjadi seperempat detik dengan subquery.
Trendfischer
IMO: (1) tabel sementara seperti itu (tabel turunan) tidak terwujud, sehingga setiap kali Anda menjalankan SQL, tabel sementara harus dibuat ulang, yang bisa sangat mahal dan leher botol nyata (yaitu menjalankan grup dengan jutaan catatan) (2) bahkan jika Anda dapat mengurangi ukuran tabel temp ke 10catatan, karena tidak ada indeks, itu masih berarti berpotensi untuk meminta 9 kali lebih banyak catatan data daripada dengan tabel temp saat Gabung tabel lainnya. BTW Saya punya masalah ini sebelumnya dengan db (MySQL) saya, dalam kasus saya, menggunakan sub-query di SELECT listbisa jauh lebih cepat.
jxc
@ jxc Saya tidak ragu bahwa ada banyak contoh, di mana menggunakan subquery kurang optimal. Sebagai praktik yang baik, Anda harus menggunakan EXPLAINkueri sebelum mengoptimalkan. Dengan yang lama set profiling=1Anda bisa dengan mudah melihat, jika meja sementara adalah hambatan. Dan bahkan indeks membutuhkan waktu pemrosesan, B-Trees mengoptimalkan permintaan untuk catatan, tetapi tabel catatan 10 bisa jauh lebih cepat daripada indeks untuk jutaan catatan. Tetapi itu tergantung pada banyak faktor seperti ukuran dan jenis bidang.
Trendfischer
1
Saya sangat menikmati penjelasan Anda. Terima kasih.
kurang baik
43

Pertama-tama, untuk membandingkan keduanya terlebih dahulu Anda harus membedakan kueri dengan subkueri dengan:

  1. kelas subquery yang selalu memiliki kueri yang setara yang ditulis dengan gabungan
  2. kelas subqueries yang tidak bisa ditulis ulang menggunakan gabungan

Untuk kueri kelas pertama, RDBMS yang baik akan melihat gabungan dan subkueri sebagai setara dan akan menghasilkan rencana kueri yang sama.

Sekarang ini bahkan mysql melakukan itu.

Meski demikian, terkadang tidak, tetapi ini tidak berarti bahwa bergabung akan selalu menang - Saya memiliki kasus ketika menggunakan subquery di mysql meningkatkan kinerja. (Misalnya jika ada sesuatu yang mencegah perencana mysql untuk memperkirakan biaya dengan benar dan jika perencana tidak melihat varian gabungan dan varian subquery sama, maka subquery dapat mengungguli gabungan dengan memaksa jalur tertentu).

Kesimpulannya adalah Anda harus menguji pertanyaan Anda untuk varian join dan subquery jika Anda ingin memastikan yang mana yang akan berkinerja lebih baik.

Untuk kelas kedua perbandingan tidak masuk akal karena kueri tersebut tidak dapat ditulis ulang menggunakan gabungan dan dalam kasus ini subquery adalah cara alami untuk melakukan tugas yang diperlukan dan Anda tidak boleh mendiskriminasikannya.

Tidak masuk akal
sumber
1
dapatkah Anda memberikan contoh kueri yang ditulis menggunakan sub-kueri yang tidak dapat dikonversi menjadi gabungan (kelas kedua, seperti Anda menyebutnya)?
Zahra
24

Saya pikir apa yang kurang ditekankan dalam jawaban yang dikutip adalah masalah duplikat dan hasil bermasalah yang mungkin timbul dari kasus (penggunaan) tertentu.

(Meskipun Marcelo Cantos menyebutkannya)

Saya akan mengutip contoh dari kursus Lagunita Stanford tentang SQL.

Meja Siswa

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

Terapkan Tabel

(aplikasi dibuat untuk universitas dan jurusan tertentu)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

Mari kita coba menemukan skor IPK untuk siswa yang telah mendaftar ke CSjurusan (terlepas dari universitas)

Menggunakan subquery:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

Nilai rata-rata untuk rangkaian hasil ini adalah:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

Menggunakan gabungan:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

nilai rata-rata untuk hasil ini:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

Jelas bahwa upaya kedua menghasilkan hasil yang menyesatkan dalam kasus penggunaan kami, mengingat bahwa itu menghitung duplikat untuk perhitungan nilai rata-rata. Juga jelas bahwa penggunaan distinctdengan pernyataan join-based tidak akan menghilangkan masalah, mengingat hal itu akan secara salah menjaga satu dari tiga kemunculan 3.9skor. Kasus yang benar adalah untuk menghitung DUA (2) kejadian dari 3.9skor mengingat bahwa kami benar-benar memiliki DUA (2) siswa dengan skor yang memenuhi kriteria permintaan kami.

Tampaknya dalam beberapa kasus, sub-kueri adalah cara paling aman, selain masalah kinerja.

pkaramol
sumber
Saya pikir Anda tidak dapat menggunakan sub-permintaan di sini. Ini bukan kasus di mana Anda secara logis dapat menggunakan salah satu tetapi memberikan jawaban yang salah karena implementasi teknisnya. Ini adalah kasus di mana Anda TIDAK BISA menggunakan sub-kueri karena siswa yang bukan milik CS dapat skor 3,9 yang ada dalam daftar skor IN. Konteks CS hilang begitu sub-kueri dieksekusi, yang bukan apa yang kita inginkan secara logis. Jadi ini bukan contoh yang baik di mana keduanya dapat digunakan. Penggunaan sub-query secara konseptual / logis salah untuk use-case ini meskipun untungnya memberikan hasil yang tepat untuk dataset yang berbeda.
Saurabh Patil
22

Dokumentasi MSDN untuk SQL Server mengatakan

Banyak pernyataan Transact-SQL yang menyertakan subquery dapat dirumuskan secara alternatif sebagai gabungan. Pertanyaan lain hanya dapat diajukan dengan subqueries. Dalam Transact-SQL, biasanya tidak ada perbedaan kinerja antara pernyataan yang mencakup subquery dan versi yang secara semantik setara yang tidak. Namun, dalam beberapa kasus di mana keberadaan harus diperiksa, gabungan menghasilkan kinerja yang lebih baik. Jika tidak, kueri bersarang harus diproses untuk setiap hasil kueri luar untuk memastikan penghapusan duplikat. Dalam kasus seperti itu, pendekatan gabungan akan menghasilkan hasil yang lebih baik.

jadi jika Anda membutuhkan sesuatu seperti

select * from t1 where exists select * from t2 where t2.parent=t1.id

coba gunakan gabung saja. Dalam kasus lain, tidak ada bedanya.

Saya katakan: Membuat fungsi untuk subqueries menghilangkan masalah cluttter dan memungkinkan Anda untuk menerapkan logika tambahan ke subqueries. Jadi saya sarankan membuat fungsi untuk subquery bila memungkinkan.

Kekacauan dalam kode adalah masalah besar dan industri telah berusaha menghindarinya selama beberapa dekade.

Uğur Gümüşhan
sumber
9
Mengganti subqueries dengan fungsi adalah ide yang sangat buruk untuk kinerja di beberapa RDBMS (misalnya Oracle), jadi saya akan merekomendasikan hal sebaliknya - gunakan subqueries / gabung alih-alih fungsi sedapat mungkin.
Frank Schmitt
3
@FrankSchmitt tolong dukung argumen Anda dengan referensi.
Uğur Gümüşhan
2
Ada juga kasus di mana Anda harus menggunakan sub kueri alih-alih bergabung bahkan jika Anda memeriksa keberadaan: jika Anda memeriksa NOT EXISTS. A NOT EXISTSmenang atas a LEFT OUTER JOIN karena berbagai alasan: dalm, keselamatan-gagal (dalam kasus kolom nulable) dan keterbacaan. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim
16

Jalankan pada basis data yang sangat besar dari CMS Mambo lama:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 detik

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 detik

EXPLAIN menunjukkan bahwa mereka memeriksa jumlah baris yang sama persis, tetapi satu membutuhkan waktu 3 detik dan yang satu mendekati instan. Pesan moral dalam cerita? Jika kinerja itu penting (kapan bukan?), Cobalah beberapa cara dan lihat mana yang tercepat.

Dan...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 detik

Sekali lagi, hasil yang sama, jumlah baris yang sama diperiksa. Dugaan saya adalah DISTINCT mos_content.catid membutuhkan waktu lebih lama untuk mencari tahu daripada DISTINCT mos_categories.id.

Jason
sumber
1
saya ingin tahu lebih banyak tentang apa yang ingin Anda tunjukkan di baris terakhir "Dugaan saya adalah bahwa DISTINCT mos_content.catid membutuhkan waktu lebih lama untuk mencari tahu daripada DISTINCT mos_categories.id lakukan." . Apakah Anda mengatakan bahwa id harus dinamai saja iddan tidak dinamai seperti itu catid? Mencoba mengoptimalkan akses db saya, dan pembelajaran Anda dapat membantu.
bool.dev
2
menggunakan SQL IN dalam kasus itu adalah praktik yang buruk dan tidak membuktikan apa pun.
Uğur Gümüşhan
15

Sesuai pengamatan saya seperti dua kasus, jika sebuah tabel memiliki kurang dari 100.000 catatan maka gabungan akan bekerja dengan cepat.

Tetapi dalam kasus bahwa tabel memiliki lebih dari 100.000 catatan maka subquery adalah hasil terbaik.

Saya memiliki satu tabel yang memiliki 500.000 catatan yang saya buat di bawah permintaan dan hasilnya adalah seperti

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

Hasil: 13,3 Detik

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

Hasil: 1,65 Detik

Vijay Gajera
sumber
Saya setuju, kadang-kadang melanggar kueri juga berfungsi, saat Anda memiliki jutaan catatan, Anda tidak ingin menggunakan gabungan karena selalu ada. Alih-alih menanganinya dalam kode dan peta dalam kode lebih baik.
user1735921
1
Ikatan gabungan Anda tidak bekerja cukup cepat, Anda mungkin kehilangan indeks. Query Analyzer dapat sangat membantu dalam membandingkan kinerja aktual.
digital.aaron
Saya setuju dengan Ajay Gajera, saya telah melihat ini untuk diri saya sendiri.
user1735921
14
Bagaimana masuk akal untuk membandingkan kinerja dua kueri yang menghasilkan hasil yang berbeda?
Paul Spiegel
Ya itu adalah pertanyaan yang berbeda tetapi mengembalikan hasil yang sama
raja neo
12

Subquery umumnya digunakan untuk mengembalikan satu baris sebagai nilai atom, meskipun mereka dapat digunakan untuk membandingkan nilai terhadap beberapa baris dengan kata kunci IN. Mereka diizinkan pada hampir semua titik yang berarti dalam pernyataan SQL, termasuk daftar target, klausa WHERE, dan sebagainya. Sub-kueri sederhana dapat digunakan sebagai kondisi pencarian. Misalnya, di antara sepasang tabel:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

Perhatikan bahwa menggunakan operator nilai normal pada hasil sub-permintaan mengharuskan hanya satu bidang yang harus dikembalikan. Jika Anda tertarik untuk memeriksa keberadaan satu nilai dalam satu set nilai lainnya, gunakan IN:

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

Ini jelas berbeda dengan mengatakan LEFT-JOIN di mana Anda hanya ingin bergabung dengan hal-hal dari tabel A dan B bahkan jika kondisi gabungan tidak menemukan catatan yang cocok di tabel B, dll.

Jika Anda hanya khawatir tentang kecepatan, Anda harus memeriksa dengan database Anda dan menulis kueri yang baik dan melihat apakah ada perbedaan kinerja yang signifikan.

rkulla
sumber
11

Versi MySQL: 5.5.28-0ubuntu0.12.04.2-log

Saya juga mendapat kesan bahwa BERGABUNG selalu lebih baik daripada sub-kueri di MySQL, tetapi EXPLAIN adalah cara yang lebih baik untuk membuat penilaian. Berikut adalah contoh di mana sub kueri bekerja lebih baik daripada GABUNGAN.

Inilah pertanyaan saya dengan 3 sub-pertanyaan:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

MENJELASKAN menunjukkan:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

Permintaan yang sama dengan BERGABUNG adalah:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

dan hasilnya adalah:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Perbandingan rowskolom memberi tahu perbedaan dan permintaan dengan BERGABUNG gunakan Using temporary; Using filesort.

Tentu saja ketika saya menjalankan kedua query, yang pertama dilakukan dalam 0,02 detik, yang kedua tidak selesai bahkan setelah 1 menit, jadi EXPLAIN menjelaskan pertanyaan ini dengan benar.

Jika saya tidak memiliki INNER BERGABUNG di atas list_tagmeja yaitu jika saya menghapus

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

dari kueri pertama dan sesuai:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

dari kueri kedua, lalu EXPLAIN mengembalikan jumlah baris yang sama untuk kedua kueri dan kedua kueri ini berjalan sama cepat.

arun
sumber
Saya memiliki situasi yang serupa, tetapi dengan lebih banyak bergabung dari milik Anda, akan mencoba menjelaskan sekali
pahnin
Dalam Oracle atau PostgreSQL saya akan mencoba: DAN TIDAK ADA (SELECT 1 FROM list_tag WHERE list_id = l.list_id AND tag_id dalam (43, 55, 246403))
David Aldridge
11

Subquery memiliki kemampuan untuk menghitung fungsi agregasi dengan cepat. Misalnya, temukan harga minimal buku dan dapatkan semua buku yang dijual dengan harga ini. 1) Menggunakan Subqueries:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) menggunakan GABUNGAN

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;
Vlad
sumber
Kasus lain: beberapa GROUP BYs dengan tabel berbeda: stackoverflow.com/questions/11415284/... Subqueries tampaknya lebih umum. Lihat juga orang MySQL: dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功 法轮功
6
-1 Ini menyesatkan karena Anda menggunakan subquery dan bergabung dalam kedua contoh. Bahwa Anda telah menarik subquery ke kueri kedua untuk menentukan harga pesanan terendah tidak berpengaruh karena database akan melakukan hal yang sama persis. Plus, Anda tidak menulis ulang bergabung menggunakan subquery; kedua kueri menggunakan gabungan. Anda adalah benar bahwa subqueries memungkinkan fungsi agregat, tetapi contoh ini tidak menunjukkan fakta bahwa.
David Harkness
Saya setuju dengan David, dan Anda dapat menggunakan grup dengan untuk mendapatkan harga minimum.
user1735921
9
  • Aturan umum adalah bahwa bergabung lebih cepat dalam banyak kasus (99%).
  • Semakin banyak tabel data, subqueries lebih lambat.
  • Semakin sedikit tabel data, subqueries memiliki kecepatan setara dengan join .
  • The subqueries lebih sederhana, lebih mudah untuk memahami, dan lebih mudah dibaca.
  • Sebagian besar kerangka kerja web dan aplikasi serta "ORM" dan "Rekaman aktif" menghasilkan kueri dengan subkueri , karena dengan subkueri lebih mudah membagi tanggung jawab, mempertahankan kode, dll.
  • Untuk situs web atau subqueries aplikasi yang lebih kecil tidak masalah, tetapi untuk situs web dan aplikasi yang lebih besar Anda harus sering menulis ulang kueri yang dihasilkan untuk bergabung dengan kueri, terutama jika kueri menggunakan banyak subkueri dalam kueri.

Beberapa orang mengatakan "beberapa RDBMS dapat menulis ulang subquery untuk bergabung atau bergabung ke subquery ketika berpikir yang lebih cepat dari yang lain.", Tapi pernyataan ini berlaku untuk kasus sederhana, pasti tidak untuk pertanyaan rumit dengan subquery yang sebenarnya penyebab masalah dalam kinerja.

fico7489
sumber
> tetapi pernyataan ini berlaku untuk kasus-kasus sederhana. Saya mengerti bahwa ini adalah kasus sederhana yang dapat ditulis ulang menjadi "GABUNG" oleh RDBMS, atau ini adalah kasus yang kompleks sehingga subqueries sesuai di sini. :-) Poin bagus tentang ORM. Saya pikir ini memiliki dampak terbesar.
pilat
4

Perbedaannya hanya terlihat ketika tabel gabungan kedua memiliki lebih banyak data secara signifikan daripada tabel primer. Saya punya pengalaman seperti di bawah ini ...

Kami memiliki tabel pengguna seratus ribu entri dan data keanggotaan mereka (pertemanan) sekitar 3 ratus ribu entri. Itu adalah pernyataan bergabung untuk mengambil teman dan data mereka, tetapi dengan penundaan besar. Tapi itu berfungsi dengan baik di mana hanya ada sejumlah kecil data di tabel keanggotaan. Setelah kami mengubahnya untuk menggunakan sub-kueri itu berfungsi dengan baik.

Tetapi sementara itu permintaan gabungan bekerja dengan tabel lain yang memiliki lebih sedikit entri daripada tabel utama.

Jadi saya pikir pernyataan join dan sub query bekerja dengan baik dan itu tergantung pada data dan situasinya.

jpk
sumber
3

Saat ini, banyak dbs dapat mengoptimalkan subqueries dan join. Jadi, Anda hanya perlu memeriksa permintaan Anda menggunakan menjelaskan dan melihat mana yang lebih cepat. Jika tidak ada banyak perbedaan dalam kinerja, saya lebih suka menggunakan subquery karena mereka sederhana dan lebih mudah dimengerti.

Song Eunwoo
sumber
1

Saya hanya berpikir tentang masalah yang sama, tetapi saya menggunakan subquery di bagian FROM. Saya perlu koneksi dan permintaan dari tabel besar, tabel "slave" memiliki 28 juta catatan tetapi hasilnya hanya 128 sehingga data besar menghasilkan sangat kecil! Saya menggunakan fungsi MAX () di atasnya.

Pertama saya menggunakan LEFT JOIN karena saya pikir itu adalah cara yang benar, mysql dapat mengoptimalkan dll. Kedua kalinya hanya untuk pengujian, saya menulis ulang untuk sub-pilih terhadap JOIN.

Runtime BERGABUNG KIRI: runtuh 1.12s SUB-SELECT: 0.06s

18 kali lebih cepat daripada memilih bergabung! Hanya di adv chokito. Subselect terlihat mengerikan tetapi hasilnya ...

Karoly Szabo
sumber
-1

Jika Anda ingin mempercepat kueri Anda menggunakan gabung:

Untuk "gabung dalam / gabung", Jangan gunakan di mana kondisi alih-alih gunakan dalam kondisi "ON". Misalnya:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

Untuk "Gabung Kiri / Kanan", Jangan gunakan dalam kondisi "ON", karena jika Anda menggunakan gabung kiri / kanan itu akan mendapatkan semua baris untuk satu tabel. Jadi, Tidak ada gunanya menggunakannya di "Aktif". Jadi, Coba gunakan kondisi "Di mana"

sam ruben
sumber
Ini tergantung pada server SQL dan pada kompleksitas permintaan. Banyak implementasi SQL akan mengoptimalkan pertanyaan sederhana seperti ini untuk kinerja terbaik. Mungkin memberikan contoh nama dan versi server di mana perilaku ini terjadi untuk meningkatkan jawabannya?
Trendfischer