アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

('ω') < 転職した

ざっくりSQL入門 #6:クエリを書く<サブクエリ&UNION編>

MySQLを前提に書いています。一部関数などは方言差がある場合もあるのでご注意ください。

今回は、サブクエリやUNIONを用いて、複数のクエリの結果を組み合わせる方法について。


サブクエリとUNION、その違いについて

サブクエリは「あるSELECTクエリをテーブルや値とみなして扱う」方法、
UNIONは「複数のSELECTクエリの結果セットを足し合わせる」方法です。

サブクエリ

前述したとおり、「あるSELECTクエリをテーブルや値とみなして扱う」方法です。

テーブルとみなす

以下のように、
FROM句やJOIN句の部分にテーブル名ではなく「カッコで括ったSELECTクエリ」を用いることで
そのクエリの結果セットをテーブルとみなして扱うことができます。

SELECT ...
FROM
(
  SELECT ... FROM ... WHERE  ...
) AS `table_sub`
WHERE ...

例えば以下は、
「商品の注文テーブルを用いて売上個数と金額を集計したものに商品マスタをJOINして、集計結果を求めるクエリ」
です。

SELECT
  `summary`.`product_id` -- 商品ID
 ,`product`.`name`       -- 商品名
 ,`summary`.`count_sum`  -- 売上個数の合計
 ,`summary`.`price_sum`  -- 売上金額の合計
FROM
(
  -- product_idごとに、count(売上個数)とprice(売上金額)のSUMを取る
  SELECT   `product_id`, SUM(`count`) AS `count_sum`, SUM(`price`) AS `price_sum`
  FROM     `order_product`
  GROUP BY `product_id`
) AS `summary`
LEFT JOIN
  `product`
  ON
    `summary`.`product_id` = `product`.`product_id`
値とみなす

以下のように、ひとつもしくは複数の値とみなすこともできます。

-- 複数の値とみなす:サブクエリ内のカラムが1つのみである必要があります
SELECT ...
FROM ...
WHERE `hoge` IN (SELECT `fuga` FROM ... WHERE ...)

-- ひとつの値とみなす:サブクエリ内のカラムが1つのみ、かつレコードが1件のみである必要があります
SELECT ...
FROM ...
WHERE `hoge` = (SELECT `fuga` FROM ... WHERE ...)

例えば以下は
「2018年5月1日以降に売れた商品の一覧を求めるクエリ」
です。
(これは単純な例なので、JOINでも簡単に置き換えることができますが…)

SELECT
  `product`.`product_id`
 ,`product`.`name`
FROM
  `product`
WHERE
  `product`.`product_id` IN (SELECT DISTINCT `product_id` 
                             FROM   `order_product` 
                             WHERE  `order_date` >= '2018-05-01 00:00:00')

UNION

前述したとおり、「複数のSELECTクエリの結果セットを足し合わせる」方法です。

こちらは実現したいこととしては単純で、例えば

SELECT `id`, `name` FROM `user_a` WHERE ...

で得られる

id name
1 Alex
2 Bill
3 Cathy

という結果セットと

SELECT `id`, `name` FROM `user_b` WHERE ...

で得られる

id name
4 Daisy
5 Emily
6 Fir

という結果セットを足し合わせ

id name
1 Alex
2 Bill
3 Cathy
4 Daisy
5 Emily
6 Fir

という結果セットを(一度のクエリで)得たい場合に使用します。

書き方も単純で、

(SELECT `id`, `name` FROM `user_a` WHERE ...)
UNION
(SELECT `id`, `name` FROM `user_b` WHERE ...)

と、二つのクエリを「UNION」でつなぐだけです。
二つのクエリはカッコで括らなくても動作しますが、
明示的に括ってしまったほうが可読性は上がるかと思います。
(後述の注意点も参照のこと)

注意点

デフォルトでは、足し合わせた結果セット内に重複レコードが発生する場合、除かれます。
重複していても削除したくない場合は「UNION ALL」とすればOKです。

また、ORDER句の取り扱いにはやや注意が必要です。

-- ORDER/LIMITで絞り込んだクエリ同士をUNIONする
-- (このパターンでは、カッコを外して書くとエラーになります!)
(SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT 5)
UNION
(SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT 5)

-- UNIONした後のクエリをORDER/LIMITで絞り込む
(SELECT ... FROM ... WHERE ...)
UNION
(SELECT ... FROM ... WHERE ...)
ORDER BY ... LIMIT 10

詳細は公式マニュアルを参照してください。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.9.4 UNION 構文