459

次のことを知りたいです。

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

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

注: PHP キューで常に遭遇する多数の質問についてよく書かれたガイドにリンクできるようにしたいので、これを書いています。

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

  1. パート 1 - 結合と結合
  2. パート 2 - サブクエリ
  3. パート 3 - トリックと効率的なコード
  4. パート 4 - From 句のサブクエリ
  5. パート 5 - さまざまなジョンのトリック
4

6 に答える 6

500

パート 1 - 結合と結合

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

  1. パート1
  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 つのテーブル結合です。モデルを識別するテーブルと、利用可能な在庫を含むテーブルがあります。ご覧のとおり、テーブルのmodel列のデータは、テーブルの列にcars関連しています。これで、models テーブルの ID がfor であることがわかったので、結合を記述します。modelscars1Sports

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れたという列もある場合、次のように実行できます。brandbrandsID

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テーブルにリンクするだけでなく、既に結合されているテーブル間の結合も指定しています。これが行われなかった場合、結果はデカルト結合と呼ばれます。これは dba の悪口です。デカルト結合は、情報がデータベースに結果を制限する方法を伝えないため、行が返されるものであり、クエリは基準に適合するすべての行を返します。

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

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)

なんてこった、それは醜い。ただし、データベースに関する限り、まさに求められていたものです。クエリでは、IDfromcarsmodelfromを要求しましたmodelsただし、テーブルを結合する方法を指定しなかったため、データベースは最初のテーブルのすべての行を 2 番目のテーブルのすべての行と一致させました。

よし、ボスが戻ってきて、またもっと情報を欲しがっている。同じリストが必要ですが、そこに 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ですが、特定のケースではクエリが優れている場合があります。これらは、簡単に結合できないテーブルのテーブルから特定の結果を返す優れた方法です。さらに言えば、完全に無関係なテーブルです。ただし、従うべきいくつかの規則があります。

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

ここで、 と の使用の違いについて疑問に思うかもしれませ。クエリは重複を削除しますが、 は削除しません。これは、 overを使用するとパフォーマンスがわずかに低下することを意味しますが、結果はそれに値する可能性があります。ただし、この種のことについては推測しません。unionunion allunionunion allunionunion all

この点について、ここでいくつかの追加の注記に​​注意する価値があるかもしれません。

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

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

Holdenブランド表に追加しました。色テーブルに参照がない -の値carsを持つ行も追加しました。color12

オーケー、上司がまた戻ってきて、リクエストを吠えています - *私たちが扱っている各ブランドの数とそれに含まれる車の数が欲しい!」 - 通常、私たちは議論の興味深いセクションにたどり着き、上司はもっと仕事を求めています.

したがって、最初に行う必要があるのは、可能なブランドの完全なリストを取得することです.

select
    a.brand
from
    brands a

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

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

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)

それで、ボスのスカルクを遠ざけます。

ここで、これをもう少し詳しく説明すると、外部結合はleftorright型にすることができます。Left または Right は、どのテーブルが完全に含まれるかを定義します。Aleft outer joinは左側のテーブルのすべての行を含み、(ご想像のとおり) aright 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 ステートメント) を使用して交差を作成できます。

unionIntersection は、前述の a に多少似た結合のタイプですが、違いは、結合によって結合されたさまざまな個々のクエリ間で同一の (つまり同一という意味です) データの行のみを返すことです。すべての点で同一の行のみが返されます。

簡単な例は次のようになります。

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 つの異なるテーブル間で交差を実行する場合は、テーブルのすべての列で結合を作成する必要があります。

于 2012-09-18T11:11:39.310 に答える
103

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

よくある質問をMySQLタグ付けして掲載します。


裏技その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にの有効な値が 1 つしかないということです。たとえば、最初の条件はtrueを返し、2 番目の条件は常に false を返します。したがって、演算子を使用すると、両方の条件が真になるはずです。それ以外の場合は false になります。別のクエリは次のようになります。AND

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

質問

各ソフトウェアの最新バージョンを見つけてください。次の列を表示します: SoftwareNameDescriptionsLatestVersion( VersionNo 列から)、DateReleased

解決

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

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


それだけでした。タグに関する他のFAQを思い出したら、すぐに別の質問を投稿します。MySQLこの小さな記事を読んでいただきありがとうございます。このことから少しでも知識を得ていただければ幸いです。

更新 1


裏技その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 デモ

于 2012-09-25T15:31:09.573 に答える
66

パート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 |
+--------+-----------+

したがって、元のクエリにカウント関数を追加するだけでよいのではないでしょうか。

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)を追加するときに、それをgroup byで追加する必要があるためです。したがって、count関数が機能する場合、IDごとに一致するIDは1つだけです。

ただし、ここでサブクエリを使用できます。実際、これに必要な同じ結果を返す2つの完全に異なるタイプのサブクエリを実行できます。select1つ目は、サブクエリを句に入れるだけです。これは、データの行を取得するたびに、サブクエリが実行され、データの列を取得して、それをデータの行にポップすることを意味します。

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番目のクエリがどのように機能するかを理解していることを確認しましょう。クエリの句で2つのテーブルを選択しfrom、クエリを作成して、データベースに、実際には代わりにテーブルであると伝えました。これは、データベースが完全に満足していることです。この方法を使用することにはいくつかの利点があります(いくつかの制限もあります)。最も重要なのは、このサブクエリが1回実行されたことです。データベースに大量のデータが含まれている場合は、最初の方法よりも大幅に改善される可能性があります。ただし、これをテーブルとして使用しているため、追加のデータ行を取り込む必要があります。これにより、実際にデータ行に結合できるようになります。また、十分な数があることを確認する必要があります上記のクエリのように単純な結合を使用する場合は、データの行。思い出してください。結合は、結合の両側に一致するデータを持つ行のみをプルバックします。注意しないと、このサブクエリに一致する行がない場合、carsテーブルから有効なデータが返されない可能性があります。

ここで、最初のサブクエリを振り返ると、いくつかの制限もあります。データを単一の行にプルバックしているため、データのプルバックできるのは1行のみです。selectクエリの句で使用されるサブクエリは、、、、または別の同様の集計sum関数などの集計関数のみを使用することがよくあります。彼らはそうする必要はありません、それはしばしば彼らが書かれている方法です。countmax

したがって、先に進む前に、サブクエリを使用できる他の場所を簡単に見てみましょう。句で使用できます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)

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

これで、このクエリの結果を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つのエントリしかありませんでした。

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

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はそれぞれ、クエリをより複雑にし、実行する。

効率的なコードを作成する必要がある場合は、クエリをさまざまな方法で記述し、結果を得るのに最適なクエリを確認する(タイミングを調整するか、説明プランを使用する)と便利です。動作する最初の方法は、常にそれを行うための最良の方法であるとは限りません。

于 2012-09-19T09:47:16.180 に答える
61

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

MySQL in() 効率

出てきたヒントやコツのために、いくつかのビットを追加すると思いました。

よくある質問の 1 つは、「2 つのテーブルから一致しない行を取得するにはどうすればよいですか」というものです。最も一般的に受け入れられている答えは次のようなものです (車とブランドのテーブルに基づいており、 Holdenはブランドですが、cars テーブルには表示されません):

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 句で更新対象のテーブル 'brands' を指定することはできません

update...MySQLでは、同じテーブルでサブセレクトを使用してクエリを実行することはできません。さて、あなたは考えているかもしれません。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
于 2012-09-20T08:01:53.630 に答える
19

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

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

これは、同じ数のテーブルとフィールドを含める非常に簡単な方法です。

于 2012-09-22T08:23:09.400 に答える