SQLクエリは複数のテーブルからデータを返します


434

以下について知りたいのですが。

  • データベースの複数のテーブルからデータを取得する方法
  • これを行うにはどのような方法がありますか?
  • ジョインとユニオンとは何ですか?それらはどのように互いに異なりますか?
  • それぞれを他のものと比較していつ使用する必要がありますか?

私の(たとえば-PHP)アプリケーションでこれを使用する予定ですが、データベースに対して複数のクエリを実行したくない場合、単一のクエリで複数のテーブルからデータを取得するにはどのようなオプションが必要ですか?

注:これは、PHPキューで頻繁に出くわす数多くの質問に関するよく書かれたガイドにリンクできるように書いているので、回答を投稿するときに詳細にリンクできます。

答えは以下をカバーしています:

  1. パート1-結合とユニオン
  2. パート2-サブクエリ
  3. パート3-トリックと効率的なコード
  4. パート4-From句のサブクエリ
  5. パート5-ジョンのトリックの混合バッグ

回答:


469

パート1-結合とユニオン

この回答は以下をカバーします:

  1. パート1
    • 内部結合を使用して2つ以上のテーブルを結合する(詳細については、ウィキペディアのエントリを参照してください)
    • ユニオンクエリの使用方法
    • 左と右の外部結合(このstackOverflowの回答は、結合のタイプを説明するのに最適です)
    • クエリを交差させる(データベースがサポートしていない場合にクエリを再現する方法)-これはSQL-Serverの機能(情報を参照)であり、私が最初にこの全体を書い理由の一部です。
  2. パート2
    • サブクエリ-それらが何であるか、どこで使用できるか、何に注意するか
    • デカルトがAKAに加わる-ああ、惨めさ!

データベース内の複数のテーブルからデータを取得するには、いくつかの方法があります。この回答では、ANSI-92結合構文を使用します。これは、古いANSI-89構文を使用します。そこに他のチュートリアルの数と異なる場合があります(あなたが89に使用されている場合や、あまり直感的に見えるかもしれません-しかし、すべて私に言えることは、それを試してみることです)そのままずっと簡単にクエリがより複雑になる時期を理解する。なぜそれを使うのですか?パフォーマンスの向上はありますか?短い答えはノーですが、あるあなたがそれに慣れる読みやすいです。この構文を使用すると、他の人が作成したクエリを読みやすくなります。

また、利用可能な車を追跡するためのデータベースを備えた小さなカーヤードの概念を使用します。所有者はあなたを彼のITコンピュータの男として雇い、あなたが彼が帽子を一滴落とすときに彼が要求するデータを彼が落とせると期待しています。

ファイナルテーブルで使用されるルックアップテーブルをいくつか作成しました。これにより、作業に適したモデルが得られます。まず、次の構造を持つサンプルデータベースに対してクエリを実行します。最初によくある間違いを考えて、何が悪いのかを説明し、もちろん修正方法も示します。

最初の表は単にカラーリストであり、自動車の庭にある色がわかるようになっています。

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)

ブランドテーブルは、カーヤードから販売される可能性のある自動車のさまざまなブランドを示しています。

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)

モデルテーブルはさまざまなタイプの車をカバーします。実際の車のモデルではなく、さまざまなタイプの車を使用する方が簡単になります。

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)

そして最後に、これらすべての他のテーブルを結び付けるために、すべてを結び付けるテーブルです。IDフィールドは、実際には車を識別するために使用される一意のロット番号です。

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)

これにより、さまざまな種類の結合の以下の例をカバーするのに十分なデータ(私は願っています)が得られ、それらを価値のあるものにするのに十分なデータも得られます。

だから、それの骨子に入ると、ボスは彼が持っているすべてのスポーツカーのIDを知りたがってます。

これは単純な2つのテーブル結合です。モデルを特定するテーブルと、在庫があるテーブルを持っています。ご覧modelcarsとおり、テーブルの列のデータは、テーブルのmodels列に関連していますcars。これで、modelsテーブルのIDが1forでSportsあることがわかったので、結合を作成しましょう。

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

このクエリは適切に見えますか?2つのテーブルを識別し、必要な情報を含め、どの列に結合するかを正しく識別する結合を使用します。

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

ああ!最初のクエリでエラーが発生しました!はい、それは梅です。ご覧のとおり、クエリは実際に正しい列を取得していますが、一部の列は両方のテーブルに存在するため、データベースは実際の列の意味と場所について混乱します。これを解決するには2つの解決策があります。1つ目は素晴らしくてシンプルです。次のtableName.columnNameように、データベースの意味を正確に伝えるために使用できます。

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)

もう1つはおそらくより頻繁に使用され、テーブルエイリアスと呼ばれます。この例のテーブルには、簡潔で簡潔な名前が付いていますが、次のように入力するKPI_DAILY_SALES_BY_DEPARTMENTとおそらくすぐに古くなるため、次のようにテーブルにニックネームを付けるのが簡単な方法です。

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

さて、リクエストに戻ります。ご覧のとおり、必要な情報はありますが、要求されなかった情報もあるため、ステートメントにwhere句を含めて、要求されたとおりのスポーツカーのみを取得する必要があります。テーブル名を何度も使用するよりも、テーブルエイリアス方式を使用する方が好きなので、ここからはそれを使い続けます。

明らかに、クエリにwhere句を追加する必要があります。ID=1またはでスポーツカーを識別できますmodel='Sports'。IDにはインデックスが付けられており、主キー(たまたま入力が少ない)なので、クエリでそれを使用できます。

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)

ビンゴ!上司は幸せです。もちろん、上司であり、彼が求めたことに決して満足していないので、彼は情報を見て、色も欲しいと言いました

さて、クエリの大部分は既に記述されていますが、色である3番目のテーブルを使用する必要があります。これで、メイン情報テーブルcarsに車の色IDが格納され、これが色ID列にリンクされます。したがって、オリジナルと同様の方法で、3番目のテーブルを結合できます。

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)

くそー、テーブルは正しく結合され、関連する列はリンクされていましたが、リンクしたばかりの新しいテーブルから実際の情報を取得するのを忘れていました。

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)

ええ、それはしばらくの間私たちの背中の上司です。では、これについてもう少し詳しく説明します。ご覧のとおりfrom、ステートメントの句はメインテーブルをリンクしています(私は、ルックアップテーブルやディメンションテーブルではなく、情報を含むテーブルを使用することがよくあります。クエリはすべてのテーブルを切り替えて使用しても同様に機能しますが、数か月後にこのクエリに戻ってそれを読むので、多くの場合、わかりやすくてわかりやすいクエリを作成することをお勧めします。直感的にレイアウトし、すべてができるだけ明確になるように、適切なインデントを使用してください。他の人に教えることを続ける場合は、特にトラブルシューティングを行う場合は、これらの特性をクエリに植え込むようにしてください。

この方法でより多くのテーブルをリンクし続けることは完全に可能です。

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

joinステートメントに複数の列を結合する可能性があるテーブルを含めるのを忘れていましたが、ここに例を示します。modelsテーブルにブランド固有のモデルがあり、そのため、フィールドbrandbrandsテーブルにリンクして戻ると呼ばれる列があった場合、次のIDように行うことができます。

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

上記のクエリは、結合されたテーブルをメインテーブルにリンクするだけでなくcars、既に結合されたテーブル間の結合も指定しています。これが行われなかった場合、結果はデカルト結合と呼ばれます。デカルト結合は、情報がデータベースに結果の制限方法を指示しないため行が返される結合であり、クエリは基準に一致するすべての行を返します。

したがって、デカルト結合の例を示すために、次のクエリを実行してみましょう。

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)

なんてこった、醜い。ただし、データベースに関する限り、それはまさに要求されたものです。クエリでは、我々は用を求めたIDからcarsmodelからmodels。我々が指定されていませんでしたので、しかし、どのようにテーブルを結合するために、データベースが一致したすべてのと最初のテーブルから行をすべての第二のテーブルからの行。

さて、上司が戻ってきたので、もう一度情報を求めています。同じリストが必要ですが、4WDも含めます

ただし、これは、これを達成するための2つの異なる方法を検討するための大きな言い訳になります。次のようにwhere句に別の条件を追加できます。

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

上記は完璧に機能しますが、別の見方をすると、これはunionクエリがどのように機能するかを示す優れた言い訳です。

次のようにすると、すべてのスポーツカーが返されます。

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

そして、以下はすべての4WDを返します。

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

したがって、union allそれらの間に句を追加すると、2番目のクエリの結果が最初のクエリの結果に追加されます。

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)

ご覧のとおり、最初のクエリの結果が最初に返され、次に2番目のクエリの結果が返されます。

この例では、もちろん最初のクエリを使用する方がはるかに簡単unionですが、特定のケースではクエリが優れている場合があります。これらは、簡単に結合できないテーブルからテーブルから特定の結果を返すための優れた方法です-またはさらに言えば、完全に無関係なテーブル。ただし、従うべきルールがいくつかあります。

  • 最初のクエリの列タイプは、以下の他のすべてのクエリの列タイプと一致する必要があります。
  • 最初のクエリの列の名前は、結果セット全体を識別するために使用されます。
  • 各クエリの列数は同じでなければなりません。

さて、あなたは可能性があるか疑問に思うことの違いは、使用の間にあるunionunion allunion一方、クエリは、重複を削除しますunion allしません。これは、unionover を使用しunion allたときにパフォーマンスにわずかな影響があることを意味しますが、結果はそれだけの価値がある可能性があります。ただし、このようなことについては推測しません。

このノートについては、ここでいくつかの追加のノートに注目する価値があるかもしれません。

  • 結果を並べ替える場合は、anを使用order byできますが、エイリアスは使用できません。上記のクエリでorder by a.IDは、両方のクエリで同じエイリアスが使用されている場合でも、結果を考慮すると、列を呼び出すのIDではなくa.ID-を追加するとエラーが発生します。
  • order byステートメントは1つしか持てず、それが最後のステートメントでなければなりません。

次の例では、テーブルに行をいくつか追加しています。

Holdenブランド表に追加しました。またcarscolor値が12-の色テーブルで参照されていない行を追加しました。

さて、上司が再び戻ってきて、リクエストが鳴り響きました-*私たちは、私たちが運ぶ各ブランドとその中の車の数を数えたいです! 。

Rightyoなので、最初に必要なことは、可能なブランドの完全なリストを取得することです。

select
    a.brand
from
    brands a

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

これを車のテーブルに結合すると、次の結果が得られます。

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)

もちろんこれは問題Holdenです。私が追加した素敵なブランドについての言及はありません。

これは、結合が両方のテーブルで一致する行を探すためです。タイプの車にはデータHoldenがないため、返されません。ここでouter結合を使用できます。これは、他のテーブルで一致するかどうかに関係なく、1つのテーブルからすべての結果を返します

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)

これで準備ができたので、素敵な集計関数を追加してカウントを取得し、少しの間ボスを背負わせることができます。

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)

それで、ボスは頭をかしげます。

これをさらに詳しく説明するために、外部結合はleftor right型にすることができます。左または右は、どのテーブルが完全に含まれるかを定義します。A left outer joinは左側のテーブルのすべての行を含みますが、(ご想像のとおり)a right outer joinは右側のテーブルのすべての結果を結果に取り込みます。

一部のデータベースではfull outer join両方のテーブルから(一致するかどうかにかかわらず)結果を返すを許可しますが、これはすべてのデータベースでサポートされているわけではありません。

さて、おそらくこの時点で、クエリで結合タイプをマージできるかどうか疑問に思っています-答えは「はい」です。

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)

それで、なぜそれが期待された結果ではないのですか?車からブランドへの外部結合を選択しましたが、結合色では指定されなかったため、特定の結合では両方のテーブルに一致する結果のみが返されます。

以下は、期待した結果を取得するために機能するクエリです。

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)

ご覧のとおり、クエリに2つの外部結合があり、期待どおりに結果が出ています。

さて、あなたが尋ねる他の種類の結合はどうですか?交差点はどうですか?

まあ、すべてのデータベースがサポートしているわけではありませんが、ほとんどすべてのデータベースでintersection、結合(または少なくとも構造化されたwhereステートメント)を使用して交差を作成できます。

Intersectionは、union上記のと多少似たタイプの結合ですが、違いは、ユニオンによって結合されたさまざまな個々のクエリ間で同一の(そして私が同一であることを意味する)データの行のみを返すことです。すべての点で同一の行のみが返されます。

簡単な例は次のとおりです。

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

通常のunionクエリはテーブルのすべての行を返します(最初のクエリは何でも返しID>2、2番目のクエリはを返しますID<4)。これにより、完全なセットが生成されますが、交差クエリid=3は両方の基準を満たすため、一致する行のみを返します。

これで、データベースがintersectクエリをサポートしていない場合、上記は次のクエリで簡単に実現できます。

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)

本質的に交差クエリをサポートしていないデータベースを使用して2つの異なるテーブル間で交差を実行する場合は、テーブルのすべての列に結合を作成する必要があります。


2
@Fluffehいい答え。私は提案をします:それをキラーSQLチュートリアルにしたいのであれば、ベン図を追加するだけでは足りません。彼らのおかげで、私はすぐに左と右の結合を理解しました。個人的なリクエスト:よくある間違いやパフォーマンスチューニングに関するチュートリアルはありますか?
StrayChild01 2012年

25
ああ。スクロールホイールが壊れています。素晴らしい質問と答え。これを10回賛成できればいいのに。
Amal Murali

3
へへ、正のフィードバックをありがとう。スクロールし続けてください、これは最初の答えにすぎませんでした。SOは私の回答が1つの「回答」に収まるには長すぎると言ったので、いくつか使用する必要がありました:)
Fluffeh

7
正直なところ、この答えは少し短くする必要があると思います。
einpoklum

素晴らしい記事。データベース結合101.
maqs

101

わかりました。この投稿は非常に興味深く、クエリの作成に関する私の知識の一部を共有したいと思います。このFluffehをありがとう。これを読んで、私が間違っていると感じるかもしれない他の人は、101%自由に編集して私の答えを批判できます。(正直なところ、間違いを訂正してくれてとても感謝しています。

よくある質問のいくつかをMySQLタグで投稿します。


トリックNo. 1(複数の条件に一致する行

このスキーマを考えると

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');

質問

とカテゴリの両方に少なくとも属するすべての映画を検索ます。 ComedyRomance

解決

この質問は非常に扱いにくい場合があります。このようなクエリが答えになると思われるかもしれません:-

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

結果が出ないため、これは間違いなく非常に間違っています。この説明は、唯一つの有効な値が存在することであるCategoryName上の各行は。たとえば、最初の条件はtrueを返し、2番目の条件は常にfalseです。したがって、AND演算子を使用すると、両方の条件が真になります。それ以外の場合はfalseになります。別のクエリはこのようなものです、

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

SQLFiddleデモ

少なくとも 1つの一致があるレコードに一致するため、結果は依然として正しくありませんcategoryName真の解決策は、 映画ごとに、レコードのインスタンス数を数えることによってでしょう。インスタンスの数は、条件で指定された値の総数と一致する必要があります。

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デモ(答え)


トリックNo. 2(各エントリの最大レコード

与えられたスキーマ、

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');

質問

各ソフトウェアの最新バージョンを見つけます。次の列を表示します。SoftwareNameDescriptionsLatestVersionVersionNoの列から)、DateReleased

解決

一部のSQL開発者は、誤ってMAX()集計関数を使用しています。彼らはこのように作成する傾向があり、

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

ほとんどのRDBMSは、group by句で非集約列の一部を指定していないため、これで構文エラーを生成します)結果はLatestVersion各ソフトウェアで正しい結果を生成しますが、明らかにDateReleased間違っています。MySQLはサポートWindow FunctionsしてCommon Table Expressionいませんが、一部のRDBMSはすでにサポートしています。この問題の回避策subqueryは、versionNo各ソフトウェアで個別の最大値を取得し、後で他のテーブルに結合するを作成することです。

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デモ(答え)


以上です。私は他のどの思い出すように私は別の、すぐに掲載する予定よくある質問MySQLタグを。この小さな記事を読んでいただきありがとうございます。これから少しでも知識を得ていただければ幸いです。

アップデート1


トリックNo. 3(2つのID間の最新のレコードを見つける

与えられたスキーマ

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');

質問

2人のユーザー間の最新の会話を見つけます。

解決

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


驚くばかり!警告ジョン、最初の解決策は、2つのフィールドに一意の制約があるためにのみ機能します。一般的な問題を解決するために、より一般的なソリューションを使用することもできます。私の意見では、唯一の解決策は、comedyおよびの個別選択を行うことですromanceHavingそのときは適合し
ません

@nawfalは実際にはそうではなく、一意の制約が追加されなかった場合は、Having distinct句を追加する必要がありますSQLFiddle Demo:D
John Woo

63

パート2-サブクエリ

さて、上司が再び急増しました- 私はすべての車とそのブランドのリストとそのブランドの合計数を知りたいです!

これは、SQLの利点である次のトリック、つまりサブクエリを使用する絶好の機会です。この用語に慣れていない場合、サブクエリは別のクエリ内で実行されるクエリです。それらを使用する多くの異なる方法があります。

このリクエストでは、まず、各車とブランドを一覧表示する簡単なクエリを作成します。

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

ここで、単純にブランド別に並べ替えた車の数を取得したい場合は、もちろん次のように書くことができます。

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

それで、count関数を元のクエリに単純に追加できるはずですよね?

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)

残念ながら、それはできません。その理由は、車のID(列a.ID)を追加するときにグループに追加する必要があるためです。つまり、count関数が機能するとき、IDごとに一致するIDは1つだけです。

ここでサブクエリを使用できます。実際、2つの完全に異なるタイプのサブクエリを実行して、これに必要な同じ結果を返すことができます。1つは、単にサブクエリをselect句に配置することです。つまり、データの行を取得するたびに、サブクエリが実行され、データの列を取得して、データの行にポップします。

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)

そしてバム!ただし、気付いた場合、このサブクエリは、返されるデータの行ごとに実行する必要があります。この小さな例でも、車のブランドは5つしかありませんが、返されるデータが11行あるため、サブクエリは11回実行されました。したがって、この場合、コードを記述する最も効率的な方法とは思えません。

別のアプローチとして、サブクエリを実行して、それがテーブルであるふりをします。

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)

さて、結果は同じです(順序が少し異なります-データベースは今回選択した最初の列で順序付けられた結果を返したいようです)-正しい数値です。

それで、2つの違いは何ですか?そして、いつ各タイプのサブクエリを使用する必要がありますか?まず、2番目のクエリがどのように機能するかを確認します。fromクエリの句で2つのテーブルを選択し、クエリを作成して、実際には代わりにテーブルであることをデータベースに伝えました-データベースは完全に満足しています。この方法を使用すると、いくつかの利点(およびいくつかの制限)生じる可能性があります。最も重要なのは、このサブクエリが1回実行されたことです。データベースに大量のデータが含まれている場合、最初の方法より大幅に改善される可能性があります。ただし、これをテーブルとして使用しているため、実際にデータ行に結合できるように、追加のデータ行を取り込む必要があります。また、十分な数があることを確認する必要があります上記のクエリのように単純な結合を使用する場合は、データの行。あなたがリコールした場合、参加するだけでデータを照合している行を引き戻すます両方の参加の側面を。注意しないと、このサブクエリに一致する行がない場合、carsテーブルから有効なデータが返されない可能性があります。

ここで、最初のサブクエリを振り返ると、いくつかの制限もあります。データを1行に戻すため、1行のデータのみを戻すことができます。で使用されるサブクエリselectクエリの句は、非常に多くの場合のようなだけ集計関数を使用してsumcountmaxまたは他の同様の集約関数。彼らはそうする必要はありません、それはしばしば彼らが書かれている方法です。

それでは、次に進む前に、サブクエリを使用できる他の場所を簡単に見てみましょう。where句で使用できます。この例はデータベースのように少し工夫されています。次のデータを取得するためのより良い方法がありますが、これは例にすぎないので、見てみましょう。

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

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

これoにより、名前に文字が含まれているブランドIDとブランド名(2番目の列はブランドを示すためにのみ追加されます)のリストが返されます。

これで、このクエリの結果をwhere句で使用できます。

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)

ご覧のとおり、サブクエリが3つのブランドIDを返していたとしても、carsテーブルには2つのブランドIDしかありませんでした。

この場合、詳細については、サブクエリは次のコードを記述したかのように機能しています。

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)

ここでも、データベースから戻るときに、サブクエリと手動入力が行の順序をどのように変更したかを確認できます。

サブクエリについて説明している間、サブクエリで他に何ができるか見てみましょう:

  • サブクエリを別のサブクエリ内などに配置できます。データベースに依存する制限がありますが、一部の非常識でマニアックなプログラマーの再帰機能が不足している場合、ほとんどの人はその制限に決して到達しません。
  • 複数のサブクエリを1つのクエリに入れることができます(selectいくつかは句に、いくつかは句に、さらにいくつかをfrom句にwhere入れます)。それぞれを入れるとクエリがより複雑になり、時間がかかる可能性があることに注意してください。実行します。

効率的なコードを記述する必要がある場合は、さまざまな方法でクエリを記述し、結果を取得するための最適なクエリであるかどうかを(タイミングまたはEXPLAIN PLANを使用して)確認することをお勧めします。最初に機能する方法が、常に最良の方法であるとは限りません。


新しい開発者にとって非常に重要です。サブクエリを結合として使用できない場合(上記を参照)を除いて、サブクエリは結果ごとに1回実行されます。
Xeoncross

59

パート3-トリックと効率的なコード

MySQLのin()効率

出てきたヒントやトリックのために、少しビットを追加すると思いました。

私が目にする1つの質問は、2つのテーブルから一致しない行を取得する方法です。最も一般的に受け入れられる答えは次のようなものです(私たちの車とブランドのテーブルに基づく-これにはHoldenがブランドですが、車の表には表示されません):

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

そして、そうです。

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

ただし、一部のデータベースでは効率的ではありません。ここにそれについて尋ねるスタックオーバーフローの質問へリンクがあります。もしあなたが要点を知りたければ、ここに優れた詳細な記事があります。

簡単に言えば、オプティマイザが効率的に処理しない場合は、次のようなクエリを使用して一致しない行を取得する方がはるかに良いでしょう。

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)

サブクエリ内の同じテーブルでテーブルを更新する

ああ、もう1つ古いですがいいですね-古いFROM句で更新するターゲットテーブル 'ブランド'を指定することはできません

MySQL update...では、同じテーブルで副選択を使用してクエリを実行することはできません。さて、あなたは考えているかもしれません、それをwhere句に平手打ちしませんか?しかしmax()、他の行の束に囲まれた日付の行のみを更新する場合はどうでしょうか。where句でそれを正確に行うことはできません。

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

それで、私たちはそれができないのですか?まあ、そうではありません。驚くほど多くのユーザーが知らないこっそりした回避策があります-ただし、注意が必要なハッカーが含まれています。

サブクエリを別のサブクエリ内に貼り付けることができます。これにより、2つのクエリの間に十分なギャップが生じ、機能します。ただし、トランザクション内にクエリを固定するのが最も安全な場合があることに注意してください。これにより、クエリの実行中にテーブルに他の変更が加えられなくなります。

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
WHERE NOT EXISTS()構造は「効率の観点」からはほとんど同じですが、私の意見でははるかに読みやすく/理解しやすいことに注意したいだけです。繰り返しになりますが、私の知識はMSSQLに限定されており、他のプラットフォームでも同じことが当てはまるかどうかはわかりません。
deroby 2012

先日、このタイプの比較を試してみましたが、NOT IN()には数百のIDのリストがあり、クエリの結合バージョンとの違いはありませんでした。おそらく、数千または数十億に達したときにそれは違いを生みます。
Buttle Butkus 2012

18

FROMキーワードで複数のクエリの概念を使用できます。一例を紹介しましょう。

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

テーブルはいくつでも使用できます。テーブルのサブクエリ内であっても、必要な場合は常に外部結合とユニオンを使用します。

これは、テーブルやフィールドと同じくらい多くのことを含む非常に簡単な方法です。


8

これがあなたが物事を読んでいるときにテーブルを見つけることを願っています:

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    |                |
+-------+-------------+------+-----+---------+----------------+
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.