【Oracle】ORA-01001の原因と解決方法|invalid cursor の直し方

【Oracle】ORA-01001の原因と解決方法|invalid cursor の直し方 Oracle

ORA-01001: invalid cursor は、Oracleで無効なカーソルを操作した時に発生するエラーです。PL/SQLでは、まだ OPEN していないカーソルを FETCH した、すでに CLOSE したカーソルを再度操作した、カーソル属性を不正な状態で参照した、といったケースで起きます。

Oracle公式のORA-01001説明では、無効なカーソル番号や存在しないカーソルが指定されたことが原因として説明されています。PL/SQL側では、OPENFETCHCLOSE の順序と、カーソルが開いているかを確認するのが基本です。

先に結論
ORA-01001が出たら、該当行で FETCHCLOSE%FOUND%NOTFOUND%ROWCOUNT を使う前に、対象カーソルが OPEN 済みかを確認します。開いているカーソルをもう一度OPENした場合は ORA-06511 側です。
スポンサーリンク

ORA-01001とは

ORA-01001は、カーソルが有効な状態ではないのに操作したことを示します。明示的カーソルやカーソル変数では、状態に応じて使える操作が変わります。

状態 操作 結果
未OPEN FETCH c INTO ... ORA-01001
OPEN済み FETCH c INTO ... 正常
CLOSE済み FETCH c INTO ... ORA-01001
OPEN済み OPEN c ORA-06511

Oracle公式のPL/SQLカーソル説明でも、明示的カーソルはOPEN、FETCH、CLOSEの流れで扱い、開いていないカーソルに対して一部の属性や操作を行うと INVALID_CURSOR 例外になります。

発生する典型例

次の例では、カーソルを OPEN せずに FETCH しています。カーソルが有効な実行状態にないため、ORA-01001になります。

ora01001-fetch-before-open.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  FETCH c_emp INTO l_id;
END;
/

-- ORA-01001: invalid cursor

明示的カーソルの基本は 明示的カーソル完全ガイド にもまとめています。

CLOSE後にFETCHするケース

カーソルを一度閉じた後に、同じカーソルからFETCHしようとしてもORA-01001になります。CLOSE後に再利用する場合は、再度 OPEN してからFETCHします。

ora01001-fetch-after-close.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;
  CLOSE c_emp;

  FETCH c_emp INTO l_id;
END;
/

-- CLOSE後なのでORA-01001

基本の直し方

カーソルを手動で扱う場合は、OPEN してから FETCH し、最後に CLOSE します。状態が分からない場合は %ISOPEN で確認します。

valid-open-fetch-close.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;

  LOOP
    FETCH c_emp INTO l_id;
    EXIT WHEN c_emp%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(l_id);
  END LOOP;

  CLOSE c_emp;
END;
/
操作 前提 注意点
OPEN カーソルが閉じている OPEN済みならORA-06511
FETCH カーソルが開いている 未OPEN/CLOSE済みならORA-01001
%NOTFOUND FETCH後に使う 未OPENではINVALID_CURSORになり得る
CLOSE カーソルが開いている 二重CLOSEを避けるなら%ISOPEN確認

操作別の最短修正

ORA-01001は、どの操作で落ちたかを見ると早く切り分けられます。エラー行が FETCH なのか、CLOSE なのか、属性参照なのかをまず確認します。

落ちた操作 よくある原因 最短修正
FETCH OPEN前、またはCLOSE後にFETCHしている FETCH前にOPENし、CLOSE後は再FETCHしない
CLOSE すでに閉じているカーソルを閉じている %ISOPEN確認後にCLOSE
%FOUND OPEN/FETCH前に参照している FETCH後に参照する
%NOTFOUND OPEN/FETCH前に参照している FETCH直後の終了判定に使う
%ROWCOUNT カーソルが有効でない OPEN後のFETCH回数として読む

症状別の最短修正

ORA-01001は、カーソルの状態と操作の順序を見ると切り分けやすいです。発生行の操作ごとに、次のように確認します。

症状 原因になりやすい箇所 最短修正
FETCHで失敗 OPEN前またはCLOSE後にFETCHしている OPEN済みか確認し、順序を直す
%FOUND%NOTFOUNDで失敗 カーソルが開いていない FETCH後に属性を参照する
CLOSEで失敗 すでに閉じているカーソルを閉じている %ISOPENを確認してからCLOSE
2回目の処理で失敗 例外時の状態管理が崩れている 例外ハンドラで状態を整理する
アプリ側だけで失敗 ResultSet/Cursorを閉じた後に読んでいる クローズ順序とトランザクション境界を確認

カーソル属性で起きる場合

%FOUND%NOTFOUND%ROWCOUNT は便利ですが、カーソルが正しい状態でないとエラーになります。%ISOPEN は状態確認に使いやすい属性です。

bad-cursor-attribute.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
BEGIN
  IF c_emp%NOTFOUND THEN
    DBMS_OUTPUT.PUT_LINE('no rows');
  END IF;
END;
/

-- OPENもFETCHもしていないため不正
good-cursor-attribute.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;
  FETCH c_emp INTO l_id;

  IF c_emp%NOTFOUND THEN
    DBMS_OUTPUT.PUT_LINE('no rows');
  END IF;

  CLOSE c_emp;
END;
/

ORA-06511との違い

ORA-01001とORA-06511はどちらもカーソル状態の問題ですが、逆方向のエラーです。ORA-06511は開いているカーソルをまた開いた時、ORA-01001は開いていない・無効なカーソルを操作した時に起きます。

エラー 状態 対処
ORA-06511 OPEN済みなのにOPEN OPEN c; OPEN c; ORA-06511を確認
ORA-01001 未OPEN/CLOSE済みなのにFETCHなど CLOSE c; FETCH c OPEN/FETCH/CLOSE順序を直す
ORA-01000 開いているカーソル数が多すぎる カーソル閉じ忘れが蓄積 ORA-01000を確認
ORA-01002 FETCH順序が不正 COMMIT後FETCHなど 次の記事候補として切り分け

カーソルFORループに置き換える

手動のOPEN/FETCH/CLOSEが不要なら、カーソルFORループに置き換えると状態管理のミスを減らせます。PL/SQLがOPEN、FETCH、CLOSEを管理するため、ORA-01001やORA-06511の原因を減らしやすいです。

cursor-for-loop-avoid-invalid.sql
BEGIN
  FOR r IN (
    SELECT employee_id
    FROM employees
  ) LOOP
    DBMS_OUTPUT.PUT_LINE(r.employee_id);
  END LOOP;
END;
/

カーソルFORループと明示的カーソルの使い分けは PL/SQLカーソル完全ガイド も参考になります。

REF CURSORやアプリ側で起きる場合

REF CURSORをアプリケーションへ返す処理では、DB側・アプリ側のどちらで閉じるかが曖昧だとORA-01001につながることがあります。Java、C#、Python、PHPなどでは、ResultSetやCursorを閉じた後に読み続けていないか、接続やトランザクションの終了でカーソルが無効になっていないかを確認します。

確認項目 理由
ResultSet/Cursorを閉じた後に読んでいないか 閉じたカーソルは無効になる
接続を閉じた後に読み続けていないか 接続終了でカーソルも使えなくなる
COMMIT/ROLLBACKのタイミング カーソル状態に影響する処理がある
例外時のfinally/defer処理 早すぎるcloseで後続読み取りが失敗する

REF CURSORを返す側・読む側の責任分界

REF CURSORを返す設計では、DB側はカーソルを開いて返し、呼び出し側は読み終わってから閉じる、という責任分界を明確にします。返す前に閉じたり、呼び出し側が読む前に接続を閉じたりすると、ORA-01001につながりやすくなります。

return-ref-cursor.sql
CREATE OR REPLACE PROCEDURE p_get_emps(p_cur OUT SYS_REFCURSOR) IS
BEGIN
  OPEN p_cur FOR
    SELECT employee_id, last_name
    FROM employees;
END;
/

-- 呼び出し側が読み終わるまで、接続やカーソルを閉じるタイミングに注意する
担当 やること 注意点
DB側 REF CURSORをOPENして返す 返す前に閉じない
アプリ側 最後まで読み取る 読み取り前にcloseしない
アプリ側 読み終わったらcloseする 閉じ忘れはORA-01000の原因にもなる
共通 例外時の後始末を決める 早すぎるcloseと閉じ忘れの両方を避ける

例外処理で順序が崩れる場合

例外ハンドラでカーソルを閉じた後、外側の処理が同じカーソルをFETCHし続けるとORA-01001になり得ます。例外時は状態を整理し、必要なら処理を中断します。

safe-close-on-exception.sql
DECLARE
  CURSOR c_emp IS
    SELECT employee_id
    FROM employees;
  l_id employees.employee_id%TYPE;
BEGIN
  OPEN c_emp;
  FETCH c_emp INTO l_id;

  CLOSE c_emp;
EXCEPTION
  WHEN OTHERS THEN
    IF c_emp%ISOPEN THEN
      CLOSE c_emp;
    END IF;
    RAISE;
END;
/

例外処理の設計は PL/SQL例外処理ガイド も参考になります。

調査手順

PL/SQL内でORA-01001が出た場合は、ORA-06512の行番号から USER_SOURCE で該当行を確認します。その行がFETCHなのか、CLOSEなのか、カーソル属性参照なのかを見て、直前のOPEN/CLOSE順序を追います。

check-user-source-ora01001.sql
SELECT line,
       text
FROM user_source
WHERE name = 'PKG_EMP'
  AND type = 'PACKAGE BODY'
  AND line BETWEEN 60 AND 90
ORDER BY line;

発生行の読み方は ORA-06512の原因と読み方、バックトレースのログは DBMS_UTILITY完全ガイド が参考になります。

よくある原因と対処一覧

原因 症状 対処
OPEN前にFETCH 最初のFETCHでORA-01001 OPENしてからFETCH
CLOSE後にFETCH CLOSE後の処理で失敗 再OPENするか処理を終了
属性参照の順序ミス %NOTFOUNDなどで失敗 FETCH後に参照
アプリ側で先にclose ResultSet/Cursor読み取り時に失敗 close順序を見直す
手動管理が複雑 正常系・例外系で状態がずれる カーソルFORループへ置換

チェックリスト

項目 OKの状態
FETCH前にOPENした 未OPENカーソルを読んでいない
CLOSE後にFETCHしていない 閉じたカーソルを再利用していない
属性参照の順序が正しい FETCH後に%FOUND/%NOTFOUNDを見ている
例外時のclose順序を確認した 後続処理が閉じたカーソルを触らない
ORA-06512の行を確認した 失敗した操作が分かっている

よくある質問

ORA-01001とORA-06511は何が違いますか?

ORA-01001は無効なカーソルを操作した時、ORA-06511はOPEN済みカーソルを再度OPENした時です。

%ISOPENを見れば防げますか?

状態確認には有効です。ただし、FETCHや属性参照の順序が間違っている場合は、OPEN/FETCH/CLOSEの流れ自体を直します。

カーソルFORループなら安全ですか?

手動OPEN/FETCH/CLOSEが不要になるため、状態管理ミスは減らせます。単純な全件処理では有力な選択肢です。

アプリ側でだけORA-01001になります

ResultSet/Cursor、接続、トランザクションを閉じるタイミングを確認してください。DBから返したカーソルを読み終える前に閉じている可能性があります。

まとめ

ORA-01001は、無効なカーソルや閉じたカーソルを操作した時に発生します。OPEN前のFETCH、CLOSE後のFETCH、カーソル属性参照の順序ミス、アプリ側で閉じた後の読み取りが主な原因です。

対処は、OPEN/FETCH/CLOSEの順序を整理し、必要なら %ISOPEN で状態を確認することです。単純な処理ならカーソルFORループへ置き換えると、カーソル状態の管理ミスを減らせます。

参考

ORA-01001 – Oracle Database Error Help

Cursors Overview – Oracle Database PL/SQL Language Reference

Cursor Attributes – Oracle PL/SQL User's Guide and Reference