Katılın ve alt sorgu


837

Ben eski okul MySQL kullanıcısıyım ve her zaman JOINalt sorgu yerine tercih ettim . Ama günümüzde herkes alt sorguyu kullanıyor ve bundan nefret ediyorum; Neden bilmiyorum.

Herhangi bir fark varsa kendime bakacak teorik bilgiye sahip değilim. Bir alt sorgu bir kadar iyi JOINmidir ve bu nedenle endişelenecek bir şey yok mu?


23
Alt sorgular bazen harika. MySQL'de performans açısından emilirler. Onları kullanma.
16'da koşma

8
Her zaman, alt sorguların belirli DB teknolojilerinde kullanılabildiği yerlerde birleştirmeler olarak yürütüldüğü izlenimi altındaydım.
Kezzer

18
Alt sorgular her zaman emilmez, oldukça büyük tablolarla birleşirken, tercih edilen yol o büyük tablodan bir alt seçim yapmak (satır sayısını sınırlamak) ve sonra birleştirmektir.
ovais.tariq

136
"Günümüzde herkes alt sorgu kullanıyor" [alıntı gerekli]
Piskvor binadan ayrıldı

3
Potansiyel olarak ilişkili (çok daha spesifik olmasına rağmen): stackoverflow.com/questions/141278/subqueries-vs-joins/…
Leigh Brenecki 29:11

Yanıtlar:


191

MySQL kılavuzundan alınmıştır ( 13.2.10.11 Alt Sorguları Birleşim Olarak Yeniden Yazma ):

Bir LEFT [OUTER] JOIN eşdeğer bir alt sorgudan daha hızlı olabilir, çünkü sunucu onu daha iyi optimize edebilir - bu sadece MySQL Server'a özgü değildir.

Bu nedenle alt sorgular daha yavaş olabilir LEFT [OUTER] JOIN, ancak bence güçleri biraz daha yüksek okunabilirlik.


45
@ user1735921 IMO bağlıdır ... Genellikle, kodun okunabilirliği çok önemlidir, çünkü daha sonraki yönetimi için büyük önem taşımaktadır ... Donald Knuth'un ünlü ifadesini hatırlayalım: "Erken optimizasyon herkesin köküdür msgstr "kötü (ya da en azından çoğunu) programlama" . Ancak, doğal olarak performansın çok önemli olduğu programlama alanları vardır ... İdeal olarak, biri diğeriyle uzlaştırmayı
başardığında

30
Daha karmaşık sorgularda, birleştirmeleri okumayı alt sorgulardan çok daha kolay buluyorum. alt sorgular kafamdaki bir kase erişte haline dönüşüyor.
Zahra

6
@ user1735921, özellikle de sorgu o kadar karmaşık hale geldiğinde yanlış bir şey yapar ve bunu düzeltmek için bir gün geçirirseniz ... aralarında, her zamanki gibi bir denge vardır.
fabio.sussetto

6
@ user1735921 Yalnızca performans kazançları gelecekte gerekli bakım süresindeki artışa değiyorsa
Joshua Schlichting

3
Benim düşüncem Joinve sub queryfarklı sözdizimi vardır, bu yüzden karşılaştırılamaz okunabilirlik, SQL sözdiziminde iyi olduğunuz sürece her ikisi de daha yüksek okunabilirliğe sahiptir. Performans daha önemlidir.
Thavaprakash Swaminathan

841

Alt sorgular, "A'dan gerçekleri al, B'den gerçeklere koşullu" formundaki sorunları çözmenin mantıksal olarak doğru yoludur. Bu gibi durumlarda, B'yi bir alt sorguda birleştirmek birleştirmekten daha mantıklıdır. Pratik anlamda da daha güvenlidir, çünkü B'ye karşı çoklu maçlar nedeniyle A'dan tekrarlanan gerçekleri almak konusunda dikkatli olmanız gerekmez.

Bununla birlikte, pratik olarak, cevap genellikle performansa iner. Bazı optimize ediciler bir alt sorguya karşı birleştirme verildiğinde limon emer, bazıları ise diğer yolla limon emer ve bu, optimizere özgü, DBMS sürümüne özgü ve sorguya özgüdür.

Tarihsel olarak, açık birleşimler genellikle kazanır, bu nedenle birleşmelerin daha iyi olduğu bilgelik kazanır, ancak optimizatörler her zaman daha iyi hale gelir ve bu nedenle önce mantıksal olarak tutarlı bir şekilde sorgular yazmayı ve sonra performans kısıtlamaları bunu gerektiriyorsa yeniden yapılandırmayı tercih ederim.


105
Mükemmel cevap. Ayrıca geliştiricilerin (özellikle amatör olanlar) SQL'de her zaman yetkin olmadığını da ekleyeceğim.
Álvaro González

4
+1 Bu sorun için uzun bir süre mantıklı bir açıklama arıyorum, bu sadece benim için mantıklı görünen cevap
Ali Umair

1
@Marcelo Cantos, Lütfen "B'ye karşı çoklu maçlar nedeniyle A'dan çoğaltılan gerçekleri almak konusunda dikkatli olmanız gerekmediğinden, pratik anlamda da daha güvenlidir" ifadesine bir örnek verebilir misiniz? Bunu çok anlayışlı ama biraz fazla soyut buldum. Teşekkürler.
Jinghui Niu

6
Pahalı ürünleri satın @JinghuiNiu Müşteriler: select custid from cust join bought using (custid) where price > 500. Bir müşteri birden fazla pahalı ürün satın aldıysa, ikiye katlanırsınız. Bunu düzeltmek için select custid from cust where exists (select * from bought where custid = cust.custid and price > 500). Bunun select distinct …yerine kullanabilirsiniz , ancak genellikle optimizer veya değerlendirici için daha fazla iştir.
Marcelo Cantos

1
@MatTheWhale evet Tembel olduğum için aşırı basitleştirilmiş bir cevap kullandım. Gerçek bir senaryoda, sadece custid'den daha fazla sütun çekiyor olacaksınız.
Marcelo Cantos

357

Çoğu durumda JOINs alt sorgulardan daha hızlıdır ve bir alt sorgunun daha hızlı olması çok nadirdir.

In JOINRDBMS, sorgunuz için daha iyi bir yürütme planı oluşturabilir ve tüm sorguları çalıştıracağı ve işlem yapmak için tüm verilerini yükleyeceği alt sorgunun aksine, hangi verilerin işlenmesi gerektiğini tahmin edebilir ve zamandan tasarruf edebilir. .

Alt sorgularda iyi olan şey, JOINs'den daha okunabilir olmalarıdır : bu nedenle çoğu yeni SQL insanının bunları tercih etmesi; bu kolay yol; ancak performans söz konusu olduğunda, JOINS çoğu zaman okunması zor olmasa da daha iyidir.


14
Evet, bu nedenle çoğu veritabanı, sorgunuzu analiz ederken alt sorguları birleştirmelere dönüştürmek için bir optimizasyon adımı olarak içerir.
Sinema

16
Bu cevap, sorulan soru için biraz fazla basitleştirilmiştir. Belirttiğiniz gibi: bazı alt sorgular tamam ve bazıları doğru değil. Cevap, ikisini ayırt etmeye gerçekten yardımcı olmuyor. (ayrıca 'çok nadir' gerçekten verilerinize / uygulamanıza bağlıdır).
Mantıksız

21
puanlarınızdan herhangi birini dokümantasyon referansı veya test sonuçlarıyla kanıtlayabilir misiniz?
Uğur Gümüşhan

62
Özellikle 100.000'in üzerindeki satır sayıları söz konusu olduğunda, üst sorguya bir geri başvuru içeren alt sorgular ile çok iyi deneyimler yaptım. Şey bellek kullanımı ve takas dosyasına çağrı gibi görünüyor. Birleştirme, belleğe sığmayan ve takas dosyasına sayfalanması gereken çok büyük miktarda veri üretir. Durum böyle olduğunda, küçük alt seçimlerin sorgu zamanları select * from a where a.x = (select b.x form b where b.id = a.id)bir birleştirmeye kıyasla son derece küçüktür. Bu çok özel bir sorundur, ancak bazı durumlarda sizi saatlerden dakikalara getirir.
zuloo

13
Oracle ile deneyimliyim ve alt filtreler büyük tablolarda çok daha iyi filtreleme veya sıralama yoksa çok daha iyi diyebilirim.
Amir Paşazadeh

130

Veritabanınızın sorgunuzu verilerinizde nasıl yürüttüğünü görmek için EXPLAIN kullanın. Bu cevapta büyük bir "duruma bağlı" var ...

PostgreSQL bir alt sorguyu bir birleşime yeniden birleştirebilir veya bir alt sorguyu birleştirerek diğerinden daha hızlı olduğunu düşünebilir. Her şey verilere, dizinlere, korelasyona, veri miktarına, sorguya vb. Bağlıdır.


6
tam olarak bu yüzden postgresql çok iyi ve yararlıdır, hedefin ne olduğunu anlar ve daha iyi olduğunu düşündüğü bir sorguyu düzeltir ve postgresql verilerine nasıl bakacağını bilmekte çok iyidir
WojonsTech

heww. Benim için tonlarca sorguyu yeniden yazmaya gerek yok sanırım! kazanmak için postgresql.
Daniel Shin

77

2010 yılında bu soruların yazarına katılırdım ve kesinlikle oy verirdim JOIN, ancak çok daha fazla deneyimle (özellikle MySQL'de) şunu söyleyebilirim: Evet alt sorgular daha iyi olabilir. Burada birden fazla cevap okudum; belirtilen bazı alt sorgular daha hızlıdır, ancak iyi bir açıklaması yoktu. Umarım bu (çok) geç cevabı verebilirim:

Her şeyden önce, en önemlisi şunu söyleyeyim: Farklı alt sorgu formları var

Ve ikinci önemli ifade: Boyut önemlidir

Alt sorgular kullanıyorsanız , DB-Server'ın alt sorguyu nasıl yürüttüğünün farkında olmalısınız . Özellikle alt sorgu bir veya her satır için değerlendirilirse! Öte yandan, modern bir DB-Server çok şey optimize edebilir. Bazı durumlarda bir alt sorgu bir sorguyu optimize etmeye yardımcı olur, ancak DB-Server'ın daha yeni bir sürümü optimizasyonu geçersiz kılabilir.

Select-Fields'deki alt sorgular

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

Kaynaktan sonuçlanan her satır için bir alt sorgu yürütüldüğünü unutmayın foo.
Mümkünse bundan kaçının; büyük veri kümelerinde sorgunuzu büyük ölçüde yavaşlatabilir. Ancak, alt sorgunun hiçbir başvurusu yoksa foo, DB sunucusu tarafından statik içerik olarak en iyi duruma getirilebilir ve yalnızca bir kez değerlendirilebilir.

Where-ifadesindeki alt sorgular

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

Şanslıysanız, DB bunu dahili olarak bir JOIN. Değilse, sorgunuz büyük veri kümelerinde çok, çok yavaş olur, çünkü fooyalnızca select türündeki sonuçları değil, her satır için alt sorguyu yürütür .

Join deyimindeki alt sorgular

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

Bu ilginç. JOINBir alt sorgu ile birleştiriyoruz . Ve burada alt sorguların gerçek gücünü elde ediyoruz. Milyonlarca satır içeren wilcoancak sadece birkaç farklı veri kümesini düşünün me. Büyük bir masaya katılmak yerine, şimdi katılmak için daha küçük bir geçici masa var. Bu, veritabanı boyutuna bağlı olarak çok daha hızlı sorgulara neden olabilir. Sen ile aynı etkiye sahip olabilir CREATE TEMPORARY TABLE ...ve INSERT INTO ... SELECT ...çok karmaşık sorgularda daha iyi okunabilirliği sağlayabilir (ama tekrarlanabilir okuma yalıtım düzeyi veri setlerini kilitleyebilir) hangi.

Yuvalanmış alt sorgular

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

Alt sorguları birden çok düzeyde iç içe yerleştirebilirsiniz. Sonuçları gruplamanız veya sıralamanız gerekiyorsa bu, büyük veri kümelerinde yardımcı olabilir. Genellikle DB-Server bunun için geçici bir tablo oluşturur, ancak bazen tablonun tamamında, yalnızca sonuç kümesinde sıralama yapmanız gerekmez. Bu, tablonun boyutuna bağlı olarak çok daha iyi performans sağlayabilir.

Sonuç

Alt sorgular a'nın yerini tutmaz JOINve bunları böyle kullanmamalısınız (mümkünse). Benim düşünceme göre, bir alt sorgunun doğru kullanımı, hızlı bir şekilde değiştirilmesidir CREATE TEMPORARY TABLE .... İyi bir alt sorgu, bir veri kümesini a ONifadesinde başaramayacağınız şekilde azaltır JOIN. Bir alt sorgu anahtar kelimelerden birine sahipse GROUP BYveya DISTINCTtercihen select alanlarında veya where deyiminde yer almıyorsa, performansı çok artırabilir.


3
Çünkü Sub-queries in the Join-statement: (1) alt sorgudan türetilmiş tablo oluşturmak çok uzun zaman alabilir. (2) elde edilen türetilmiş tablo indekslenmemiştir. bu ikisi tek başına SQL'i önemli ölçüde yavaşlatabilir.
jxc

@jxc Sadece MySQL için konuşabiliyorum (1) Birleştirme işlemine benzer geçici bir tablo var. Zaman, veri miktarına bağlıdır. Verileri bir alt sorguyla azaltamazsanız, birleştirme kullanın. (2) Bu doğru, geçici tablodaki verileri azaltabileceğiniz faktöre bağlıdır. Ben birleştirmek boyutu form birkaç milyon birkaç yüz azaltmak ve sorgu süresi birden çok saniye (tam dizin kullanımı ile) bir alt sorgu ile saniyenin çeyrek için azaltabilir gerçek dünya vakaları vardı.
Trendfischer

IMO: (1) bu tür geçici tablo (türetilmiş tablo) gerçekleştirilmez, bu nedenle SQL'i her çalıştırdığınızda, geçici tablo yeniden oluşturulmalıdır, bu çok maliyetli olabilir ve gerçek bir şişe boynu (yani bir grubu milyonlarca çalıştırmak) (2) geçici tablonun boyutunu 10kayıtlara azaltabilseniz bile , dizin olmadığından, diğer tablolara KATILIRKEN geçici tablodan w'den 9 kat daha fazla veri kaydını sorgulamak potansiyel olarak anlamına gelir. BTW Daha önce benim db (MySQL) ile bu sorunu vardı, benim durumumda, alt sorgu kullanarak SELECT listçok daha hızlı olabilir.
jxc

@jxc Bir alt sorgu kullanmanın daha az optimal olduğu pek çok örnek olduğundan şüphe duymuyorum. İyi uygulama olarak EXPLAIN, optimize etmeden önce bir sorguda kullanmanız gerekir . Eskisi ile set profiling=1geçici bir tablonun bir darboğaz olup olmadığını kolayca görebilirsiniz. Ve bir indeks bile işlem süresine ihtiyaç duyar, B-Ağaçlar kayıtlar için sorgulamayı optimize eder, ancak 10 kayıt tablosu milyonlarca kayıt için bir endeksten çok daha hızlı olabilir. Ancak alan boyutları ve türleri gibi birçok faktöre bağlıdır.
Trendfischer

1
Açıklamanızdan gerçekten zevk aldım. Teşekkür ederim.
unpairestgood

43

Her şeyden önce, ilk ikisini karşılaştırmak için sorguları alt sorgularla ayırmanız gerekir:

  1. her zaman birleşimlerle yazılmış karşılık gelen eşdeğer sorguya sahip bir alt sorgu sınıfı
  2. birleştirmeler kullanılarak yeniden yazılamayan bir alt sorgu sınıfı

Birinci sorgu sınıfı için iyi bir RDBMS, birleştirme ve alt sorguları eşdeğer olarak görür ve aynı sorgu planları üretir.

Bugünlerde mysql bile bunu yapıyor.

Yine de, bazen değil, ama bu her zaman kazanacak anlamına gelmez - mysql gelişmiş performans alt sorgular kullanırken vakaları vardı. (Örneğin, mysql planlayıcısının maliyeti doğru bir şekilde tahmin etmesini engelleyen bir şey varsa ve planlayıcı birleştirme varyantını ve alt sorgu varyantını aynı görmüyorsa, alt sorgular belirli bir yolu zorlayarak eklemlerden daha iyi performans gösterebilir).

Sonuç, hangisinin daha iyi performans göstereceğinden emin olmak istiyorsanız, sorgularınızı hem birleştirme hem de alt sorgu varyantları için test etmeniz gerektiğidir.

İkinci sınıf için karşılaştırma anlamlı değildir, çünkü bu sorgular birleştirmeler kullanılarak yeniden yazılamaz ve bu durumlarda alt sorgular gerekli görevleri yerine getirmenin doğal bir yoludur ve bunlara karşı ayrımcılık yapmamalısınız.


1
birleştirmelere dönüştürülemeyen alt sorgular kullanılarak yazılmış bir sorgu örneği sağlayabilir misiniz?
Zahra

24

Alıntılanan cevaplarda vurgulananların , belirli (kullanım) davalardan kaynaklanabilecek kopyalar ve sorunlu sonuçlar meselesi olduğunu düşünüyorum .

(Marcelo Cantos bundan bahsetmesine rağmen)

SQL'deki Stanford Lagunita kurslarından örnek vereceğim.

Öğrenci Masası

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

Tablo Uygula

(belirli üniversitelere ve bölümlere yapılan başvurular)

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

Başvuran öğrencilerin GPA puanlarını bulmaya çalışalım CSAnadal programına (üniversiteye bakılmaksızın)

Bir alt sorgu kullanma:

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

Bu sonuç kümesi için ortalama değer:

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

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

Bir birleştirme kullanma:

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

bu sonuç kümesi için ortalama değer:

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

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

İkinci denemenin, ortalama değerin hesaplanması için kopyaları saydığı göz önüne alındığında, kullanım durumumuzda yanıltıcı sonuçlar verdiği açıktır. Kullanımı olduğu da açıktır distincttabanlı açıklamada edecek - katıl ile değil o yanlışlıkla üç olaylardan birini tutacak göz önüne alındığında, sorunu ortadan 3.9skor. Doğru durum, aşağıdakilerin İKİ (2) oluşumunu hesaba katmaktır .3.9 gerçekte sahip olduğumuz puanın İKİ (2) sorgu ölçütlerimize uyan öğrencimiz olduğu .

Bazı durumlarda, bir alt sorgunun performans sorunlarının yanı sıra en güvenli yol olduğu görülmektedir.


Bence burada bir alt sorgu kullanamazsınız. Bu mantıksal olarak da kullanabileceğiniz bir durum değildir, ancak teknik uygulaması nedeniyle yanlış bir cevap verilir. CS'ye ait olmayan bir öğrenci IN puan listesinde yer alan 3.9 puan alabildiğinden, bu bir alt sorgu kullanamayacağınız bir durumdur. Alt sorgu yürütüldüğünde CS bağlamı kaybolur, bu mantıksal olarak istediğimiz şey değildir. Yani bu her ikisinin de kullanılabileceği iyi bir örnek değil. Neyse ki farklı bir veri kümesi için doğru sonuç verse bile, alt sorgu kullanımı bu kullanım durumu için kavramsal / mantıksal olarak yanlıştır.
Saurabh Patil

22

SQL Server için MSDN Belgeleri diyor

Alt sorgular içeren birçok Transact-SQL deyimi alternatif olarak birleştirme olarak formüle edilebilir. Diğer sorular sadece alt sorgular ile sorulabilir. Transact-SQL'de, genellikle bir alt sorgu içeren bir deyim ile içermeyen anlamsal olarak eşdeğer bir sürüm arasında hiçbir performans farkı yoktur. Bununla birlikte, varoluşun kontrol edilmesi gereken bazı durumlarda, birleştirme daha iyi performans sağlar. Aksi takdirde, iç içe sorgu, dış sorgunun her bir sonucu için kopyaların ortadan kaldırılmasını sağlamak üzere işlenmelidir. Bu gibi durumlarda, birleştirme yaklaşımı daha iyi sonuçlar verir.

yani bir şeye ihtiyacınız varsa

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

bunun yerine join kullanmayı deneyin. Diğer durumlarda, hiçbir fark yaratmaz.

Diyorum ki: Fonksiyonlar yaratmak Alt sorgular için , dağınıklık sorununu ortadan kaldırır ve alt sorgular için ek mantık uygulamasını sağlar. Bu nedenle, mümkün olduğunda alt sorgular için işlevler oluşturmanızı öneririm.

Koddaki karmaşa büyük bir sorundur ve endüstri on yıllardır bundan kaçınmak için çalışmaktadır.


9
Alt sorguları işlevlerle değiştirmek bazı RDBMS'de (ör. Oracle) performans açısından çok kötü bir fikirdir, bu nedenle mümkün olan her yerde işlevler yerine tam tersi alt sorguları / birleştirmeleri kullanmanızı öneririm.
Frank Schmitt

3
@FrankSchmitt lütfen argümanınızı referanslarla destekleyin.
Uğur Gümüşhan

2
Varlığını kontrol etseniz bile birleştirme yerine bir alt sorgu kullanmanız gereken durumlar da vardır: eğer kontrol ederseniz NOT EXISTS. Bir NOT EXISTSaşkın kazanç LEFT OUTER JOIN çeşitli nedenlerle: preformance ve okunabilirliği (nulable kolonların durumunda)-hata emniyeti. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter

16

Eski bir Mambo CMS'den çok büyük bir veritabanında çalıştırın:

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

0 saniye

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

~ 3 saniye

Bir EXPLAIN, aynı sayıda satırı incelediklerini gösterir, ancak biri 3 saniye sürer ve biri anında yakındır. Hikayeden çıkarılacak ders? Performans önemliyse (ne zaman değil?), Birden fazla yol deneyin ve hangisinin en hızlı olduğunu görün.

Ve...

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

0 saniye

Yine aynı sonuçlar, aynı sayıda satır incelendi. Tahminimce DISTINCT mos_content.catid'in anlaşılması DISTINCT mos_categories.id'den çok daha uzun sürüyor.


1
son satırda işaret etmeye çalıştığınız şey hakkında daha fazla bilgi edinmek istiyorum "Sanırım DISTINCT mos_content.catid DISTINCT mos_categories.id daha anlamaya çok daha uzun sürüyor." . Bir kimliğin yalnızca adlandırılması idve benzer bir adın olmaması gerektiğini mi söylüyorsunuz catid? Benim db erişimlerini optimize etmek çalışıyor, ve sizin öğrenme yardımcı olabilir.
bool.dev

2
bu durumda SQL IN kullanmak kötü bir uygulamadır ve hiçbir şey kanıtlamaz.
Uğur Gümüşhan

15

İki vaka gibi gözlemime göre, bir tablonun 100.000'den az kaydı varsa, birleştirme hızlı çalışacaktır.

Ancak bir tablonun 100.000'den fazla kaydı olması durumunda bir alt sorgu en iyi sonuçtur.

Sorgu altında oluşturulan 500.000 kayıtları olan bir tablo var ve sonuç süresi gibi

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

Sonuç: 13.3 Saniye

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

Sonuç: 1.65 Saniye


Katılıyorum, bazen sorguyu kırmak da işe yarıyor, milyon kayıtlarınız olduğunda, birleştirmeleri kullanmak istemiyorsunuz çünkü sonsuza dek sürüyorlar. Aksine kodda işlemek ve kodda harita daha iyidir.
user1735921

1
Birleştirmeler yeterince hızlı çalışmıyor, bir dizini kaçırıyor olabilirsiniz. Query Analyzer, gerçek performansı karşılaştırmada oldukça yardımcı olabilir.
digital.aaron

Ajay Gajera'ya katılıyorum, bunu kendim için gördüm.
user1735921

14
Farklı sonuçlar veren iki sorgunun performansını karşılaştırmak ne kadar mantıklı?
Paul Spiegel

Evet, bunlar farklı sorgular ancak aynı sonucu döndürüyor
Neo

12

Alt sorgular genellikle tek bir satırı atomik değer olarak döndürmek için kullanılır, ancak IN anahtar sözcüğüyle birden çok satıra karşı değerleri karşılaştırmak için kullanılabilirler. Hedef listesi, WHERE yantümcesi vb. Dahil olmak üzere bir SQL ifadesinde neredeyse tüm anlamlı noktalara izin verilir. Basit bir alt sorgu arama koşulu olarak kullanılabilir. Örneğin, bir çift tablo arasında:

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

Alt sorgu sonuçlarında normal değer operatörünün kullanılmasının yalnızca bir alanın döndürülmesi gerektiğini unutmayın. Diğer değerler kümesinde tek bir değerin varlığını kontrol etmekle ilgileniyorsanız IN kullanın:

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

Bu, birleştirme koşulu, tablo B'de vb. Eşleşen bir kayıt bulamasa bile, A ve B tablolarından bir şeylere katılmak istediğiniz bir SOL-JOIN demekinden açıkça farklıdır.

Hız konusunda endişeleniyorsanız, veritabanınızı kontrol etmeniz ve iyi bir sorgu yazmanız ve performansta önemli bir fark olup olmadığını görmeniz gerekir.


11

MySQL sürümü: 5.5.28-0ubuntu0.12.04.2-log

Ayrıca JOIN'in MySQL'deki bir alt sorgudan her zaman daha iyi olduğu izlenimini edindim, ancak EXPLAIN bir karar vermenin daha iyi bir yoludur. Alt sorguların JOIN'lerden daha iyi çalıştığı bir örnek.

İşte benim 3 alt sorgu ile benim sorgu:

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;

EXPLAIN şunu gösterir:

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

JOINs ile aynı sorgu:

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;

ve çıktı:

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

Karşılaştırması rowsSütunun farkı söyler ve JOINs ile yapılan sorguUsing temporary; Using filesort .

Tabii ki her iki sorguyu çalıştırdığımda, ilki 0.02 saniye içinde yapılır, ikincisi 1 dakika sonra bile tamamlanmaz, bu nedenle EXPLAIN bu sorguları düzgün bir şekilde açıkladı.

list_tagTabloda INNER JOIN yoksa , yani kaldırırsam

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

ilk sorgudan ve buna göre:

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

ikinci sorgudan sonra, EXPLAIN her iki sorgu için aynı sayıda satır döndürür ve her iki sorgu da eşit derecede hızlı çalışır.


Ben benzer bir durum var, ama sizinkinden daha fazla
katılma

Oracle veya PostgreSQL'de denerdim: VE VAR DEĞİL (list_tag'DEN 1 SEÇİN list_id = l.list_id VE (43, 55, 246403 içinde tag_id))
David Aldridge

11

Alt sorgular, toplama işlevlerini anında hesaplama yeteneğine sahiptir. Örneğin, kitabın minimum fiyatını bulun ve bu fiyatla satılan tüm kitapları edinin. 1) Alt Sorguları Kullanma:

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

2) JOIN'leri kullanma

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;


6
-1 Bu, bir alt sorgu kullandığınız ve her iki örneğe katıldığınız için yanıltıcıdır. Alt sorguyu, en düşük sipariş fiyatını belirlemek için ikinci bir sorguya çektiğinizden, veritabanı tam olarak aynı şeyi yapacağından. Ayrıca, bir alt sorgu kullanarak birleştirmeyi yeniden yazmazsınız; her iki sorgu da birleştirme kullanır. Sen olan alt sorgular toplama işlevleri sağlar, ancak bu örnek gerçeği göstermek etmediğini doğru.
David Harkness

David ile aynı fikirdeyim ve minimum fiyatı almak için grubunu kullanabilirsiniz.
user1735921

9
  • Genel bir kural, çoğu durumda birleşmelerin daha hızlı olmasıdır (% 99).
  • Daha fazla veri tablosu varsa, alt sorgular yavaşlar.
  • Veri tabloları ne kadar azsa, alt sorguların birleştirmelerle eşdeğer hızı vardır .
  • Alt sorgular daha basit, anlaşılması ve okunması daha kolaydır.
  • Web ve uygulama çerçevelerinin çoğu ve "ORM" leri ve "Etkin kayıt" ları alt sorgularla sorgular oluşturur , çünkü alt sorgularla sorumluluğu bölmek, kodu korumak vb.
  • Daha küçük web siteleri veya uygulamalar için alt sorgular uygundur, ancak daha büyük web siteleri ve uygulamalar için , sorguda birçok alt sorgu kullanılıyorsa, özellikle sorguları birleştirmek için oluşturulan sorguları yeniden yazmanız gerekir .

Bazı insanlar "Bazı RDBMS bir yazabiliriz ki alt sorgu a katılmak ya da katılmak a alt sorgu . O bir diğerinden daha hızlı olduğunu düşündüğünde", ama bu ifade ile komplike sorguları için değil elbette, basit durumlar için geçerlidir alt sorgular hangi aslında a neden performans sorunları.


> ama bu ifade basit durumlar için geçerlidir Ben ya RDBMS tarafından "JOIN" için yeniden yazılabilir ya da burada alt sorgular uygun karmaşık bir durum olduğunu anlıyorum. :-) ORM'lerde güzel bir nokta. Bunun en büyük etkisi olduğunu düşünüyorum.
19'da pilat

4

Fark sadece ikinci birleştirme tablosunun birincil tablodan önemli ölçüde daha fazla veriye sahip olması durumunda görülür. Aşağıdaki gibi bir deneyim yaşadım ...

Yüz bin girişli bir kullanıcı masamız vardı ve üyelik verileri (dostluk) yaklaşık 3 bin bin giriş vardı. Arkadaşlarınızı ve verilerini almak için birleştirme ifadesiydi, ancak büyük bir gecikmeyle. Ancak üyelik tablosunda çok az miktarda verinin bulunduğu yerlerde iyi çalışıyordu. Bir alt sorgu kullanmak için değiştirdikten sonra iyi çalıştı.

Ancak bu arada birleştirme sorguları birincil tablodan daha az girdiye sahip diğer tablolarla çalışır.

Bu yüzden katılmak ve alt sorgu deyimleri iyi çalışıyor ve veri ve duruma bağlıdır düşünüyorum.


3

Bu günlerde, birçok dbs alt sorguları ve birleştirmeleri optimize edebilir. Böylece, sorgunuzu açıklamak ve hangisinin daha hızlı olduğunu görmek için incelemeniz yeterlidir. Performansta çok fazla fark yoksa, basit ve anlaşılması daha kolay oldukları için alt sorguyu kullanmayı tercih ederim.


1

Ben sadece aynı sorunu düşünüyorum, ama FROM bölümünde alt sorgu kullanıyorum. Bağlanmak ve büyük tablolardan sorgu gerekir, "köle" tablo 28 milyon kayıt var ama sonuç sadece 128 kadar küçük sonuç büyük veri! Üzerinde MAX () işlevi kullanıyorum.

İlk önce LEFT JOIN kullanıyorum çünkü doğru yol olduğunu düşünüyorum, mysql en iyi duruma getirebilir vs.

SOLA KATIL çalışma zamanı: 1.12s SUB-SELECT çalışma zamanı: 0.06s

Alt seçime katılmaktan 18 kat daha hızlı! Sadece chokito'da Alt seçim korkunç görünüyor ama sonuç ...


-1

Join kullanarak sorgunuzu hızlandırmak istiyorsanız:

"İç birleşim / birleşim" için, "ON" koşulunda kullanmayın. Örneğin:

     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'

"Sol / Sağ Birleştirme" için, "ON" koşulunda kullanmayın, çünkü sol / sağ birleştirme kullanırsanız, herhangi bir tablo için tüm satırları alır. Bu nedenle, "Nerede" koşulunu kullanmaya çalışın


Bu, SQL sunucusuna ve sorgunun karmaşıklığına bağlıdır. Birçok SQL uygulaması, en iyi performans için bunun gibi basit sorguları optimize eder. Belki de cevabı iyileştirmek için bu davranışın gerçekleştiği örnek bir sunucu adı ve sürümü sağlayın?
Trendfischer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.