Oracle SQL 副問い合わせ (サブクエリ) 入門!基礎から高速化まで

Oracle Master Silver

SQL を書く際、「条件に別の集計結果を使いたい」「2段階の処理を1回で済ませたい」と悩むことはありませんか?

Oracle 副問い合わせ(サブクエリ)を活用すると、複雑なデータ取得をシンプルに記述でき、アプリケーションのパフォーマンス向上にも繋がります。

この記事では、Oracle Database 19c を前提に、副問い合わせの基本構文から、相関副問い合わせ、EXISTSWITH 句を使った実践的なテクニックまでを網羅的に解説します。

💰 【PR】Oracleエンジニアの市場価値、調べてみませんか?

Oracleのスキルは需要が高く、特定の資格や経験を持っていると年収が大幅にアップするケースがあります。まずはIT専門のエージェントで非公開求人をチェックしてみませんか?


結論:副問い合わせ活用のポイント

時間がない方向けに、この記事の要点をリストアップしました。

  • 副問い合わせとは:SQL 文の中に埋め込まれた「入れ子」の SELECT 文。
  • 使い分け
    • スカラー:結果が「1行1列」。= などで比較。
    • 複数行:結果が「リスト」。IN で比較。
    • 相関:外側の行ごとに評価される。重くなりやすいので注意。
  • ベストプラクティス:可読性と再利用性を高めるなら WITH の利用を推奨。
  • 注意点NOT IN は NULL を含むと結果が空になる(NOT EXISTS を使う)。

1. 副問い合わせ(サブクエリ)の基礎

用語の定義と仕組み

副問い合わせ(Subquery)とは、メインの SQL 文(外側のクエリ)の中に記述される、括弧 () で囲まれた SELECT 文のことです。

SQL は通常、内側の副問い合わせから先に実行され、その結果を使って外側のメインクエリが実行されます(※相関副問い合わせを除く)。

  • メインクエリ:親となる SQL(外側)
  • サブクエリ:子となる SQL(内側)

2. 実習用サンプルデータの作成

解説と合わせて手元の環境で動作確認できるよう、サンプル表を作成します。

※SQL 内にマルチバイト文字(全角)を含まない形式で作成します。

前提条件

  • 接続ユーザー:一般的な作成権限を持つユーザー(SCOTT 等)
  • 環境:Oracle Database 19c Enterprise/Standard Edition 2

以下の SQL を実行して、EMPLOYEES(従業員)と DEPARTMENTS(部署)を作成してください。

-- テーブルが既に存在する場合は削除(任意)
-- DROP TABLE employees PURGE;
-- DROP TABLE departments PURGE;

-- 1. 部署表の作成
CREATE TABLE departments (
    dept_id   NUMBER PRIMARY KEY,
    dept_name VARCHAR2(50)
);

-- 2. 従業員表の作成
CREATE TABLE employees (
    emp_id    NUMBER PRIMARY KEY,
    emp_name  VARCHAR2(50),
    dept_id   NUMBER,
    salary    NUMBER,
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);

-- 3. データ挿入
-- 部署: 1=Sales, 2=Dev, 3=HR, 4=Marketing(空の部署)
INSERT INTO departments VALUES (1, 'Sales');
INSERT INTO departments VALUES (2, 'Dev');
INSERT INTO departments VALUES (3, 'HR');
INSERT INTO departments VALUES (4, 'Marketing');

-- 従業員
INSERT INTO employees VALUES (1, 'Taro Tanaka', 1, 300000);
INSERT INTO employees VALUES (2, 'Jiro Suzuki', 2, 400000);
INSERT INTO employees VALUES (3, 'Saburo Sato', 1, 350000);
INSERT INTO employees VALUES (4, 'Shiro Yamada', 3, 500000);

COMMIT;

▼作成されたテーブル

SQL> SELECT * FROM departments;

DEPT_ID DEPT_NAME
---------- --------------------
1 Sales
2 Dev
3 HR
4 Marketing

SQL> SELECT * FROM employees;

EMP_ID EMP_NAME DEPT_ID SALARY
---------- -------------------- ---------- ----------
1 Taro Tanaka 1 300000
2 Jiro Suzuki 2 400000
3 Saburo Sato 1 350000
4 Shiro Yamada 3 500000

3. 実践:副問い合わせの種類と書き方

3-1. スカラー副問い合わせ(単一行)

副問い合わせの結果が「1行、かつ1列」であるパターンです。= < > などの比較演算子で利用します。

例:’Sales’ 部門(IDが不明)に所属する従業員を取得する

SELECT emp_name, salary
FROM   employees
WHERE  dept_id = (
           SELECT dept_id 
           FROM   departments 
           WHERE  dept_name = 'Sales'
       );

▼実行結果

SQL> SELECT emp_name, salary
2 FROM employees
3 WHERE dept_id = (
4 SELECT dept_id
5 FROM departments
6 WHERE dept_name = 'Sales'
7 );

EMP_NAME SALARY
-------------------------------------------------- ----------
Taro Tanaka 300000
Saburo Sato 350000

解説

カッコ内の SQL が先に実行され、結果の 1 が返ります。その後、メインクエリが WHERE dept_id = 1 として実行されます。

3-2. 複数行の副問い合わせ

副問い合わせの結果が「複数行」になるパターンです。= で繋ぐとエラーになるため、IN 演算子を使用します。

例:’Sales’ または ‘Dev’ 部門に所属する従業員を取得する

SELECT emp_name, salary
FROM   employees
WHERE  dept_id IN (
           SELECT dept_id 
           FROM   departments 
           WHERE  dept_name IN ('Sales', 'Dev')
       );

▼実行結果

SQL> SELECT emp_name, salary
2 FROM employees
3 WHERE dept_id IN (
4 SELECT dept_id
5 FROM departments
6 WHERE dept_name IN ('Sales', 'Dev')
7 );

EMP_NAME SALARY
-------------------------------------------------- ----------
Taro Tanaka 300000
Jiro Suzuki 400000
Saburo Sato 350000

実行結果のイメージ

サブクエリが 1 と 2 を返します。メインクエリは WHERE dept_id IN (1, 2) として評価されます。

3-3. 相関副問い合わせ

ここが初学者の壁になりやすいポイントです。

副問い合わせの中で、外側のテーブルの列を参照しているパターンです。

例:自分の部署の「平均給与」よりも高い給与をもらっている従業員を探す

SELECT e.emp_name, e.salary, e.dept_id
FROM   employees e
WHERE  e.salary > (
           SELECT AVG(s.salary) 
           FROM   employees s 
           WHERE  s.dept_id = e.dept_id
       );

▼実行結果

SQL> SELECT e.emp_name, e.salary, e.dept_id
2 FROM employees e
3 WHERE e.salary > (
4 SELECT AVG(s.salary)
5 FROM employees s
6 WHERE s.dept_id = e.dept_id
7 );

EMP_NAME SALARY DEPT_ID
-------------------------------------------------- ---------- ----------
Saburo Sato 350000 1

解説

  1. メインクエリが employees テーブル(e)から1行読み込みます(例:Taro Tanaka, Dept 1)。
  2. サブクエリが実行されます。この時 WHERE s.dept_id = e.dept_ide.dept_id1 が代入されます。
  3. Dept 1 の平均給与(325,000)が計算されます。
  4. Taro の給与(300,000)と比較されます(300,000 > 325,000 は False)。
  5. 次の行へ進み、繰り返します。

💰 【PR】Oracleエンジニアの市場価値、調べてみませんか?

Oracleのスキルは需要が高く、特定の資格や経験を持っていると年収が大幅にアップするケースがあります。まずはIT専門のエージェントで非公開求人をチェックしてみませんか?


4. 応用:EXISTS と WITH 句

4-1. EXISTS / NOT EXISTS(存在確認)

行のデータそのものではなく、「データが存在するかどうか」だけをチェックします。特に大量データを扱う際、IN よりも高速に動作する場合があります。

例:従業員が誰もいない部署を探す(NOT EXISTS)

SELECT d.dept_name
FROM   departments d
WHERE  NOT EXISTS (
           SELECT 1
           FROM   employees e
           WHERE  e.dept_id = d.dept_id
       );

▼実行結果

SQL> SELECT d.dept_name
2 FROM departments d
3 WHERE NOT EXISTS (
4 SELECT 1
5 FROM employees e
6 WHERE e.dept_id = d.dept_id
7 );

DEPT_NAME
--------------------------------------------------
Marketing

(Marketing 部門には誰も配属していないため)

4-2. WITH 句(Common Table Expression)

SQL が長くなる場合、副問い合わせを WITH 句として先頭に切り出すことで、可読性が劇的に向上します。現代の Oracle SQL 開発では推奨される書き方です。

例:各部署の平均給与を計算し、それより高い給与の従業員をリスト化(相関副問い合わせの書き換え)

-- 前処理:部署ごとの平均給与を定義
WITH dept_avg AS (
    SELECT dept_id, AVG(salary) AS avg_sal
    FROM   employees
    GROUP  BY dept_id
)
-- メイン処理
SELECT e.emp_name, e.salary, a.avg_sal
FROM   employees e
JOIN   dept_avg a ON e.dept_id = a.dept_id
WHERE  e.salary > a.avg_sal;

▼実行結果

SQL> WITH dept_avg AS (
2 SELECT dept_id, AVG(salary) AS avg_sal
3 FROM employees
4 GROUP BY dept_id
5 )
6 SELECT e.emp_name, e.salary, a.avg_sal
7 FROM employees e
8 JOIN dept_avg a ON e.dept_id = a.dept_id
9 WHERE e.salary > a.avg_sal;

EMP_NAME SALARY AVG_SAL
-------------------------------------------------- ---------- ----------
Saburo Sato 350000 325000

5. トラブルシューティング(よくあるエラー)

副問い合わせ利用時に頻出するエラーと対処法です。

エラーコードエラーメッセージ原因と対処
ORA-01427単一行副問合せにより2つ以上の行が戻されました= で比較しているサブクエリが複数の値を返しています。IN に変えるか、サブクエリの条件を厳しくして1行に絞ってください。
ORA-00904無効な識別子ですサブクエリ内の列名が間違っているか、外側の列を参照する際のエイリアス(別名)指定が正しくありません。
データなし(結果が0件になる)NOT IN の落とし穴です。サブクエリの結果に NULL が含まれると、全体の結果が空になります。NOT EXISTS を使用してください。

6. 運用・パフォーマンス上の注意点

メリットとデメリット

  • メリット:複雑なロジックを1つの SQL で完結できる。アプリ側のコード行数を減らせる。
  • デメリット:ネスト(入れ子)が深すぎると、第三者が読めない「スパゲッティコード」になる。

パフォーマンスの落とし穴

相関副問い合わせは「行数分だけサブクエリが実行」されるため、外側のテーブルが巨大な場合、著しく性能が低下します。

  • 対策:可能であれば JOINWITH 句を使った結合処理に書き換えることを検討してください。
  • 確認:実行計画(Explain Plan)を取得し、インデックスが使われているか確認しましょう。

7. FAQ(よくある質問)

Q1. 副問い合わせと結合(JOIN)はどちらが速いですか?

A. ケースバイケースですが、現在はオプティマイザ(Oracle の最適化機能)が優秀なため、どちらで書いても内部的に同じ処理プランになることが多いです。ただし、相関副問い合わせは遅くなる傾向があるため、大量データの場合は JOIN や WITH 句への書き換えを推奨します。

Q2. UPDATE 文でも副問い合わせは使えますか?

A. はい、可能です。SET 句の値指定や、WHERE 句の条件指定に使用できます。

例:UPDATE employees SET salary = (SELECT …)

Q3. 副問い合わせのネスト(入れ子)に制限はありますか?

A. Oracle の仕様上、サブクエリのネストレベルに厳密な上限はありませんが、可読性とメモリ消費の観点から、深くても3階層程度に留めるのがベストプラクティスです。


8. まとめ

  • 副問い合わせは、SQL の表現力を広げる必須テクニック。
  • 1行なら =、複数行なら IN を使う。
  • 相関副問い合わせは強力だがパフォーマンスに注意。
  • 複雑になりそうなら WITH で整理する。
  • NOT INNULL が混ざると危険。NOT EXISTS を使う習慣をつける。

まずはサンプルコードをコピーして実行し、エラー(ORA-01427)をわざと出してみるなどして挙動を体感してみてください。


本記事は Oracle Database 19c を対象に解説します(他バージョンは画面や既定値が異なる場合があります)。


[参考]
Oracle Database SQL言語リファレンス 19c

💰 【PR】Oracleエンジニアの市場価値、調べてみませんか?

Oracleのスキルは需要が高く、特定の資格や経験を持っていると年収が大幅にアップするケースがあります。まずはIT専門のエージェントで非公開求人をチェックしてみませんか?

コメント

タイトルとURLをコピーしました