データベースを効率的に運用するためには、データを適切な単位で分割して管理する「正規化」が欠かせません。
分散されたデータから必要な情報を引き出す際、SQLにおいて「別のテーブルから値を取得する」という操作は最も頻繁に利用される技術の一つです。
複数のテーブルを一つの結果としてまとめる結合(JOIN)や、クエリの中に別のクエリを埋め込むサブクエリなど、用途に応じたさまざまな手法が存在します。
本記事では、実務で頻繁に利用されるJOINから、より高度な制御が可能なサブクエリ、さらに近年推奨されるCTE(共通テーブル式)まで、具体的な使い分けのポイントを交えて詳しく解説します。
データ抽出の精度を高め、パフォーマンスの最適化を図るための基礎知識を身に付けていきましょう。
1. 複数のテーブルを結合してデータを取得するJOIN
リレーショナルデータベース(RDB)において、関連するテーブル同士を紐づけてデータを取得する最も標準的な手法がJOIN(結合)です。
JOINを利用することで、IDなどの共通カラムをキーにして、異なるテーブルにある属性情報を一つの行として統合できます。
INNER JOIN(内部結合)の基本
INNER JOINは、結合する両方のテーブルに共通して存在するデータのみを取得する方法です。
例えば、「社員テーブル」と「部署テーブル」を結合し、部署に所属している社員の名前と部署名を表示する場合に使用します。
-- 社員名とその所属部署名を取得する
SELECT
e.employee_id,
e.employee_name,
d.department_name
FROM
employees AS e
INNER JOIN
departments AS d
ON
e.department_id = d.department_id;
employee_id | employee_name | department_name
------------+---------------+-----------------
1 | 田中 太郎 | 営業部
2 | 佐藤 花子 | 開発部
3 | 鈴木 一郎 | 営業部
このように、結合条件(ON句)に一致しないデータは結果から除外されるのがINNER JOINの特徴です。
LEFT JOIN(左外部結合)の活用
実務で非常によく使われるのがLEFT JOINであり、これは左側(FROM句)のテーブルの全レコードを保持しつつ、右側のテーブルから一致するデータを取得します。
もし右側のテーブルに一致するデータがない場合、そのカラムの値はNULLとして表示されます。
「部署に所属していない新入社員」なども含めて全社員をリストアップしたい場合に最適です。
-- 部署未所属の社員も含めて取得する
SELECT
e.employee_name,
d.department_name
FROM
employees AS e
LEFT JOIN
departments AS d
ON
e.department_id = d.department_id;
employee_name | department_name
--------------+-----------------
田中 太郎 | 営業部
佐藤 花子 | 開発部
高橋 次郎 | NULL
データの欠落を防ぎたい場合は、INNER JOINよりもLEFT JOINを選択するのが安全です。
2. クエリの中で別のクエリを実行するサブクエリ
サブクエリ(副問い合わせ)とは、SQL文の中に記述された別のSELECT文のことを指します。
JOINとは異なり、一時的に値を計算したり、特定の条件に合致するデータのみを抽出したりする際に非常に便利です。
スカラ・サブクエリによる単一値の取得
スカラ・サブクエリは、一つの値(1行1列)を返すサブクエリで、SELECT句の中で別のテーブルの値を参照する際に利用されます。
-- 各製品の価格と、全製品の平均価格を並べて表示する
SELECT
product_name,
price,
(SELECT AVG(price) FROM products) AS average_price
FROM
products;
product_name | price | average_price
-------------+-------+---------------
PC | 120000| 85000
Mouse | 5000 | 85000
Monitor | 30000 | 85000
このように、集計した値を各行に付与したい場合に強力な武器となります。
相関サブクエリの仕組み
相関サブクエリは、外側のメインクエリの値をサブクエリの中で参照しながら実行される手法です。
「各カテゴリーの中で最も高い価格の商品を取得する」といった複雑な条件抽出に使用されます。
-- カテゴリーごとに最高値の商品を抽出する
SELECT
category_id,
product_name,
price
FROM
products AS p1
WHERE
price = (
SELECT MAX(price)
FROM products AS p2
WHERE p1.category_id = p2.category_id
);
ただし、相関サブクエリはメインクエリの行数分だけサブクエリが実行される可能性があるため、大量のデータを扱う際はパフォーマンスに注意が必要です。
3. JOINとサブクエリの使い分けのポイント
別のテーブルから値を取得するという目的において、JOINとサブクエリはどちらでも同じ結果を得られる場合があります。
エンジニアとしてどちらを採用すべきか判断するための基準を整理しておきましょう。
| 特徴 | JOIN | サブクエリ |
|---|---|---|
| 主な用途 | 複数の属性情報を結合して表示する | 計算値やフィルタリングの条件にする |
| 可読性 | 構造がシンプルで読みやすい | 複雑になると入れ子が深く読みづらい |
| 実行速度 | 最適化されやすく高速なことが多い | データ量が多いと低速化のリスクがある |
基本的には、「複数のカラムを取得したい場合はJOIN」、「条件判定や一つの値を計算したい場合はサブクエリ」と使い分けるのが定石です。
4. 読みやすさを劇的に向上させるCTE(共通テーブル式)
複雑なサブクエリが増えてくると、SQLのコードは非常に読みづらくなり、メンテナンス性が低下します。
そこで活用したいのが、WITH句を用いたCTE(Common Table Expressions)です。
CTEを使用すると、一時的な結果セットに名前を付けて、後続のクエリでテーブルのように参照できます。
-- CTEを使って集計結果をJOINする
WITH CategoryAvg AS (
SELECT
category_id,
AVG(price) AS avg_price
FROM
products
GROUP BY
category_id
)
SELECT
p.product_name,
p.price,
ca.avg_price
FROM
products AS p
JOIN
CategoryAvg AS ca ON p.category_id = ca.category_id
WHERE
p.price > ca.avg_price;
このクエリは、「各カテゴリーの平均価格よりも高い商品」を抽出するものです。
サブクエリを直接WHERE句やSELECT句に書くよりも、処理の論理的な流れが明確になるというメリットがあります。
5. 値の存在チェック:EXISTSとINの使い分け
別のテーブルにデータが存在するかどうかを確認して、メインのテーブルから値を取得するケースも多々あります。
この際に利用されるのがIN演算子とEXISTS演算子です。
IN演算子の特徴
INは、指定したリストやサブクエリの結果の中に値が含まれているかを判定します。
直感的に理解しやすいため、比較的小さなデータセットに対して有効です。
-- 注文履歴がある顧客のみを取得する
SELECT
customer_name
FROM
customers
WHERE
customer_id IN (SELECT customer_id FROM orders);
EXISTS演算子のメリット
EXISTSは、サブクエリが少なくとも1行のデータを返すかどうかを確認します。
一致するデータが見つかった時点でスキャンを終了するため、大きなテーブル同士の比較ではINよりもEXISTSの方がパフォーマンスに優れる傾向があります。
-- EXISTSを使った存在チェック
SELECT
customer_name
FROM
customers AS c
WHERE
EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.customer_id
);
6. パフォーマンスを意識したデータ取得のコツ
どれほど正確なSQLを書いたとしても、実行速度が遅ければ実用には耐えません。
別のテーブルから値を取得する際は、以下の3つのポイントを常に意識しましょう。
インデックスの確認
JOINの結合キーとなるカラムや、WHERE句で指定するカラムには、適切なインデックスが貼られている必要があります。
インデックスがない場合、データベースはテーブル全体を走査(フルスキャン)するため、データ量に比例して処理時間が膨大になります。
SELECT * を避ける
別のテーブルから値を取得する際、SELECT *を使ってすべてのカラムを取得するのは避けましょう。
必要なカラム名だけを明示することで、ネットワークの負荷を軽減し、メモリの消費効率を高めることができます。
実行計画(EXPLAIN)の活用
クエリがどのように実行されるかを確認するために、EXPLAINコマンドを活用しましょう。
どのテーブルが先に読み込まれ、どのインデックスが使われているかを知ることで、ボトルネックの解消に繋がります。
まとめ
SQLで別のテーブルから値を取得する方法には、JOIN、サブクエリ、CTEなど複数のアプローチがあります。
単純なデータの紐づけであればINNER JOINやLEFT JOIN、一時的な計算結果を利用するならサブクエリ、そして可読性を重視するならCTEというように、状況に応じた最適な選択が求められます。
また、実務においては正確性だけでなく、インデックスの活用や実行計画の確認といったパフォーマンス面への配慮も欠かせません。
それぞれの構文が持つ特性を深く理解し、効率的でメンテナンス性の高いクエリを書けるよう練習を重ねていきましょう。
今回紹介したテクニックを駆使することで、複雑なデータ構造からも自在に必要な情報を引き出せるようになるはずです。
