ORA-01403: no data foundは、PL/SQL の SELECT INTO 文が0件の結果を返した場合に発生します。SQL*Plus から直接 SELECT を実行したときには発生しませんが、PL/SQL のコード内で SELECT INTO を使ったときだけ発生します。
ORA-01403 が発生した場合、PL/SQL では NO_DATA_FOUND 例外として扱われます。対応していない場合はブロック全体がエラーになりますが、EXCEPTION 句で適切に捕捉することで安全に処理できます。
この記事でわかること
- ORA-01403 が発生する状況(SELECT INTO で0件の場合のみ)
- NO_DATA_FOUND と TOO_MANY_ROWS の違い
- EXCEPTION ブロックで NO_DATA_FOUND を捕捉する方法
- 0件の場合にデフォルト値を返す実装パターン
- CURSOR を使って0件を安全に処理する方法
- BULK COLLECT で NO_DATA_FOUND が発生しない理由
ORA-01403 が発生するパターン
NO_DATA_FOUND が発生するパターンと発生しないパターン
-- NG: SELECT INTO で0件の場合 → NO_DATA_FOUND(ORA-01403)が発生する
DECLARE
v_salary employees.salary%TYPE;
BEGIN
-- 存在しない employee_id を指定した場合に0件になり例外が発生する
SELECT salary INTO v_salary
FROM employees
WHERE employee_id = 9999; -- 存在しない ID
DBMS_OUTPUT.PUT_LINE('給与: ' || v_salary);
-- → ORA-01403: no data found で以降の処理は実行されない
END;
/
-- NG: TOO_MANY_ROWS も発生する(SELECT INTO で2件以上返った場合)
DECLARE
v_salary employees.salary%TYPE;
BEGIN
SELECT salary INTO v_salary
FROM employees
WHERE department_id = 60; -- 複数件ある部署の場合 → ORA-01422: exact fetch returns more than requested number of rows
-- PL/SQL では ORA-01422 を TOO_MANY_ROWS 例外として扱う
END;
/
-- OK: SQL*Plus での SELECT は0件でもエラーにならない(PL/SQL 外では発生しない)
SELECT salary FROM employees WHERE employee_id = 9999;
-- → "no rows selected"(エラーではない)
NO_DATA_FOUND を EXCEPTION 句で処理する
NO_DATA_FOUND と TOO_MANY_ROWS を捕捉する
-- 基本的な EXCEPTION 処理
DECLARE
v_salary employees.salary%TYPE;
BEGIN
SELECT salary INTO v_salary
FROM employees
WHERE employee_id = 9999;
DBMS_OUTPUT.PUT_LINE('給与: ' || v_salary);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('該当する社員が見つかりません');
v_salary := 0; -- デフォルト値を設定する
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('複数件ヒットしました。条件を絞り込んでください');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('予期しないエラー: ' || SQLERRM);
RAISE; -- ハンドルできないエラーは再送出する
END;
/
-- 関数で NO_DATA_FOUND を処理してデフォルト値を返す
CREATE OR REPLACE FUNCTION get_salary(p_emp_id NUMBER) RETURN NUMBER IS
v_salary employees.salary%TYPE;
BEGIN
SELECT salary INTO v_salary
FROM employees
WHERE employee_id = p_emp_id;
RETURN v_salary;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL; -- 見つからない場合は NULL を返す(呼び出し元で判断する)
WHEN TOO_MANY_ROWS THEN
RAISE_APPLICATION_ERROR(-20001, '複数件ヒット: emp_id=' || p_emp_id);
END;
/
0件を安全に処理するパターン
0件の場合にデフォルト値を返す実装パターン
-- パターン①: EXCEPTION を使う(シンプルで最も一般的)
DECLARE
v_name employees.last_name%TYPE := '(未登録)'; -- デフォルト値を初期値として設定
BEGIN
SELECT last_name INTO v_name
FROM employees WHERE employee_id = 9999;
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL; -- 初期値のまま続行
END;
/
-- パターン②: MAX() を使って0件でも NULL を返す(例外が発生しない)
-- ※ 一意の値を取得するとき以外は集計関数を使う方法もある
DECLARE
v_salary employees.salary%TYPE;
BEGIN
SELECT MAX(salary) INTO v_salary
FROM employees WHERE employee_id = 9999;
-- 0件でも NULL が返る(NO_DATA_FOUND は発生しない)
IF v_salary IS NULL THEN
DBMS_OUTPUT.PUT_LINE('該当なし');
ELSE
DBMS_OUTPUT.PUT_LINE('給与: ' || v_salary);
END IF;
END;
/
-- パターン③: COUNT を先に確認する(推奨しない: ファントムリード問題がある)
-- COUNT してから SELECT するとその間に行が削除される可能性がある
-- → EXCEPTION を使う方が安全
-- パターン④: カーソルを使う(0件でも安全・複数件も処理可能)
DECLARE
CURSOR c IS SELECT salary FROM employees WHERE employee_id = 9999;
v_salary employees.salary%TYPE;
BEGIN
OPEN c;
FETCH c INTO v_salary;
IF c%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('該当なし');
ELSE
DBMS_OUTPUT.PUT_LINE('給与: ' || v_salary);
END IF;
CLOSE c;
-- カーソルは0件でも NO_DATA_FOUND が発生しない
END;
/
-- パターン⑤: FOR ループカーソル(0件でもループが0回実行されるだけ)
BEGIN
FOR rec IN (SELECT salary FROM employees WHERE employee_id = 9999) LOOP
DBMS_OUTPUT.PUT_LINE('給与: ' || rec.salary);
END LOOP;
-- 0件の場合はループが実行されない(例外なし)
END;
/
BULK COLLECT では NO_DATA_FOUND が発生しない
BULK COLLECT は0件でもコレクションが空になるだけ(例外なし)
-- BULK COLLECT: 0件でもコレクションが空になるだけ(NO_DATA_FOUND は発生しない)
DECLARE
TYPE salary_t IS TABLE OF employees.salary%TYPE;
v_salaries salary_t;
BEGIN
SELECT salary BULK COLLECT INTO v_salaries
FROM employees WHERE department_id = 9999; -- 存在しない部署
-- 0件でも例外は発生しない → v_salaries.COUNT = 0
IF v_salaries.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('該当なし');
ELSE
FOR i IN 1 .. v_salaries.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(v_salaries(i));
END LOOP;
END IF;
END;
/
-- SELECT INTO との違いのまとめ:
-- SELECT INTO → 0件: NO_DATA_FOUND / 2件以上: TOO_MANY_ROWS
-- BULK COLLECT INTO → 0件: コレクションが空(例外なし)/ 複数件: すべてコレクションに格納
-- カーソル FETCH → 0件: %NOTFOUND = TRUE(例外なし)
SELECT INTO 以外での NO_DATA_FOUND
その他の NO_DATA_FOUND 発生箇所
-- Associative Array(連想配列)の存在しないキーへのアクセス
DECLARE
TYPE map_t IS TABLE OF NUMBER INDEX BY VARCHAR2(50);
v_map map_t;
BEGIN
v_map('a') := 100;
-- 存在しないキーにアクセスすると NO_DATA_FOUND が発生する
DBMS_OUTPUT.PUT_LINE(v_map('z')); -- ORA-01403: NO_DATA_FOUND
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('キー z は存在しません');
END;
/
-- 事前確認: EXISTS() で存在チェックしてからアクセスする
BEGIN
IF v_map.EXISTS('z') THEN
DBMS_OUTPUT.PUT_LINE(v_map('z'));
ELSE
DBMS_OUTPUT.PUT_LINE('キー z は存在しません');
END IF;
END;
/
-- DBMS_OUTPUT.GET_LINE: バッファが空の場合に NO_DATA_FOUND が発生する
-- UTL_FILE.GET_LINE: ファイルの末尾に達した場合に NO_DATA_FOUND が発生する
-- → これらは特定の終了条件を示すための正常な使い方
-- WHEN OTHERS で隠れてしまうケース(注意が必要)
BEGIN
-- もし WHEN OTHERS が先にある場合、NO_DATA_FOUND もここで処理される
NULL;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('エラー: ' || SQLCODE || ' ' || SQLERRM);
-- SQLCODE = 100(ORA-01403 の SQLCODE は 100)
-- SQLERRM = 'ORA-01403: no data found'
END;
/
まとめ
- ORA-01403 の発生条件:PL/SQL の SELECT INTO で0件が返ったとき。SQL*Plus で直接 SELECT しても発生しない。PL/SQL 例外名は NO_DATA_FOUND
- TOO_MANY_ROWS との違い:NO_DATA_FOUND は0件、TOO_MANY_ROWS は2件以上のとき。どちらも EXCEPTION 句で捕捉する
- 推奨する対処法:EXCEPTION WHEN NO_DATA_FOUND THEN を使う。変数に初期値を設定しておき、例外発生時は NULL 継続が最もシンプル。COUNT() や MAX() で回避することもできるが、ファントムリードのリスクがある
- カーソル / BULK COLLECT では発生しない:カーソル FETCH は %NOTFOUND で確認する。BULK COLLECT は0件でもコレクションが空になるだけで例外は発生しない
- 連想配列のキーアクセス:Associative Array の存在しないキーへのアクセスも NO_DATA_FOUND を発生させる。EXISTS() で確認してからアクセスする
SELECT INTO のその他のエラー(ORA-06502: PL/SQL: numeric or value error など)については Oracle SELECT INTO エラー完全ガイドを参照してください。PL/SQL の例外処理全般については Oracle 例外処理完全ガイドも参照してください。