
Oracle SQLを学ぶ中で「スカラー副問合せ」という言葉を目にしたことはありませんか?
スカラー副問合せとは、SELECT文の中でさらにSELECT文を記述し、単一の値を返す副問合せのことを指します。集計処理や動的な値の取得に非常に便利ですが、使い方を誤ると ORA-01427
エラーの原因にもなり、初心者がつまずきやすいポイントです。
本記事では、スカラー副問合せの基本的な仕組みから、実際の使用例、エラーが起こるケースとその回避方法までを徹底解説します。Oracle SQLを効率的に扱えるようになりたい方は、ぜひ参考にしてください。
スカラー副問合せとは?
スカラー副問合せ(スカラーサブクエリ)は、単一の値(スカラー値)を返す副問合せ(サブクエリ)のことです。主にSELECT句やWHERE句など式として使われ、1行1列の結果を期待します。Oracleでは、スカラー副問合せは(SELECT ...)
の形式で記述します。
スカラー副問合せの特徴
- 0行(NULL)または1行1列の値を返すことが期待される。
- 2行以上を返すと
ORA-01427: single-row subquery returns more than one row
というエラーになる。 - 相関(外側の行に依存する)する場合は行ごとに評価される。
- 非相関副問合せ は外側の行に依存しないため、DBは最適化により一度だけ実行する場合があります。
- 相関副問合せ は外側の行の値を参照するため、基本的に外側の行ごとに評価されます(結果、コストが高くなる可能性あり)。
スカラー副問合せでよく使われるパターンとSQL例
動作確認の前提
本記事での解説は以下データを登録していることを前提とします。
事前登録データ(DDL)
-- departments テーブル-- DROP TABLE departments CASCADE CONSTRAINTS;CREATE TABLE departments ( dept_id NUMBER PRIMARY KEY, dept_name VARCHAR2(50));
INSERT INTO departments (dept_id, dept_name) VALUES (10, 'Sales');INSERT INTO departments (dept_id, dept_name) VALUES (20, 'HR');INSERT INTO departments (dept_id, dept_name) VALUES (30, 'IT');INSERT INTO departments (dept_id, dept_name) VALUES (40, 'Finance');
-- employees テーブル-- DROP TABLE employees CASCADE CONSTRAINTS;CREATE TABLE employees ( emp_id NUMBER PRIMARY KEY, emp_name VARCHAR2(50), dept_id NUMBER, job VARCHAR2(30), salary NUMBER, CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES departments(dept_id));
-- データ挿入-- Sales 部署(平均よりやや低い)INSERT INTO employees VALUES (1001, 'Alice', 10, 'Sales Rep', 4000);INSERT INTO employees VALUES (1002, 'Bob', 10, 'Sales Rep', 4200);
-- HR 部署(中間層)INSERT INTO employees VALUES (1003, 'Carol', 20, 'HR Manager', 5000);
-- IT 部署(平均より高い)INSERT INTO employees VALUES (1004, 'Dave', 30, 'Developer', 7000);INSERT INTO employees VALUES (1005, 'Eve', 30, 'Developer', 7200);
-- Finance 部署(さらに高い)INSERT INTO employees VALUES (1006, 'Frank', 40, 'Accountant', 9000);
-- bonuses テーブル-- DROP TABLE bonuses CASCADE CONSTRAINTS;CREATE TABLE bonuses ( job VARCHAR2(30) PRIMARY KEY, bonus_pct NUMBER);
-- jobごとにボーナス率を定義(単純なパーセント加算額)INSERT INTO bonuses VALUES ('Sales Rep', 500); -- Sales Rep に+500INSERT INTO bonuses VALUES ('HR Manager', 1000); -- HR Manager に+1000INSERT INTO bonuses VALUES ('Developer', 1500); -- Developer に+1500INSERT INTO bonuses VALUES ('Accountant', 800); -- Accountant に+800
-- summary_table 作成-- DROP TABLE summary_table CASCADE CONSTRAINTS;CREATE TABLE summary_table ( dept_id NUMBER, emp_count NUMBER);
COMMIT;
テーブルデータ
departments テーブル
DEPT_ID | DEPT_NAME |
---|---|
10 | Sales |
20 | HR |
30 | IT |
40 | Finance |
employees テーブル
EMP_ID | EMP_NAME | DEPT_ID | JOB | SALARY |
---|---|---|---|---|
1001 | Alice | 10 | Sales Rep | 4000 |
1002 | Bob | 10 | Sales Rep | 4200 |
1003 | Carol | 20 | HR Manager | 5000 |
1004 | Dave | 30 | Developer | 7000 |
1005 | Eve | 30 | Developer | 7200 |
1006 | Frank | 40 | Accountant | 9000 |
bonuses テーブル
JOB | BONUS_PCT |
---|---|
Sales Rep | 500 |
HR Manager | 1000 |
Developer | 1500 |
Accountant | 800 |
summary_table テーブル
空
SELECT句でスカラー副問合せを利用
-- SELECT句での例(スカラー副問合せ)SELECT e.emp_id, e.emp_name, (SELECT d.dept_name FROM departments d WHERE d.dept_id = e.dept_id) AS departmentFROM employees e;
実行結果
EMP_ID | EMP_NAME | DEPARTMENT |
---|---|---|
1001 | Alice | Sales |
1002 | Bob | Sales |
1003 | Carol | HR |
1004 | Dave | IT |
1005 | Eve | IT |
1006 | Frank | Finance |
この例では、(SELECT d.dept_name ...)
が各行ごとに実行され、対応する部門名が取得されます。副問合せが該当行を返さない場合はNULLになります。
WHERE句でスカラー副問合せを利用(比較条件)
-- 例: 社員の給与が全社平均より高い社員を取得SELECT *FROM employeesWHERE salary > (SELECT AVG(salary) FROM employees);
実行結果
EMP_ID | EMP_NAME | DEPT_ID | JOB | SALARY |
---|---|---|---|---|
1004 | Dave | 30 | Developer | 7000 |
1005 | Eve | 30 | Developer | 7200 |
1006 | Frank | 40 | Accountant | 9000 |
このケースの副問合せは非相関なので一度だけ評価されます。
UPDATEでスカラー副問合せを利用(更新値に副問合せを使う)
-- 例: ボーナス率テーブルからjobごとに加算するUPDATE employees eSET salary = salary + ( SELECT b.bonus_pct FROM bonuses b WHERE b.job = e.job)WHERE EXISTS ( SELECT 1 FROM bonuses b WHERE b.job = e.job);
UPDATE前(SELECT * FROM employees;
)
EMP_ID | EMP_NAME | DEPT_ID | JOB | SALARY |
---|---|---|---|---|
1001 | Alice | 10 | Sales Rep | 4000 |
1002 | Bob | 10 | Sales Rep | 4200 |
1003 | Carol | 20 | HR Manager | 5000 |
1004 | Dave | 30 | Developer | 7000 |
1005 | Eve | 30 | Developer | 7200 |
1006 | Frank | 40 | Accountant | 9000 |
UPDATE後(SELECT * FROM employees;
)
EMP_ID | EMP_NAME | DEPT_ID | JOB | SALARY |
---|---|---|---|---|
1001 | Alice | 10 | Sales Rep | 4500 |
1002 | Bob | 10 | Sales Rep | 4700 |
1003 | Carol | 20 | HR Manager | 6000 |
1004 | Dave | 30 | Developer | 8500 |
1005 | Eve | 30 | Developer | 8700 |
1006 | Frank | 40 | Accountant | 9800 |
注意: ( SELECT b.bonus_pct FROM bonuses b WHERE b.job = e.job )
が複数行を返さないことを保証する必要があります。
INSERTでスカラー副問合せを利用(挿入時に計算)
INSERT INTO summary_table (dept_id, emp_count)SELECT d.dept_id, (SELECT COUNT(*) FROM employees e WHERE e.dept_id = d.dept_id)FROM departments d;
INSERT後( SELECT * FROM summary_table;
)
DEPT_ID | EMP_COUNT |
---|---|
10 | 2 |
20 | 1 |
30 | 2 |
40 | 1 |
相関副問合せ・非相関副問合せの違いまとめ
- 相関副問合せ(correlated): 外側のクエリのカラムを参照する副問合せ。例:
WHERE b.job = e.job
のようにe
を参照する。外側の行ごとに評価される。パフォーマンスに注意。 - 非相関副問合せ(uncorrelated): 外側クエリに依存しないためDBが一度実行してキャッシュすることが多い。
実務では、外側の行ごとに実行される相関副問合せは JOIN や WITH (共通テーブル式) に書き換えられることが多いです。
よく遭遇するエラーと対処法(例:ORA-01427)
以下のSQLは、各部署ごとに「その部署の従業員の給与」をスカラー副問合せで取得しようとしています。 しかし、同じ部署に複数の従業員がいるため、副問合せが複数行を返しエラーとなります。
SELECT d.dept_id, (SELECT e.salary FROM employees e WHERE e.dept_id = d.dept_id) AS one_salaryFROM departments d;
エラー例
ORA-01427: 単一行副問合せにより2つ以上の行が戻されます
原因: スカラー副問合せが2行以上を返している。
対処法
解決策の例としては、副問合せに MAX や MIN を使って1行に絞ることです。
SELECT d.dept_id, (SELECT MAX(e.salary) FROM employees e WHERE e.dept_id = d.dept_id) AS max_salaryFROM departments d;
実行結果
DEPT_ID | MAX_SALARY |
---|---|
40 | 9800 |
30 | 8700 |
10 | 4700 |
20 | 6000 |
スカラー副問合せによるパフォーマンス上の注意点と改善手法
スカラー副問合せは便利だが、使い方次第でパフォーマンスを悪化させる。特に相関副問合せは外側の各行で実行されるため、次の点を確認する。
改善のためのポイント
-
非相関化できないか検討: 同じ値を多回計算しているなら一度だけ計算するようにWITH句(共通テーブル式)やマテリアライズを使う。
WITH avg_sal AS (SELECT AVG(salary) AS avg_salary FROM employees)SELECT e.*, (SELECT avg_salary FROM avg_sal) FROM employees e; -
JOINに書き換える: 部門名などを参照する場合はJOIN(LEFT JOIN/INNER JOIN)に置換するとオプティマイザがより効率的な計画を作ることが多い。
SELECT e.emp_id,e.emp_name,d.dept_nameFROM employees eLEFT JOIN departments d ON e.dept_id = d.dept_id; -
集約関数を使って1行にする: 副問合せの結果を集約して1行にする。
-
インデックスを活用: 副問合せ内で使われる列(join条件や検索条件)に適切なインデックスがあるか確認。
-
実行計画(EXPLAIN PLAN)を確認:
EXPLAIN PLAN FOR ...
やDBMS_XPLAN.DISPLAY
で実行計画をチェックして、繰り返し実行されているかを確認する。 -
バインド変数を使う: 繰り返し実行されるクエリではバインド変数を利用してライブラリキャッシュの有効活用を促す。
Oracle固有の注意点
- Oracleのオプティマイザは非相関副問合せを一度だけ評価する最適化を行うが、相関副問合せは基本的に行ごとに評価されるため注意が必要。
- 12c以降の機能(
FETCH FIRST
など)を活用すると副問合せ内で安全に1行だけ取り出せる。
スカラー副問合せまとめ
スカラー副問合せは、SQLを簡潔に書ける便利な表現ですが、使い方を誤ると ORA-01427
のようなエラーやパフォーマンス問題を招きます。以下を意識して使いましょう、
- 副問合せが必ず1行に収まることを確認しているか?(集約・制限を検討)
- 相関副問合せで行ごとに評価される影響を考慮したか?(行数×副問合せコスト)
- JOIN、WITH、または分析関数で書き換えられないか検討したか?
- 副問合せ内の検索列に適切なインデックスが張られているか?
- 実行計画を
EXPLAIN PLAN
/DBMS_XPLAN
で確認したか? - 副問合せが0行を返す時の取り扱い(NULL)を設計しているか?(
NVL
,COALESCE
など)
以上で本記事の解説を終わります。
よいITライフを!