IPA|情報処理技術者試験

SQLの壁を越える!DBA午後試験で必須のJOIN、サブクエリ、集合演算の実践テクニック

第1回の『Window関数』編では、1つの結果セット内で順位付けや移動平均といった高度な分析を行う方法を学びました。

しかし、実際の業務データは、関連する情報が複数のテーブルに分割されて格納されているのが一般的です。そこで第2回となる今回は、それらのテーブルを自在に「結合」「比較」「組み合わせる」ための、SQL中級者以上を目指す上で避けては通れないテクニックを徹底解説します。

INNER JOINとの違いが紛らしいOUTER JOININEXISTSの使い分けが問われるサブクエリ、そして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_idNULLの田中さんや、誰も所属していない総務部は結果に含まれません。

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_nameNULLとして取得できている点に注目してください。

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 JOINWHERE ... 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 INNOT 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関数など、知っていると差が付く実用的なテクニックを解説しますので、ご期待ください。

-IPA|情報処理技術者試験