Birden çok tablodan SQL sorgusu döndürme verileri


434

Aşağıdakileri bilmek istiyorum:

  • veritabanımdaki birden çok tablodan veri nasıl alınır?
  • Bunu yapmak için ne tür yöntemler var?
  • birleşimler ve sendikalar nedir ve birbirlerinden nasıl farklıdırlar?
  • Her birini diğerlerine kıyasla ne zaman kullanmalıyım?

Bunu (örneğin - PHP) uygulamamda kullanmayı planlıyorum, ancak veritabanına karşı birden çok sorgu çalıştırmak istemiyorum, tek bir sorguda birden çok tablodan veri almak için hangi seçeneklere ihtiyacım var?

Not: Ben sürekli PHP kuyruğuna rastlamak çok sayıda soru üzerinde iyi yazılmış bir kılavuz bağlamak mümkün olduğu için bu yazıyorum, bu yüzden bir cevap gönderirken daha fazla ayrıntı için bu bağlantı olabilir.

Cevaplar aşağıdakileri kapsamaktadır:

  1. Bölüm 1 - Birleşimler ve Birlikler
  2. Bölüm 2 - Alt sorgular
  3. Bölüm 3 - Püf Noktaları ve Verimli Kod
  4. Bölüm 4 - From Clause Alt Sorgular
  5. Bölüm 5 - John's Tricks Karışık Çanta

Yanıtlar:


469

Bölüm 1 - Birleşimler ve Birlikler

Bu cevap şunları kapsamaktadır:

  1. Bölüm 1
    • Bir iç birleşim kullanarak iki veya daha fazla tabloya katılma ( Ek bilgi için wikipedia girişine bakın )
    • Birlik sorgusu nasıl kullanılır?
    • Sol ve Sağ Dış Birleşimler (bu stackOverflow yanıtı , birleştirme türlerini tanımlamak için mükemmeldir)
    • Kesişen sorgular (ve veritabanınız onları desteklemiyorsa bunları nasıl çoğaltacağınız) - bu bir SQL-Server ( bkz. Bilgi ) işlevidir ve her şeyi ilk başta yazmamın nedeninin bir parçasıdır .
  2. Bölüm 2
    • Alt sorgular - ne oldukları, nerede kullanılabilecekleri ve nelere dikkat edilecekleri
    • Kartezyen AKA'ya katıldı - Ah, sefalet!

Veritabanındaki birden çok tablodan veri almanın birkaç yolu vardır. Bu cevapta ANSI-92 birleştirme sözdizimi kullanacağım. Bu eski ANSI-89 sözdizimi kullanın orada diğer öğreticiler bir dizi farklı olabilir (eğer 89 için kullanılıyorsa ve çok daha az sezgisel görünebilir - ama tüm söyleyebileceğim denemek için) olduğu gibi çok daha kolay sorguların ne zaman karmaşıklaşmaya başladığını anlamak. Neden kullanmalıyım? Performans kazancı var mı? Kısa cevap hayır, ama bir sen buna alışması kez daha kolay okunur. Bu sözdizimini kullanarak diğer kişiler tarafından yazılan sorguları okumak daha kolaydır.

Ayrıca, hangi arabaların mevcut olduğunu takip etmek için bir veritabanına sahip küçük bir avlu kavramını kullanacağım. Sahibi, BT Bilgisayarı adamı olarak sizi işe aldı ve ona bir şapka düştüğünde istediği verileri bırakabilmenizi bekliyor.

Son tablo tarafından kullanılacak bir dizi arama tablosu yaptım. Bu bize çalışmak için makul bir model verecektir. Başlamak için, sorgularımı aşağıdaki yapıya sahip örnek bir veritabanına çalıştıracağım. Başlarken yapılan yaygın hataları düşünmeye ve onlarla neyin yanlış gittiğini açıklamaya çalışacağım - tabii ki bunları nasıl düzelteceğimizi göstereceğim.

İlk tablo sadece bir renk listesidir, böylece araba bahçesinde hangi renklere sahip olduğumuzu biliriz.

mysql> create table colors(id int(3) not null auto_increment primary key, 
    -> color varchar(15), paint varchar(10));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from colors;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

mysql> insert into colors (color, paint) values ('Red', 'Metallic'), 
    -> ('Green', 'Gloss'), ('Blue', 'Metallic'), 
    -> ('White' 'Gloss'), ('Black' 'Gloss');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from colors;
+----+-------+----------+
| id | color | paint    |
+----+-------+----------+
|  1 | Red   | Metallic |
|  2 | Green | Gloss    |
|  3 | Blue  | Metallic |
|  4 | White | Gloss    |
|  5 | Black | Gloss    |
+----+-------+----------+
5 rows in set (0.00 sec)

Markalar tablosu, otomobilin dışarıda satabileceği farklı markaları tanımlar.

mysql> create table brands (id int(3) not null auto_increment primary key, 
    -> brand varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from brands;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| brand | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> insert into brands (brand) values ('Ford'), ('Toyota'), 
    -> ('Nissan'), ('Smart'), ('BMW');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from brands;
+----+--------+
| id | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  3 | Nissan |
|  4 | Smart  |
|  5 | BMW    |
+----+--------+
5 rows in set (0.00 sec)

Model tablosu farklı araba tiplerini kapsayacaktır, bunun için gerçek araba modelleri yerine farklı araba tiplerini kullanmak daha basit olacaktır.

mysql> create table models (id int(3) not null auto_increment primary key, 
    -> model varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from models;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| model | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> insert into models (model) values ('Sports'), ('Sedan'), ('4WD'), ('Luxury');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from models;
+----+--------+
| id | model  |
+----+--------+
|  1 | Sports |
|  2 | Sedan  |
|  3 | 4WD    |
|  4 | Luxury |
+----+--------+
4 rows in set (0.00 sec)

Ve son olarak, diğer tüm tabloları birleştirmek için, her şeyi birbirine bağlayan tablo. Kimlik alanı aslında arabaları tanımlamak için kullanılan benzersiz lot numarasıdır.

mysql> create table cars (id int(3) not null auto_increment primary key, 
    -> color int(3), brand int(3), model int(3));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from cars;
+-------+--------+------+-----+---------+----------------+
| Field | Type   | Null | Key | Default | Extra          |
+-------+--------+------+-----+---------+----------------+
| id    | int(3) | NO   | PRI | NULL    | auto_increment |
| color | int(3) | YES  |     | NULL    |                |
| brand | int(3) | YES  |     | NULL    |                |
| model | int(3) | YES  |     | NULL    |                |
+-------+--------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> insert into cars (color, brand, model) values (1,2,1), (3,1,2), (5,3,1), 
    -> (4,4,2), (2,2,3), (3,5,4), (4,1,3), (2,2,1), (5,2,3), (4,5,1);
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

mysql> select * from cars;
+----+-------+-------+-------+
| id | color | brand | model |
+----+-------+-------+-------+
|  1 |     1 |     2 |     1 |
|  2 |     3 |     1 |     2 |
|  3 |     5 |     3 |     1 |
|  4 |     4 |     4 |     2 |
|  5 |     2 |     2 |     3 |
|  6 |     3 |     5 |     4 |
|  7 |     4 |     1 |     3 |
|  8 |     2 |     2 |     1 |
|  9 |     5 |     2 |     3 |
| 10 |     4 |     5 |     1 |
+----+-------+-------+-------+
10 rows in set (0.00 sec)

Bu, bize farklı türlerdeki birleşimlerin aşağıdaki örneklerini kapsamak için yeterli veri (umarım) verecek ve aynı zamanda onları değerli kılmak için yeterli veri verecektir.

Bu nedenle, patron sahip olduğu tüm spor otomobillerin kimliklerini bilmek istiyor .

Bu basit bir iki masa birleşimidir. Modeli ve tabloyu mevcut stok ile tanımlayan bir tablonuz var. Gördüğünüz gibi, veri modelsütun carstabloya ilgilidir modelssütununun carselimizdeki masaya. Şimdi, biz modelleri tablosu bir kimliği olduğunu biliyoruz 1için Sportsöylesine katılmak yazmanıza olanak tanır.

select
    ID,
    model
from
    cars
        join models
            on model=ID

Yani bu sorgu iyi görünüyor değil mi? İki tabloyu belirledik ve ihtiyacımız olan bilgileri içerdik ve hangi sütunlara katılacağınızı doğru bir şekilde tanımlayan bir birleştirme kullandık.

ERROR 1052 (23000): Column 'ID' in field list is ambiguous

Ah hayır! İlk sorgumuzda bir hata! Evet, ve bir erik. Gördüğünüz gibi, sorgu gerçekten doğru sütunlara sahip, ancak bazıları her iki tabloda da var, bu yüzden veritabanı ne demek istediğimiz ve nerede olduğumuzu karıştırıyor. Bunu çözmek için iki çözüm var. Birincisi hoş ve basit, tableName.columnNameveritabanına tam olarak ne demek istediğimizi söylemek için kullanabiliriz :

select
    cars.ID,
    models.model
from
    cars
        join models
            on cars.model=models.ID

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
|  2 | Sedan  |
|  4 | Sedan  |
|  5 | 4WD    |
|  7 | 4WD    |
|  9 | 4WD    |
|  6 | Luxury |
+----+--------+
10 rows in set (0.00 sec)

Diğeri muhtemelen daha sık kullanılır ve masa takma adı olarak adlandırılır. Bu örnekteki tabloların güzel ve kısa basit isimleri vardır, ancak böyle bir şey yazmak KPI_DAILY_SALES_BY_DEPARTMENTmuhtemelen hızlı bir şekilde eskidir, bu nedenle basit bir yol tabloyu şu şekilde adlandırmaktır:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID

Şimdi, isteğe geri dönelim. Gördüğünüz gibi, ihtiyacımız olan bilgilere sahibiz, ancak istenmeyen bilgilerimiz de var, bu yüzden ifadeye yalnızca Spor arabaları istendiği gibi bir yer cümlesi eklememiz gerekiyor. Tablo adlarını defalarca kullanmak yerine tablo diğer yöntemini tercih ettiğim için, bu noktadan sonra buna yapışacağım.

Açıkçası, sorgumuza where cümlesi eklememiz gerekiyor. Spor arabaları ID=1veya ile tanımlayabiliriz model='Sports'. Kimlik endekslendiğinde ve birincil anahtar (ve daha az yazarak) olduğundan, sorgumuzda bunu kullanalım.

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Bingo! Patron mutlu. Tabii ki, bir patron olmak ve istediği şeyden asla mutlu olmamak, bilgiye bakar, sonra renkleri de istediğimi söylüyor .

Tamam, bu yüzden sorgumuzun zaten iyi bir parçası var, ancak renkler olan üçüncü bir tablo kullanmamız gerekiyor. Şimdi, ana bilgi tablomuz carsaraç renk kimliğini saklar ve bu, renk kimliği sütununa geri bağlanır. Yani, orijinaline benzer şekilde üçüncü bir masaya katılabiliriz:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Kahretsin, tablo doğru bir şekilde birleştirilmiş ve ilgili sütunlar birbirine bağlı olmasına rağmen, yeni bağladığımız yeni tablodan gerçek bilgileri almayı unuttuk .

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
+----+--------+-------+
4 rows in set (0.00 sec)

Tamam, bu bir an için sırtımızdan çıkan patron. Şimdi, bunlardan bazılarını biraz daha ayrıntılı olarak açıklayalım. Gördüğünüz gibi, fromifademizdeki cümle ana tabloyu bağlar (genellikle arama veya boyut tablosu yerine bilgi içeren bir tablo kullanırım. Sorgu, tüm tablolar ile de çalışır, ancak daha az anlamlı olur birkaç ay içinde okumak için bu sorguya geri dönüyoruz, bu yüzden iyi ve anlaşılması kolay bir sorgu yazmaya çalışmak en iyisidir - sezgisel olarak yerleştirin, her şeyin açık olması için güzel girinti kullanın Başkalarına öğretmeye devam ederseniz, bu özellikleri sorgularına yerleştirmeye çalışın - özellikle de sorun gidermeye başlayacaksanız.

Giderek daha fazla tabloyu bu şekilde bağlamaya devam etmek tamamen mümkündür.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

İfadede birden fazla sütuna katılmak isteyebileceğimiz bir tablo eklemeyi unuttum, ancak joinbir örnek. Eğer modelstablo bu nedenle markaya özel modeller vardı ve ayrıca adında bir sütun vardı brandgeri bağlantılı brandstabloda IDalanında, bu şekilde yapılabilir:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
            and b.brand=d.ID
where
    b.ID=1

Gördüğünüz gibi, yukarıdaki sorgu yalnızca birleştirilmiş tabloları ana carstabloya bağlamakla kalmaz, aynı zamanda zaten birleştirilmiş tablolar arasındaki birleştirmeleri de belirtir. Bu yapılmadıysa, sonuç kartezyen bir birleşim olarak adlandırılır - dba kötüdür. Kartezyen birleştirme, bilgi veritabanına sonuçları nasıl sınırlandıracağını söylemediği için satırların döndürüldüğü yerdir, bu nedenle sorgu ölçütlere uyan tüm satırları döndürür .

Kartezyen birleştirme örneği vermek için aşağıdaki sorguyu çalıştıralım:

select
    a.ID,
    b.model
from
    cars a
        join models b

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  1 | Sedan  |
|  1 | 4WD    |
|  1 | Luxury |
|  2 | Sports |
|  2 | Sedan  |
|  2 | 4WD    |
|  2 | Luxury |
|  3 | Sports |
|  3 | Sedan  |
|  3 | 4WD    |
|  3 | Luxury |
|  4 | Sports |
|  4 | Sedan  |
|  4 | 4WD    |
|  4 | Luxury |
|  5 | Sports |
|  5 | Sedan  |
|  5 | 4WD    |
|  5 | Luxury |
|  6 | Sports |
|  6 | Sedan  |
|  6 | 4WD    |
|  6 | Luxury |
|  7 | Sports |
|  7 | Sedan  |
|  7 | 4WD    |
|  7 | Luxury |
|  8 | Sports |
|  8 | Sedan  |
|  8 | 4WD    |
|  8 | Luxury |
|  9 | Sports |
|  9 | Sedan  |
|  9 | 4WD    |
|  9 | Luxury |
| 10 | Sports |
| 10 | Sedan  |
| 10 | 4WD    |
| 10 | Luxury |
+----+--------+
40 rows in set (0.00 sec)

Tanrım, bu çirkin. Ancak, veritabanı ile ilgili olarak, tam olarak ne istenir. Sorguda, biz istedi IDgelen carsve modelgelen models. Biz belirtmedi Ancak, nasıl tabloları katılmak için, veritabanı karşıladığında her ilk tablodan satır her ikinci tablodan sıranın.

Tamam, patron geri döndü ve tekrar daha fazla bilgi istiyor. Aynı listeyi istiyorum, ama aynı zamanda 4WD'leri de içeriyorum .

Ancak bu, bunu başarmanın iki farklı yoluna bakmak için bize harika bir bahane verir. Nereye bu maddeye başka bir koşul ekleyebiliriz:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
    or b.ID=3

Yukarıdakiler mükemmel bir şekilde çalışsa da, farklı bir şekilde bakalım, bu bir unionsorgunun nasıl çalışacağını göstermek için harika bir bahane .

Aşağıdakilerin tüm Spor arabalarını iade edeceğini biliyoruz:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Ve aşağıdaki tüm 4WD'leri döndürür:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

Bu nedenle, union allaralarına bir cümle eklenerek , ikinci sorgunun sonuçları ilk sorgunun sonuçlarına eklenir.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
union all
select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
|  5 | 4WD    | Green |
|  7 | 4WD    | White |
|  9 | 4WD    | Black |
+----+--------+-------+
7 rows in set (0.00 sec)

Gördüğünüz gibi, önce ilk sorgunun sonuçları, ardından ikinci sorgunun sonuçları döndürülür.

Bu örnekte, elbette ilk sorguyu kullanmak çok daha kolay olurdu, ancak unionsorgular belirli durumlar için harika olabilir. Bunlar, kolayca birleştirilemeyen tablolardan tablolardan belirli sonuçlar döndürmek için harika bir yoldur - veya bu nedenle tamamen ilgisiz tablolar. Ancak, uyulması gereken birkaç kural vardır.

  • İlk sorgudaki sütun türleri, aşağıdaki diğer tüm sorgulardaki sütun türleriyle eşleşmelidir.
  • İlk sorgudaki sütunların adları sonuç kümesinin tamamını tanımlamak için kullanılacaktır.
  • Her sorgudaki sütun sayısı aynı olmalıdır.

Şimdi ve ile arasındaki farkın ne olduğunu merak ediyor olabilirsiniz . Bir sorgu yinelenenleri kaldıracak, bir ise olmayacak. Bu kullanırken küçük bir performans isabet olduğunu anlama geliyor üzerinde ama sonuç değerinde o olabilir - Ben bu olsa içinde böyle şeylere üzerinde spekülasyon olmaz.unionunion allunionunion allunionunion all

Bu notta, burada bazı ek notlara dikkat etmek gerekebilir.

  • Sonuçları sipariş etmek istersek, bir kullanabiliriz, order byancak artık takma adı kullanamazsınız. Yukarıdaki sorguda, her iki sorguda da aynı takma ad kullanılmış olsa bile order by a.ID, sonuç eklenmesi bir hatayla sonuçlanır - sonuçlar söz konusu olduğunda, sütun IDyerine çağrılır a.ID.
  • Sadece bir order bycümle yapabiliriz ve bu son cümle gibi olmalıdır.

Sonraki örnekler için, tablolarımıza birkaç satır daha ekliyorum.

HoldenMarkalar tablosuna ekledim . Ben de renkler tablosunda hiçbir referansı olan - değerine carssahip bir satır ekledim .color12

Tamam, patron tekrar geri döndü, havlıyor istekleri - * Taşımakta olduğumuz her markanın bir sayısını ve araba sayısını istiyorum! `` - Tipik olarak, tartışmamızın ilginç bir bölümüne gidiyoruz ve patron daha fazla iş istiyor .

Doğru, yani ilk yapmamız gereken olası markaların tam bir listesini almak.

select
    a.brand
from
    brands a

+--------+
| brand  |
+--------+
| Ford   |
| Toyota |
| Nissan |
| Smart  |
| BMW    |
| Holden |
+--------+
6 rows in set (0.00 sec)

Şimdi, bunu otomobil masamıza kattığımızda şu sonucu alıyoruz:

select
    a.brand
from
    brands a
        join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Nissan |
| Smart  |
| Toyota |
+--------+
5 rows in set (0.00 sec)

Tabii ki bu bir sorun - Holdeneklediğim güzel markanın herhangi bir sözünü görmüyoruz .

Bunun nedeni bir birleştirmenin her iki tabloda da eşleşen satırları aramasıdır . Otomobillerde herhangi bir veri Holdenolmadığı için iade edilmez. Birleştirme kullanabileceğimiz yer burası outer. Bu , diğer tabloda eşleştirilip eşleştirilmemelerine bakılmaksızın tüm sonuçları bir tablodan döndürür :

select
    a.brand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Holden |
| Nissan |
| Smart  |
| Toyota |
+--------+
6 rows in set (0.00 sec)

Artık buna sahibiz, bir sayı elde etmek ve patronu bir anlığına arkadan almak için hoş bir toplama işlevi ekleyebiliriz.

select
    a.brand,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+--------------+
| brand  | countOfBrand |
+--------+--------------+
| BMW    |            2 |
| Ford   |            2 |
| Holden |            0 |
| Nissan |            1 |
| Smart  |            1 |
| Toyota |            5 |
+--------+--------------+
6 rows in set (0.00 sec)

Ve bununla birlikte, patron kaçıyor.

Şimdi, bunu daha ayrıntılı olarak açıklamak için, dış birleşimler leftveya righttipinde olabilir. Sol veya Sağ hangi tablonun tam olarak dahil edileceğini tanımlar . A left outer join, soldaki tablodaki tüm satırları içerirken (tahmin ettiniz) a right outer join, sağdaki tablodaki tüm sonuçları sonuçlara getirir.

Bazı veritabanları, her iki tablodan full outer joinda sonuçları (eşleşen olsun olmasın) geri getirmesine izin verir , ancak bu tüm veritabanlarında desteklenmez.

Şimdi, muhtemelen bu noktada anlıyorum, bir sorguda birleştirme türlerini birleştirip birleştiremeyeceğinizi merak ediyorsunuz - ve cevap evet, kesinlikle yapabilirsiniz.

select
    b.brand,
    c.color,
    count(a.id) as countOfBrand
from
    cars a
        right outer join brands b
            on b.ID=a.brand
        join colors c
            on a.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
+--------+-------+--------------+
9 rows in set (0.00 sec)

Peki, bu beklenen sonuçlar neden olmasın? Çünkü arabalardan markalara dış birleşimi seçmiş olsak da, renklerin birleşiminde belirtilmedi - böylece belirli bir birleşim sadece her iki tabloda eşleşen sonuçları geri getirecektir.

Beklediğimiz sonuçları almak için işe yarayacak sorgu:

select
    a.brand,
    c.color,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
        left outer join colors c
            on b.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Holden | NULL  |            0 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| Toyota | NULL  |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
+--------+-------+--------------+
11 rows in set (0.00 sec)

Gördüğümüz gibi, sorguda iki dış birleşimimiz var ve sonuçlar beklendiği gibi geliyor.

Şimdi, sorduğunuz bu tür diğer birleşimlere ne dersiniz? Kavşaklar ne olacak?

Eh, tüm veritabanları desteklemez, intersectionancak hemen hemen tüm veritabanları bir birleştirme (veya en azından en iyi şekilde yapılandırılmış bir ifade) yoluyla bir kavşak oluşturmanıza izin verecektir.

Bir Kesişim, unionyukarıda tarif edilene benzer bir birleşim türüdür - ancak fark, yalnızca birleşmenin birleştirdiği çeşitli ayrı sorgular arasında özdeş olan (ve özdeş demek istediğim) veri satırlarını döndürmesidir. Yalnızca her açıdan özdeş olan satırlar döndürülür.

Basit bir örnek şöyle olacaktır:

select
    *
from
    colors
where
    ID>2
intersect
select
    *
from
    colors
where
    id<4

Normal bir unionsorgu tablonun tüm satırlarını döndürür (ilk sorgu her şeyi döndürür ID>2ve ikinci her şey sahip olur ID<4), bu da tam küme ile sonuçlanır, ancak kesişen bir sorgu yalnızca id=3her iki ölçütü karşıladığından satır eşleşmesini döndürür .

Şimdi, veritabanınız bir intersectsorguyu desteklemiyorsa , yukarıdakiler aşağıdaki sorgu ile kolayca gerçekleştirilebilir:

select
    a.ID,
    a.color,
    a.paint
from
    colors a
        join colors b
            on a.ID=b.ID
where
    a.ID>2
    and b.ID<4

+----+-------+----------+
| ID | color | paint    |
+----+-------+----------+
|  3 | Blue  | Metallic |
+----+-------+----------+
1 row in set (0.00 sec)

Bir kesişim sorgusunu doğal olarak desteklemeyen bir veritabanı kullanarak iki farklı tablo arasında bir kavşak gerçekleştirmek istiyorsanız , tabloların her sütununda bir birleştirme oluşturmanız gerekir .


2
@Fluffeh Güzel cevaplar. Ben bir öneri var: bir katil SQL Eğitimi yapmak istiyorsanız, sadece Venn diyagramları eklemek için eksik; Onlar sayesinde hemen sol ve sağ birleşimleri anladım. Kişisel istek: Yaygın hatalar / performans belirleme hakkında bir eğitiminiz var mı?
StrayChild01

25
Aman. Kaydırma tekerleğim bozuk. Harika soru ve cevap. Keşke bunu 10 kez oylayabilseydim.
Amal Murali

3
Hehe, olumlu geribildirim için teşekkürler. Yine de ilerlemeye devam edin, bu sadece ilk cevaptı. SO cevabım bir "cevap" sığdırmak için çok uzun olduğunu söyledi, bu yüzden birkaç kullanmak zorunda :)
Fluffeh

7
Dürüst olmak gerekirse, bu cevabın biraz kısaltılması gerektiğini düşünüyorum.
einpoklum

Mükemmel makale. Veritabanı 101. Katıldı
maqs

101

Tamam, bu gönderiyi çok ilginç buldum ve bir sorgu oluşturma hakkındaki bazı bilgilerimi paylaşmak istiyorum. Bu Fluffeh için teşekkürler . Bunu okuyabilen ve yanlış olduğumu düşünebilen diğer kişiler yanıtımı düzenlemek ve eleştirmek için% 101 ücretsizdir. ( Dürüst olmak gerekirse, hatalarımı düzelttiğim için çok minnettarım. )

Sıkça sorulan bazı soruları MySQLetikette yayınlayacağım .


1 Numara ( birden fazla koşulla eşleşen satırlar )

Bu şema verildi

CREATE TABLE MovieList
(
    ID INT,
    MovieName VARCHAR(25),
    CONSTRAINT ml_pk PRIMARY KEY (ID),
    CONSTRAINT ml_uq UNIQUE (MovieName)
);

INSERT INTO MovieList VALUES (1, 'American Pie');
INSERT INTO MovieList VALUES (2, 'The Notebook');
INSERT INTO MovieList VALUES (3, 'Discovery Channel: Africa');
INSERT INTO MovieList VALUES (4, 'Mr. Bean');
INSERT INTO MovieList VALUES (5, 'Expendables 2');

CREATE TABLE CategoryList
(
    MovieID INT,
    CategoryName VARCHAR(25),
    CONSTRAINT cl_uq UNIQUE(MovieID, CategoryName),
    CONSTRAINT cl_fk FOREIGN KEY (MovieID) REFERENCES MovieList(ID)
);

INSERT INTO CategoryList VALUES (1, 'Comedy');
INSERT INTO CategoryList VALUES (1, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Drama');
INSERT INTO CategoryList VALUES (3, 'Documentary');
INSERT INTO CategoryList VALUES (4, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Action');

SORU

Bul tüm filmleri en azından aittir hem Comedy ve Romancekategoriler.

Çözüm

Bu soru bazen çok zor olabilir. Böyle bir sorgu cevap olacak gibi görünebilir:

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName = 'Comedy' AND
        b.CategoryName = 'Romance'

SQLFiddle Demosu

bu kesinlikle çok yanlış çünkü sonuç vermiyor . Bunun açıklaması CategoryName, her satırda yalnızca bir geçerli değerin olmasıdır . Örneğin, ilk koşul true değerini döndürür , ikinci koşul her zaman false olur. Bu nedenle, ANDoperatör kullanarak , her iki koşul da doğru olmalıdır; aksi takdirde yanlış olur. Başka bir sorgu böyle,

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')

SQLFiddle Demosu

ve sonuç hala yanlıştır çünkü en az bir eşleşmesi bulunan kayıtla eşleşir categoryName. Gerçek çözüm film başına rekor örneklerinin sayısını sayarak olurdu . Örnek sayısı, koşulda sağlanan değerlerin toplam sayısıyla eşleşmelidir.

SELECT  a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')
GROUP BY a.MovieName
HAVING COUNT(*) = 2

SQLFiddle Demo (cevap)


2 Numara ( her kayıt için maksimum kayıt )

Şema verildi,

CREATE TABLE Software
(
    ID INT,
    SoftwareName VARCHAR(25),
    Descriptions VARCHAR(150),
    CONSTRAINT sw_pk PRIMARY KEY (ID),
    CONSTRAINT sw_uq UNIQUE (SoftwareName)  
);

INSERT INTO Software VALUES (1,'PaintMe','used for photo editing');
INSERT INTO Software VALUES (2,'World Map','contains map of different places of the world');
INSERT INTO Software VALUES (3,'Dictionary','contains description, synonym, antonym of the words');

CREATE TABLE VersionList
(
    SoftwareID INT,
    VersionNo INT,
    DateReleased DATE,
    CONSTRAINT sw_uq UNIQUE (SoftwareID, VersionNo),
    CONSTRAINT sw_fk FOREIGN KEY (SOftwareID) REFERENCES Software(ID)
);

INSERT INTO VersionList VALUES (3, 2, '2009-12-01');
INSERT INTO VersionList VALUES (3, 1, '2009-11-01');
INSERT INTO VersionList VALUES (3, 3, '2010-01-01');
INSERT INTO VersionList VALUES (2, 2, '2010-12-01');
INSERT INTO VersionList VALUES (2, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 3, '2011-12-01');
INSERT INTO VersionList VALUES (1, 2, '2010-12-01');
INSERT INTO VersionList VALUES (1, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 4, '2012-12-01');

SORU

Her yazılımın en son sürümünü bulun. Aşağıdaki sütunları görüntüle: SoftwareName, Descriptions, LatestVersion( VersionNo sütundan ),DateReleased

Çözüm

Bazı SQL geliştiricileri yanlışlıkla MAX()toplama işlevini kullanır . Böyle yaratma eğilimindedirler,

SELECT  a.SoftwareName, a.Descriptions,
        MAX(b.VersionNo) AS LatestVersion, b.DateReleased
FROM    Software a
        INNER JOIN VersionList b
            ON a.ID = b.SoftwareID
GROUP BY a.ID
ORDER BY a.ID

SQLFiddle Demosu

( RDBMS'nin çoğu, group byyan tümcede birleştirilmiş olmayan sütunların bazılarını belirtmediği için bu sözdizimi hatası oluşturur ) sonuç LatestVersionher yazılımda doğru üretir, ancak açıktır DateReleased. MySQLdesteklemiyor Window Functionsve Common Table Expressionbazı RDBMS'nin zaten yaptığı gibi. Bu soruna geçici bir çözüm oluşturmak , her yazılımda subquerybireysel maksimum alır versionNove daha sonra diğer tablolarda birleştirilir.

SELECT  a.SoftwareName, a.Descriptions,
        b.LatestVersion, c.DateReleased
FROM    Software a
        INNER JOIN
        (
            SELECT  SoftwareID, MAX(VersionNO) LatestVersion
            FROM    VersionList
            GROUP BY SoftwareID
        ) b ON a.ID = b.SoftwareID
        INNER JOIN VersionList c
            ON  c.SoftwareID = b.SoftwareID AND
                c.VersionNO = b.LatestVersion
GROUP BY a.ID
ORDER BY a.ID

SQLFiddle Demo (cevap)


İşte bu kadar. Ben başka hatırlama gibi başka Yakında gönderme olacak SSS üzerinde MySQLetiketinin. Bu küçük makaleyi okuduğunuz için teşekkür ederim. Umarım en azından bundan biraz bilgi sahibi olursun.

GÜNCELLEME 1


Numara 3 ( İki kimlik arasında en son kaydı bulma )

Verilen Şema

CREATE TABLE userList
(
    ID INT,
    NAME VARCHAR(20),
    CONSTRAINT us_pk PRIMARY KEY (ID),
    CONSTRAINT us_uq UNIQUE (NAME)  
);

INSERT INTO userList VALUES (1, 'Fluffeh');
INSERT INTO userList VALUES (2, 'John Woo');
INSERT INTO userList VALUES (3, 'hims056');

CREATE TABLE CONVERSATION
(
    ID INT,
    FROM_ID INT,
    TO_ID INT,
    MESSAGE VARCHAR(250),
    DeliveryDate DATE
);

INSERT INTO CONVERSATION VALUES (1, 1, 2, 'hi john', '2012-01-01');
INSERT INTO CONVERSATION VALUES (2, 2, 1, 'hello fluff', '2012-01-02');
INSERT INTO CONVERSATION VALUES (3, 1, 3, 'hey hims', '2012-01-03');
INSERT INTO CONVERSATION VALUES (4, 1, 3, 'please reply', '2012-01-04');
INSERT INTO CONVERSATION VALUES (5, 3, 1, 'how are you?', '2012-01-05');
INSERT INTO CONVERSATION VALUES (6, 3, 2, 'sample message!', '2012-01-05');

SORU

İki kullanıcı arasındaki en son konuşmayı bulun.

Çözüm

SELECT    b.Name SenderName,
          c.Name RecipientName,
          a.Message,
          a.DeliveryDate
FROM      Conversation a
          INNER JOIN userList b
            ON a.From_ID = b.ID
          INNER JOIN userList c
            ON a.To_ID = c.ID
WHERE     (LEAST(a.FROM_ID, a.TO_ID), GREATEST(a.FROM_ID, a.TO_ID), DeliveryDate)
IN
(
    SELECT  LEAST(FROM_ID, TO_ID) minFROM,
            GREATEST(FROM_ID, TO_ID) maxTo,
            MAX(DeliveryDate) maxDate
    FROM    Conversation
    GROUP BY minFROM, maxTo
)

SQLFiddle Demosu


Müthiş! Bir uyarı John, ilk çözümünüz sadece iki alanda benzersiz bir kısıtlama olduğu için çalışır. Sık karşılaşılan bir soruna yardımcı olmak için daha genel bir çözüm kullanabilirsiniz. Bence tek çözüm için bireysel seçer yapmaktır comedyve romance. Havingo zaman uygun değil ..
nawfal

benzersiz kısıtlama eklenmedi eğer @nawfal gerçekten, daha sonra eklemeniz gerekmez distinctsahip hükmü üzerinde SQLFiddle Demo : D
John Woo

63

Bölüm 2 - Alt sorgular

Tamam, şimdi patron tekrar patladı - marka ile tüm arabalarımızın bir listesini ve bu markanın toplamının toplamını istiyorum!

Bu, SQL hediyeler çantanızdaki bir sonraki numarayı kullanmak için harika bir fırsattır - alt sorgu. Terime aşina değilseniz, alt sorgu, başka bir sorgunun içinde çalışan bir sorgudur. Bunları kullanmanın birçok farklı yolu vardır.

İsteğimiz için, önce her bir aracı ve markayı listeleyecek basit bir sorgu oluşturalım:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID

Şimdi, sadece markaya göre sıralanmış bir dizi araba almak istiyorsak, elbette bunu yazabiliriz:

select
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    b.brand

+--------+-----------+
| brand  | countCars |
+--------+-----------+
| BMW    |         2 |
| Ford   |         2 |
| Nissan |         1 |
| Smart  |         1 |
| Toyota |         5 |
+--------+-----------+

Yani, sayım işlevini orijinal sorgumuza ekleyebilmemiz gerekir mi?

select
    a.ID,
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    a.ID,
    b.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         1 |
|  2 | Ford   |         1 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         1 |
|  6 | BMW    |         1 |
|  7 | Ford   |         1 |
|  8 | Toyota |         1 |
|  9 | Toyota |         1 |
| 10 | BMW    |         1 |
| 11 | Toyota |         1 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Ne yazık ki hayır, bunu yapamayız. Bunun nedeni, araç kimliğini (a.ID sütunu) eklediğimizde, bunu gruba eklememiz gerektiğidir - şimdi sayma işlevi çalıştığında, kimlik başına yalnızca bir kimlik eşleşir.

Ancak burada bir alt sorgu kullanabiliriz - aslında bunun için ihtiyacımız olan sonuçları aynı şekilde döndürecek tamamen farklı iki alt sorgu türü yapabiliriz. Birincisi, sadece alt sorguyu selectmaddeye koymaktır . Bu, her veri satırı aldığımızda, alt sorgunun tükeneceği, bir veri sütunu alacağı ve ardından veri satırımıza yerleştirileceği anlamına gelir.

select
    a.ID,
    b.brand,
    (
    select
        count(c.ID)
    from
        cars c
    where
        a.brand=c.brand
    ) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  2 | Ford   |         2 |
|  7 | Ford   |         2 |
|  1 | Toyota |         5 |
|  5 | Toyota |         5 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 11 | Toyota |         5 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  6 | BMW    |         2 |
| 10 | BMW    |         2 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Ve Bam!, Bu bizi yapar. Yine de fark ederseniz, bu alt sorgunun döndürdüğümüz her veri satırı için çalışması gerekir. Bu küçük örnekte bile, sadece beş farklı otomobil markamız var, ancak alt sorgu, geri döndürdüğümüz on bir veri satırına sahip olduğumuz için on bir kez koştu. Yani, bu durumda, kod yazmanın en etkili yolu gibi görünmüyor.

Farklı bir yaklaşım için, bir alt sorgu çalıştıralım ve bir tablo gibi davranalım:

select
    a.ID,
    b.brand,
    d.countCars
from
    cars a
        join brands b
            on a.brand=b.ID
        join
            (
            select
                c.brand,
                count(c.ID) as countCars
            from
                cars c
            group by
                c.brand
            ) d
            on a.brand=d.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         5 |
|  2 | Ford   |         2 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         5 |
|  6 | BMW    |         2 |
|  7 | Ford   |         2 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 10 | BMW    |         2 |
| 11 | Toyota |         5 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Tamam, bu yüzden aynı sonuçlara sahibiz (biraz farklı sipariş verdik - veritabanı, bu sefer seçtiğimiz ilk sütun tarafından sipariş edilen sonuçları döndürmek istiyor gibi görünüyor) - ama aynı doğru sayılar.

Peki, ikisi arasındaki fark nedir - ve her bir alt sorguyu ne zaman kullanmalıyız? İlk olarak, ikinci sorgunun nasıl çalıştığını anladığımızdan emin olalım. fromSorgumuzun cümlesinde iki tablo seçtik ve sonra bir sorgu yazdık ve veritabanına bunun yerine bir tablo olduğunu söyledik - veritabanının mükemmel bir şekilde mutlu olduğu. Orada olabilir bu yöntemi kullanmanın bazı avantajları (yanı sıra bazı sınırlamalar) olmak. Her şeyden önce bu alt sorgu bir kez koştu . Veritabanımız büyük miktarda veri içeriyorsa, ilk yönteme göre büyük bir gelişme olabilir. Bununla birlikte, bunu bir tablo olarak kullandığımız için, fazladan veri satırı getirmeliyiz - böylece gerçekte veri satırlarımıza tekrar birleştirilebilirler. Biz de olmadığından emin olmak zorunda yeterliyukarıdaki sorgudaki gibi basit bir birleştirme kullanacaksak veri satırları. Hatırlarsanız, birleştirme yalnızca birleştirmenin her iki tarafında eşleşen verilere sahip satırları geri çeker . Dikkatli olmazsak, bu alt sorguda eşleşen bir satır yoksa, geçerli veriler otomobiller tablomuzdan döndürülmeyebilir.

Şimdi, ilk sorguya baktığımızda bazı sınırlamalar da var. verileri tek bir satıra geri çektiğimiz için, SADECE bir veri satırını geri çekebiliriz. Kullanılan altsorgular selectbir sorgunun madde çoğu zaman, örneğin sadece bir toplama fonksiyonu sum, count, maxveya benzer başka bir toplama işlevi. Onlar yok olması için, ama bu yazıldıkları sıklığıdır.

Bu yüzden, devam etmeden önce, başka bir alt sorguyu nerede kullanabileceğimize hızlı bir göz atalım. Bunu wherecümle içinde kullanabiliriz - şimdi, bu örnek veritabanımızda olduğu gibi biraz daha tutarlı, aşağıdaki verileri elde etmenin daha iyi yolları var, ancak sadece bir örnek için olduğunu görmek, bir göz atalım:

select
    ID,
    brand
from
    brands
where
    brand like '%o%'

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  6 | Holden |
+----+--------+
3 rows in set (0.00 sec)

Bu, bize o, adındaki harfi içeren marka kimlikleri ve Marka adlarının bir listesini döndürür (ikinci sütun yalnızca bize markaları göstermek için eklenir) .

Şimdi, bu sorgunun sonuçlarını bir where deyiminde kullanabiliriz:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in
        (
        select
            ID
        from
            brands
        where
            brand like '%o%'
        )

+----+--------+
| ID | brand  |
+----+--------+
|  2 | Ford   |
|  7 | Ford   |
|  1 | Toyota |
|  5 | Toyota |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Gördüğünüz gibi, alt sorgu üç marka kimliğini döndürüyor olsa da, otomobiller tablomuzda sadece ikisi için girişler vardı.

Bu durumda, daha fazla ayrıntı için, alt sorgu aşağıdaki kodu yazmış gibi çalışıyor:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in (1,2,6)

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Toyota |
|  2 | Ford   |
|  5 | Toyota |
|  7 | Ford   |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Yine, bir alt sorgunun manüel girişlere karşı veritabanından dönerken satırların sırasını nasıl değiştirdiğini görebilirsiniz.

Alt sorguları tartışırken, bir alt sorgu ile başka neler yapabileceğimizi görelim:

  • Bir alt sorguyu başka bir alt sorgunun içine yerleştirebilirsiniz, vb. Veritabanınıza bağlı bir sınır var, ancak bazı çılgın ve manyak programcıların özyinelemeli işlevlerinden yoksun, çoğu insan asla bu sınıra ulaşmayacak.
  • Tek bir sorguya, selectyan tümcesi birkaç yan tümcesi, yan tümcesi birkaç yan tümcesi, frombirden fazla alt wheresorgu yerleştirebilirsiniz. yürütün.

Verimli bir kod yazmanız gerekiyorsa, sorguyu birkaç şekilde yazmak ve sonuçlarınızı almak için en uygun sorgu olan (zamanlama veya bir açıklama planı kullanarak) görmek yararlı olabilir. Çalışmanın ilk yolu her zaman bunu yapmanın en iyi yolu olmayabilir.


Yeni cihazlarý Çok önemli: subqueries muhtemelen her sonuç için bir kez çalıştırmak sürece bir katılmak gibi alt sorgusu kullanabilirsiniz (yukarıda gösterilmiştir).
Xeoncross

59

Bölüm 3 - Püf Noktaları ve Verimli Kod

MySQL in () verimliliği

Gelen ipuçları ve püf noktaları için fazladan bazı bitler ekleyeceğimi düşündüm.

Ben adil biraz gelip bkz biri soru, ben iki tablodaki eşleşmeyen satırları alırım nasıl ve cevap en sık aşağıdaki (bizim arabalara dayalı ve markalar tablo gibi bir şey olarak kabul bkz - etti Holden a olarak listelenen marka, ancak otomobil tablosunda görünmez):

select
    a.ID,
    a.brand
from
    brands a
where
    a.ID not in(select brand from cars)

Ve evet işe yarayacak.

+----+--------+
| ID | brand  |
+----+--------+
|  6 | Holden |
+----+--------+
1 row in set (0.00 sec)

Ancak bazı veritabanlarında verimli değildir . İşte bu konuda bir Stack Overflow sorusu için bir link ve nitrit cesur içine almak istiyorsanız burada derinlemesine bir makale mükemmel .

Kısa cevap, eğer optimizer verimli bir şekilde işlemezse, eşleşmeyen satırları almak için aşağıdaki gibi bir sorgu kullanmak çok daha iyi olabilir:

select
    a.brand
from
    brands a
        left join cars b
            on a.id=b.brand
where
    b.brand is null

+--------+
| brand  |
+--------+
| Holden |
+--------+
1 row in set (0.00 sec)

Alt sorguda aynı tablo ile Güncelleme Tablosu

Ahhh, başka bir ihtiyar ama goodie - eski FROM maddesinde güncelleme için hedef marka 'markalarını' belirleyemezsiniz .

MySQL update..., aynı tabloda bir alt seçimle sorgu çalıştırmanıza izin vermez . Şimdi, düşünüyor olabilirsiniz, neden onu sadece nerede cümle içine atmıyorsunuz? Ancak, yalnızca max()bir satır diğer satırlarla tarih içeren satırı güncellemek isterseniz ne olur ? Bunu tam olarak nerede bir maddede yapamazsınız.

update 
    brands 
set 
    brand='Holden' 
where 
    id=
        (select 
            id 
        from 
            brands 
        where 
            id=6);
ERROR 1093 (HY000): You can't specify target table 'brands' 
for update in FROM clause

Yani, bunu yapamayız ha? Tam olarak değil. Şaşırtıcı derecede çok sayıda kullanıcının bilmediği gizli bir çözüm var - buna dikkat etmeniz gereken bazı hackery'ler de dahil.

Alt sorguyu çalışması için iki sorgu arasında yeterince boşluk bırakan başka bir alt sorgunun içine yapıştırabilirsiniz. Ancak, sorguyu bir işlemin içine sokmanın en güvenli yol olabileceğini unutmayın. Bu, sorgu çalışırken tablolarda başka değişikliklerin yapılmasını önler.

update 
    brands 
set 
    brand='Holden' 
where id=
    (select 
        id 
    from 
        (select 
            id 
        from 
            brands 
        where 
            id=6
        ) 
    as updateTable);

Query OK, 0 rows affected (0.02 sec)
Rows matched: 1  Changed: 0  Warnings: 0

3
Sadece NEREDE OLMADI () yapısının 'verimlilik açısından' hemen hemen aynı olduğunu, ancak bence okunması / anlaşılması çok daha kolay olduğunu belirtmek isterim. Sonra tekrar, benim bilgim MSSQL ile sınırlıdır ve aynı diğer platformlarda doğru söz veremem.
deroby

Ben NOT IN () birkaç yüz ID's hakkında bir liste vardı ve sorgu ve birleştirmenin sürümü arasında hiçbir fark yoktu geçen gün bu tür karşılaştırma denedim. Binlerce ya da milyarı bulan zaman fark yaratabilir.
Buttle Butkus

18

FROM anahtar sözcüğünde birden çok sorgu kavramını kullanabilirsiniz. Size bir örnek göstereyim:

SELECT DISTINCT e.id,e.name,d.name,lap.lappy LAPTOP_MAKE,c_loc.cnty COUNTY    
FROM  (
          SELECT c.id cnty,l.name
          FROM   county c, location l
          WHERE  c.id=l.county_id AND l.end_Date IS NOT NULL
      ) c_loc, emp e 
      INNER JOIN dept d ON e.deptno =d.id
      LEFT JOIN 
      ( 
         SELECT l.id lappy, c.name cmpy
         FROM   laptop l, company c
         WHERE l.make = c.name
      ) lap ON e.cmpy_id=lap.cmpy

İstediğiniz kadar tablo kullanabilirsiniz. Tablo alt sorgularında bile, gerektiğinde dış birleşimleri ve birleşimi kullanın.

Bu kadar çok tablo ve alan eklemek için çok kolay bir yöntemdir.


8

Bu şeyleri okurken tabloları bulmasını umuyor:

jsfiddle

mysql> show columns from colors;                                                         
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+           
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
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.