第1回の『Window関数』編では、1つの結果セット内で順位付けや移動平均といった高度な分析を行う方法を学びました。
しかし、実際の業務データは、関連する情報が複数のテーブルに分割されて格納されているのが一般的です。そこで第2回となる今回は、それらのテーブルを自在に「結合」「比較」「組み合わせる」ための、SQL中級者以上を目指す上で避けては通れないテクニックを徹底解説します。
INNER JOIN
との違いが紛らしいOUTER JOIN
、IN
とEXISTS
の使い分けが問われるサブクエリ、そしてSQLを劇的に整理するWITH
句まで、データベーススペシャリスト試験の午後問題で頻出の論点を網羅。これらの技術をマスターすれば、複雑な要件のデータ抽出も恐れるに足りません。
早速、テーブル結合の世界へ進んでいきましょう。
目次
OUTER JOINを完全理解│INNER JOINとの違いとON句の重要性
テーブル結合の基本であるINNER JOIN
は、両方のテーブルに共通するキーが存在する行だけを取得します。しかし、実務では「片方のテーブルにしか存在しないデータ」も同時に取得したいケースが頻繁にあります。
【思考プロセス】なぜINNER JOINだけでは不足なのか?
例えば、従業員テーブルと部署テーブルがあるとします。この2つをINNER JOIN
で結合すると、必ずどこかの部署に所属している従業員しかリストアップできません。
もし「まだどの部署にも配属されていない新人従業員」や、「設立されたばかりでまだ誰も所属していない部署」の情報を取得したい場合、INNER JOIN
ではデータが欠落してしまいます。この課題を解決するのがOUTER JOIN
(外部結合)です。
OUTER JOINの種類とイメージ
OUTER JOIN
は、基準となるテーブル(主テーブル)の行をすべて残し、そこにもう片方のテーブルを結合します。もし相手テーブルに対応するデータがなければ、その列はNULL
として表示されます。
種類 | 説明 |
---|---|
LEFT OUTER JOIN |
左側のテーブルの行をすべて取得する。 |
RIGHT OUTER JOIN |
右側のテーブルの行をすべて取得する。 |
FULL OUTER JOIN |
両方のテーブルの行をすべて取得する。 |
一般的に、LEFT JOIN
が最もよく使われます。(OUTER
は省略可能です)
【具体例】INNER JOINとLEFT JOINの結果の違い
テーブル:employees
id | name | dept_id |
---|---|---|
1 | 佐藤 | 10 |
2 | 鈴木 | 20 |
3 | 高橋 | 10 |
4 | 田中 | NULL |
テーブル:departments
id | dept_name |
---|---|
10 | 営業部 |
20 | 開発部 |
30 | 総務部 |
INNER JOIN
の場合
両方のテーブルにdept_id
が存在する行だけが結合されます。dept_id
がNULL
の田中さんや、誰も所属していない総務部は結果に含まれません。
SELECT e.name, d.dept_name
FROM employees AS e
INNER JOIN departments AS d ON e.dept_id = d.id;
LEFT JOIN
の場合
FROM
句で指定した左側のテーブル(employees
)の行はすべて表示されます。部署に未所属の田中さんのdept_name
はNULL
として取得できている点に注目してください。
SELECT e.name, d.dept_name
FROM employees AS e
LEFT JOIN departments AS d ON e.dept_id = d.id;
このように、OUTER JOIN
はマスタデータに対するトランザクションデータの有無を確認するといった要件で必須のテクニックとなります。結合条件を記述するON
句と、結合結果全体からさらに行を絞り込むWHERE
句の役割の違いを意識することが重要です。
サブクエリの基本と応用│IN, EXISTS, NOT EXISTSを使い分ける
サブクエリ(副問合せ)は、その名の通り「SQL文の中に入れ子で記述する別のSQL文」のことです。WHERE
句やFROM
句などで使い、より複雑な条件でのデータ抽出を可能にします。午後試験では、特にIN
, EXISTS
, NOT EXISTS
の適切な使い分けが問われます。
IN
: 値の「リスト」に含まれるかを判定
IN
は、サブクエリが返した結果セット(値のリスト)に、対象の列の値が含まれているかどうかを判定します。
【具体例】特定の条件に合致する部署に所属する従業員を抽出
「東京本社に所属する全部署の従業員一覧」を取得したい場合を考えます。まずサブクエリで「東京本社の部署IDリスト」を取得し、そのリストにdept_id
が含まれる従業員を外側のクエリで抽出します。
SELECT
employee_name
FROM
employees
WHERE
dept_id IN ( -- ここからサブクエリ
SELECT
dept_id
FROM
departments
WHERE
location = '東京本社'
); -- サブクエリここまで
IN
は直感的で分かりやすいですが、サブクエリの結果セットが非常に大きくなるとパフォーマンスが低下することがあります。
EXISTS
: データが「存在するか否か」を判定
EXISTS
は、サブクエリが1行でも結果を返せばTRUE(真)、1行も返さなければFALSE
(偽)を返す演算子です。IN
のように値そのものを比較するのではなく、単に「条件に合致する行が存在するか」だけをチェックします。
EXISTS
では、外側のクエリの値をサブクエリの中で参照する相関サブクエリが使われるのが一般的です。
SELECT
e.employee_name
FROM
employees AS e
WHERE
EXISTS ( -- ここから相関サブクエリ
SELECT
1 -- 何をSELECTしても結果は同じ
FROM
departments AS d
WHERE
d.dept_id = e.dept_id -- 外側のテーブルeを参照
AND d.location = '東京本社'
);
データベースは条件に合う行を1件見つけた瞬間にサブクエリの評価を打ち切れるため、IN
よりも効率的に動作することが多いです。
【頻出】NOT EXISTS
による「アンチジョイン」
NOT EXISTS
は、データベーススペシャリスト試験で特に重要です。「~に存在しない」という条件を指定する際に威力を発揮し、このパターンをアンチジョインと呼びます。
【具体例】従業員が一人も所属していない部署を抽出
OUTER JOIN
とWHERE ... IS NULL
でも同じ結果を得られますが、NOT EXISTS
を使うと「~という条件を満たす従業員が存在しない部署」という意図がSQLで明確に表現できます。
SELECT
d.dept_name
FROM
departments AS d
WHERE
NOT EXISTS ( -- この部署IDを持つ従業員が存在しない
SELECT
1
FROM
employees AS e
WHERE
e.dept_id = d.dept_id
);
このNOT EXISTS
を使いこなせるかは、SQLスキルの一つの指標となります。パフォーマンス面でも有利なことが多いため、積極的に使えるようにしておきましょう。
問い合わせ結果を操作する集合演算子│UNIONとEXCEPT
JOIN
がテーブル同士を横方向に結合して列を増やすのに対し、集合演算子は複数のSELECT
文の結果セットを縦方向に連結したり、比較したりします。
この違いを理解することが、集合演算子をマスターする第一歩です。
JOIN
と集合演算子のイメージの違い
JOIN
: テーブルAとテーブルBをキーで紐づけ、列を増やす。(ファスナーで服を閉じるイメージ)- 集合演算子: クエリAの結果とクエリBの結果を、積み木のように縦に積んだり、見比べたりするイメージ。
UNION
/ UNION ALL
: 結果セットの和集合
UNION
は、2つ以上のSELECT
文の結果を1つにまとめます。その際、重複する行は自動的に排除されます。もし重複行もすべて含めたい場合は、UNION ALL
を使用します。パフォーマンスはUNION ALL
の方が高速です。
【注意点】
UNION
で連結する各SELECT
文は、列の数と対応するデータ型が一致している必要があります。
【具体例】東京支社と大阪支社の従業員リストを統合する
-- 東京支社の従業員
SELECT employee_id, employee_name FROM employees WHERE branch = '東京';
UNION
-- 大阪支社の従業員
SELECT employee_id, employee_name FROM employees WHERE branch = '大阪';
もし両方の支社に重複して登録されている従業員がいた場合、UNION
では1名として集約され、UNION ALL
では2名としてリストアップされます。
EXCEPT
: 結果セットの差集合
EXCEPT
は、最初のSELECT
文の結果から、2番目のSELECT
文の結果に含まれる行を取り除いた差集合を返します。データベース製品によってはMINUS
というキーワードで実装されています。
【具体例】「優良顧客」ではない「東京支社の顧客」を抽出する
NOT IN
やNOT EXISTS
でも似たことは可能ですが、2つのリストを単純に比較して差分を求めたい場合はEXCEPT
が最も直感的です。
-- 東京支社の全顧客リスト
SELECT customer_id, customer_name FROM customers WHERE branch = '東京';
EXCEPT
-- 優良顧客リスト
SELECT customer_id, customer_name FROM premium_customers;
このクエリは、「東京支社の顧客ではあるが、優良顧客リストには載っていない顧客」だけを返します。
その他の集合演算子
INTERSECT
: 2つの結果セットの両方に存在する行のみを返す積集合です。
これらの集合演算子は、複数の条件で抽出した結果を加工・整形する際に強力なツールとなります。特にEXCEPT
はデータクレンジングや差分チェックなどで役立ちます。
SQLを劇的に見やすくするWITH句(CTE)の使い方
複雑なSQLを記述していると、サブクエリが何重にもネスト(入れ子)してしまい、解読が困難になることがあります。WITH
句(共通テーブル式:Common Table Expression)は、このようなSQLを劇的に読みやすく、整理するための強力な機能です。
WITH
句を使うと、サブクエリに一時的な名前を付けて、メインのSQL文の前に定義しておくことができます。
【メリット】可読性と再利用性の向上
- 可読性: 処理のステップごとに名前を付けて分割できるため、上から順にロジックを追いやすくなります。
- 再利用性: 一度定義した一時テーブルは、後続のクエリで何度も参照できます。
【具体例】サブクエリのネストをWITH句でリファクタリング
例えば、「各部署の平均給与を算出し、その平均給与よりも高い給与をもらっている従業員をリストアップする」という要件を考えます。
WITH
句を使わない場合
FROM
句の中にサブクエリを記述することになり、どの部分が何をしているのか一見して分かりにくいです。
SELECT
e.employee_name,
e.salary,
dept_avg.avg_salary AS department_average
FROM
employees AS e
INNER JOIN (
-- ここがサブクエリ。部署ごとの平均給与を算出
SELECT
dept_id,
AVG(salary) AS avg_salary
FROM
employees
GROUP BY
dept_id
) AS dept_avg ON e.dept_id = dept_avg.dept_id
WHERE
e.salary > dept_avg.avg_salary; -- 部署平均より高い給与の従業員を絞り込み
WITH
句を使った場合
まず「部署ごとの平均給与算出」という処理をdept_avg_cte
という名前で定義します。メインのSELECT
文では、その名前を付けた一時テーブルを参照するだけなので、構造が非常にシンプルになります。
-- まず「部署ごとの平均給与」という中間テーブルを「dept_avg_cte」として定義
WITH dept_avg_cte AS (
SELECT
dept_id,
AVG(salary) AS avg_salary
FROM
employees
GROUP BY
dept_id
)
-- 上で定義した中間テーブルを使って、メインの処理を記述
SELECT
e.employee_name,
e.salary,
d.avg_salary AS department_average
FROM
employees AS e
INNER JOIN dept_avg_cte AS d ON e.dept_id = d.dept_id
WHERE
e.salary > d.avg_salary;
このように、WITH
句は複雑な処理を意味のある単位で部品化し、SQL全体の可読性を飛躍的に高めます。データベーススペシャリストの午後試験で長文SQLを記述・読解する際には必須のテクニックです。
また、応用としてWITH RECURSIVE
を使うと、組織図のような階層構造データを扱うことも可能です。
まとめ:結合と集合を制して、複雑なデータ抽出を自在に操る
今回は、データベーススペシャリスト試験の午後問題で頻出の「テーブル結合」「サブクエリ」「集合演算」といった、複数のデータを扱うための必須テクニックを解説しました。
それぞれの構文は目的が明確に異なります。どの場面で何を使うべきか、頭の中を整理しておきましょう。
OUTER JOIN
: データが欠落しないようにテーブルを横に結合したいときに使う。NULL
を許容することで、主テーブルの情報をすべて保持するのが目的。- サブクエリ (
IN
,EXISTS
): ある問い合わせ結果を条件として、別の問い合わせを行いたいときに使う。特にNOT EXISTS
は「~ないものを探す」アンチジョインの型として非常に強力。 - 集合演算子 (
UNION
,EXCEPT
): 2つ以上の問い合わせ結果そのものを、縦に連結したり(和集合)、差し引いたり(差集合)したいときに使う。 WITH
句 (CTE): 上記を含む複雑なSQL文の可読性を高めたいときに使う。処理のステップを部品化し、全体のロジックを明確にするための武器。
これらのテクニックを自在に組み合わせることで、午後試験で要求される複雑なデータ抽出要件のほとんどに対応できます。まずはそれぞれの役割を正確に理解し、簡単なSQLからでも実際に書いて試してみることが重要です。
次回は、いよいよこの連載の最終回となる『第3回:SQL応用テクニック集!カーソル・便利関数・制約定義編』をお届けします。CASE
式やCOALESCE
関数など、知っていると差が付く実用的なテクニックを解説しますので、ご期待ください。