【Oracle】ORA-01403 完全ガイド|NO DATA FOUND の発生パターン・PL/SQL 例外処理・COUNT を使わない安全な SELECT INTO まで解説

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 例外処理完全ガイドも参照してください。