SQLのサブクエリを理解して複雑な問い合わせをする

データベース データベース関連
この記事は約5分で読めます。

この記事は、13年前に書かれました。

はじめに

サブクエリとは、クエリ内にさらにクエリを入れ子にしたものです。どういった場面で使用するかは様々ですが、私が今回実際の案件で使用した方法をご紹介します。

動作確認環境

  • MySQL5.3.x

親子関係のあるテーブルから親のリストと子の件数を取得したい

例えば以下のようなpersonsテーブルがあったとします。
少々特殊な事例ですが、parentの値に親のIDが入っているレコードが子となります。必然的に”0”は親レコードとなります。
例えば「1:Sato Takeshi」は親となり、IDが2,3のレコードは「1:Sato Takeshi」の子となります。
事情がありこのような構造となっていますが、このテーブルから親のみのリストと、その親にぶらさがる子の件数を1つのSQL文にて一括取得してみたいと思います。

 id |   name          |  parent | 
----+-----------------+---------+
  1 | Sato Takeshi    |  0      |
  2 | Sato Yukari     |  1      |
  3 | Sato Nami       |  1      |
  4 | Yamada Takayuki |  0      |
  5 | Yamada Taro     |  4      |
  6 | Yamada Shinji   |  4      |
  7 | Tanaka Keisuke  |  0      |
  8 | Tanaka Akemi    |  8      |
(8 rows)

まず、それぞれの親に属する子の人数をカウントするSQLを単独で作成してみます。

SELECT parent AS parent_id , count(parent) AS child_count FROM parents GROUP BY parent;

問い合わせ結果は以下のとおりです。

 parent_id | child_count| 
-----------+------------+
  0        |   3        |
  1        |   2        |
  4        |   2        |
  7        |   1        |
(4 rows)

count()はSQLのカウント関数ですが、AS句を利用してカウント結果をchild_countという別名にしています。また「parent AS parent_id」と別名指定し、paretをparent_idに置き換えています。これは、あとでサブクエリを組み立てるときに使用するためです。

サブクエリ化の完成

子のカウントが取得できたところで、いよいよサブクエリの出番です。
以下のようなSQLとなります。

SELECT m.id, m.name, s.child_count FROM parents AS m 
LEFT JOIN ( SELECT parent AS parent_id , count(parent) AS child_count FROM parents GROUP BY parent ) 
AS s ON m.id = s.parent_id 
WHERE m.parent = 0 ORDER BY m.name;

一行目のSELECT~は最終結果を出力するSQLとなりますので後ほど説明します。
2行目のLEFT JOIN~に続く( )内のSELECT文がサブクエリとなります。このSQL文は、先ほど子のカウントを取得したSQL文です。
LEFT JOINは左外部結合という機能です。1行目の「FROM parents AS m」と別名指定しているmテーブルと結合しており、左のテーブルはすべて表示するという意味になります。他にも内部結合、外部結合などがありますが、ここでは説明を割愛します。
3行目の「AS s」以降が重要で、サブクエリの問い合わせ結果をテーブル名”s”として別名にしています。さらに「ON m.id = s.parent_id」という箇所で、左のテーブル(parent AS m)と右のテーブル(サブクエリ結果 s)のidが一致するものを結合条件としています。
WHERE句では、「m.paret = 0」を指定していますが、parentが0のレコードは必然的に親レコードとなりますので、親のみを抽出するという意味になります。
最終的に、1行目にて「m.id, m.name, s.child_count」を表示していますが、テーブルを別名指定していますので、mかsどちらのカラムを表示させるかを明示的に指定します。

問い合わせ結果は結果は以下のようになります。

 id |    name          | child_count| 
-----------------------+------------+
  1 | Yamada Takayuki  |   2        |
  4 | Tanaka Keisuke   |   1        |
  7 | Sato Takeshi     |   2        |
(3 rows)

サブクエリは奥が深く、理解するのは少々難しいのですが、非常に便利な機能です。

実際の案件では、製品テーブルにて以下のようにサブクエリを使用しました。

select m.products_id,m.name1,m.viewno,s.child_count from products AS m
  left join ( select parent AS child_id , count(parent) as child_count from products group by parent ) AS s
       on m.products_id = s.child_id
 where category_id= 2 AND m.parent = 0 AND m.viewflg = 0 ORDER BY m.viewno

まとめ

SQL文のSELECT文を単体で使用する場合は非常に単純です。
しかし、あるテーブルの検索結果によって別のテーブルの検索をおこなうという複雑な問い合わせでは、サブクエリが必須となります。INNER JOIN, LEFT JOIN , RIGHT JOINといったものも頻繁に使用することになりますので、これらについても理解を深める必要があります。
今回は1つのテーブルでサブクエリを使用する特殊な例でしたが、考え方は特に変わりありません。