Q1FROM 句にサブクエリ(派生表)を書くときに必要なものはどれですか。
サブクエリ ② — FROM 派生表・SELECT 句・CREATE / INSERT SELECT
FROM の派生表、SELECT 句のサブクエリ、CASE との併用、CREATE / INSERT SELECT を社員・売上データで実行しながら学べます。
本記事で使うデータ — employee / department / sales
前回はサブクエリを WHERE で使いました。
本記事ではサブクエリを `FROM` 句に置く(派生表)・`SELECT` の列に書く・`CASE` と併用する・サブクエリの結果から新しいテーブルを作る という 4 つの応用を順に押さえます。
題材は社員データ 3 表です。
employee(社員 30 名)・department(部署 6 件)・sales(売上明細 50 件)を使い、部署ごとの集計表を作ったり、社員ごとの売上合計を列に並べたりします。
FROM 派生表 — サブクエリの結果を 1 つの表として扱う
派生表(derived table)とは、FROM 句にサブクエリを書いて その結果をひとつの仮想テーブルとして扱う 書き方です。
「部署ごとの平均給与」を先に集計表として作り、その表に対してさらに JOIN や WHERE をかけられます。
集計した結果をもう一段絞り込みたいときに便利です。
派生表で重要なルールは 必ず別名を付ける ことです。
FROM (SELECT ...) AS ds のように AS 別名(SQLite では AS 省略も可)を付けないとエラーになります。
付けた別名を通して ds.avg_salary のように派生表の列を参照します。
-- FROM 派生表: 部署ごとの人数と平均給与を集計し department と結合
SELECT d.dept_name, ds.headcount, ds.avg_salary
FROM (
SELECT dept_id, COUNT(*) AS headcount, AVG(salary) AS avg_salary
FROM employee
WHERE dept_id IS NOT NULL
GROUP BY dept_id
) AS ds
JOIN department d ON d.dept_id = ds.dept_id
ORDER BY ds.avg_salary DESC;
SELECT 句のサブクエリ — 列の値として 1 つの値を埋め込む
SELECT の列リストにスカラサブクエリを書くと、行ごとに 1 つの値を計算した列 を追加できます。
(SELECT SUM(sale.amount) FROM sales sale WHERE sale.emp_id = emp.emp_id) を列に置けば、社員ごとに「その社員の売上合計」を 1 列として並べられます。
これは外側の emp.emp_id を参照する相関サブクエリです。
-- SELECT 句のサブクエリ: 社員ごとの売上合計を 1 列として追加
SELECT emp.name,
COALESCE(
(SELECT SUM(sale.amount) FROM sales sale WHERE sale.emp_id = emp.emp_id),
0
) AS total_amount
FROM employee emp
ORDER BY emp.emp_id
LIMIT 6;
売上が 1 件もない社員はサブクエリが NULL を返すので、COALESCE(..., 0) で 0 に置き換えると表が読みやすくなります。
SELECT 句のサブクエリも 1 行 1 列 に収まる必要があります。
CASE と併用する — サブクエリの値で分岐ラベルを付ける
SELECT 句のサブクエリは CASE の条件にもそのまま書けます。
「売上合計が 150 万以上なら High、0 より大きいなら Mid、それ以外は None」のようなランク列を、サブクエリの値を CASE WHEN で判定して作れます。
同じサブクエリを CASE の各 WHEN で繰り返し書く必要がある点に注意してください(読みやすさを優先するなら、後の章で学ぶ WITH(共通テーブル式) で 1 度だけ書く方法もあります)。
本記事ではサブクエリと CASE を直接組み合わせる基本形を押さえます。
-- 売上件数で社員を分類
SELECT emp.name,
(SELECT COUNT(*) FROM sales sale WHERE sale.emp_id = emp.emp_id) AS sale_count,
CASE
WHEN (SELECT COUNT(*) FROM sales sale WHERE sale.emp_id = emp.emp_id) >= 4 THEN 'Frequent'
WHEN (SELECT COUNT(*) FROM sales sale WHERE sale.emp_id = emp.emp_id) >= 1 THEN 'Occasional'
ELSE 'None'
END AS activity
FROM employee emp
ORDER BY sale_count DESC, emp.emp_id
LIMIT 8;
CREATE / INSERT SELECT — サブクエリの結果をテーブルにする
CREATE TABLE 新表 AS SELECT ... と書くと、SELECT の結果をそのまま新しいテーブルとして保存 できます(CTAS: Create Table As Select)。
集計結果をスナップショットとして残したいときによく用います。
すでにあるテーブルに行を足したいときは INSERT INTO 既存表 SELECT ... を使い、SELECT した結果を一括で追加します。
これらはテーブルを作る・行を増やす 書き込み操作 です。
本記事では集計用の作業テーブル top_seller を使い切りで作って試します。
何度実行しても同じ結果になるよう、作成前に DROP TABLE IF EXISTS で作業テーブルを片付けてから作り直します。
まとめて INSERT する方が速い
INSERT INTO 表 SELECT ... は対象行を 1 つの文でまとめて挿入 します。
INSERT INTO 表 VALUES (...) を行数ぶん繰り返すより、SQL の解析・インデックス更新・トランザクション処理が 1 回で済むため、入れる行が多いほど速く なります。
別テーブルや集計結果をコピー・移送するときは、1 行ずつ繰り返し INSERT するより INSERT ... SELECT の一括挿入を優先します。
-- 部署別の集計をスナップショット表に保存
DROP TABLE IF EXISTS dept_summary;
CREATE TABLE dept_summary AS
SELECT dept_id, COUNT(*) AS headcount, AVG(salary) AS avg_salary
FROM employee
WHERE dept_id IS NOT NULL
GROUP BY dept_id;
SELECT * FROM dept_summary ORDER BY avg_salary DESC;
理解度チェック
まずは1問ずつ答えてみましょう。
Q2SELECT emp.name, (SELECT SUM(sale.amount) FROM sales sale WHERE sale.emp_id = emp.emp_id) AS total FROM employee emp で、売上が 1 件もない社員の total 列はどうなりますか。
Q3CREATE TABLE top_seller AS SELECT ... の説明として正しいものはどれですか。