【完全ガイド】PLS-00341: declaration of cursor is incomplete or malformed の原因と解決方法|PLS-00320 カスケード・自己参照 RETURN 型 徹底解説

【完全ガイド】PLS-00341: declaration of cursor is incomplete or malformed の原因と解決方法|PLS-00320 カスケード・自己参照 RETURN 型 徹底解説

Oracle PL/SQL 開発者がカーソル宣言時に遭遇するコンパイルエラー:

DECLARE
  CURSOR c1 IS SELECT id, name FROM non_existent_table;
BEGIN
  NULL;
END;
/
*
ERROR at line 2:
ORA-06550: line 2, column 32:
PLS-00201: identifier 'NON_EXISTENT_TABLE' must be declared
ORA-06550: line 2, column 3:
PL/SQL: Item ignored
ORA-06550: line 3, column 3:
PLS-00341: declaration of cursor 'C1' is incomplete or malformed

**「カーソル宣言が不完全または不正」**というシンプルなメッセージ。多くの場合、他のエラーの結果として発生するカスケードエラーです:

  • PLS-00201(識別子未宣言、SELECT 内のテーブル/列)
  • PLS-00341(そのため CURSOR が不完全) ← 本記事
  • PLS-00320(そのため型宣言が不完全)
  • PLS-00597(INTO の型不一致、併発)

このエラーの本質は、CURSOR 宣言の SELECT 文中で参照している識別子が未定義、またはRETURN 型の指定が不正:

CURSOR 宣言の仕組み:
CURSOR c IS SELECT ... FROM ...;
              ↑
        SELECT が有効である必要
        → 参照先のテーブル/列が存在必須
        → 権限も必要
        → 未存在なら PLS-00341
目次

エラーカスケードの構造

通常、複数エラーがセットで発生:

PLS-00201: identifier 'X' must be declared     ← 根本原因
PLS-00341: declaration of cursor 'C' is incomplete  ← カスケード
PLS-00320: declaration of the type ...          ← さらにカスケード
PLS-00597: Expression in the INTO list is wrong type ← INTO でも失敗

**「PLS-00341 だけ見て対処しても解決しない」**ケースが多発します。

現場で最も典型的なパターン:

  • CURSOR 内 SELECT で未存在テーブル参照
  • CURSOR 内 SELECT で未存在列参照
  • スペルミス
  • スキーマ違いOTHER_SCHEMA.TABLE
  • 自己参照的な RETURN 型(Oracle 公式が illegal と明記)
  • INVALID なテーブル/ビュー
  • 権限なし(SELECT 権限不足)
  • 大文字小文字(引用符付き識別子)
  • REF CURSOR 型不整合
  • ブロック構造での宣言位置ミス

さらに、Oracle 公式が明示する典型的な illegal パターン:

-- 自己参照は不可(Oracle 公式が明記)
CURSOR c1 RETURN c1%ROWTYPE IS SELECT ...;
--       ↑            ↑
-- c1 の定義に      c1 自身を参照
-- → PLS-00341

-- 正しくは(RETURN 省略、暗黙型)
CURSOR c1 IS SELECT ...;

多くの日本語記事が「カーソルの SELECT を確認せよ」で終わりますが、実務では:

  • PLS-00320/PLS-00201 との厳密な関係
  • エラースタックの正しい読み方(最初のエラーが根本原因)
  • RETURN c%ROWTYPE自己参照禁止ルール
  • REF CURSOR弱型 vs 強型
  • SYS_REFCURSOR の活用
  • ALL_TAB_COLUMNS による事前検証
  • PL/SQL ブロック構造での宣言位置
  • INVALID オブジェクトの連鎖影響
  • 権限とシノニムの相互作用
  • Rails / Java / Python からの解析

さらに、カーソル宣言時の権限は要注意ポイント:

-- 実行権限 ≠ SELECT 権限
GRANT SELECT ON hr.emp TO scott;   -- ✅ SELECT 権限
-- しかし PL/SQL コンパイル時は「ロール経由不可」の罠あり

本記事では、PLS-00341: declaration of cursor is incomplete or malformed完全な原因と解決方法を、リファレンスとして実用的に整理します。カスケードエラー、10大発生パターン、6つの解決策、REF CURSOR、Rails/Java/Python 対応、実践シナリオ、FAQまで完全網羅。この1本で PLS-00341 を根本から解決できるようになります。


結論:CURSOR 内 SELECT を検証

時間がない方向けに、最速の対処を先に示します。

エラーメッセージの読み方

PLS-00341: declaration of cursor 'CURSOR_NAME' is incomplete or malformed
                                   ↑
                            不完全なカーソル

= CURSOR 宣言に問題がある
  多くの場合、SELECT 内の識別子未定義が原因

エラースタックの読み方

上から順に確認最初の PLS-00341 以外のエラーが根本原因:

PLS-00201: identifier 'X' must be declared     ← 根本原因!
PLS-00341: declaration of cursor 'C' is incomplete  ← カスケード
PLS-00320: declaration of the type ...          ← カスケード

最速の診断

-- ① CURSOR 内 SELECT を単独実行
SELECT id, name FROM my_table WHERE ...;
-- 実行できないなら SELECT の問題

-- ② テーブル/列存在確認
SELECT column_name FROM user_tab_columns 
WHERE table_name = 'MY_TABLE';

-- ③ シノニム確認
SELECT * FROM all_synonyms 
WHERE synonym_name = 'MY_TABLE';

6つの解決策

#手法使う場面
SELECT 単独検証基本
スキーマプレフィックス別スキーマ
シノニム作成権限管理
弱型 SYS_REFCURSORREF CURSOR
宣言位置修正ブロック構造
INVALID 再コンパイル依存修復

併発する PLS-* エラー

エラー意味
PLS-00341カーソル宣言不完全
PLS-00320型宣言不完全(カスケード)
PLS-00201識別子未宣言(根本原因)
PLS-00597INTO 型不一致

詳細は以下で解説します。


Oracle CURSOR の宣言構造

明示的 CURSOR

DECLARE
  CURSOR c IS SELECT id, name FROM emp;
  r c%ROWTYPE;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO r;
    EXIT WHEN c%NOTFOUND;
  END LOOP;
  CLOSE c;
END;
/

RETURN 型付き CURSOR

DECLARE
  CURSOR c RETURN emp%ROWTYPE IS SELECT * FROM emp;
BEGIN
  ...
END;

RETURN 型: カーソルが返す型を明示。

REF CURSOR(動的カーソル)

DECLARE
  -- 強型
  TYPE emp_cursor IS REF CURSOR RETURN emp%ROWTYPE;
  rc1 emp_cursor;
  
  -- 弱型(推奨)
  rc2 SYS_REFCURSOR;
BEGIN
  OPEN rc2 FOR SELECT * FROM emp;
END;
/

エラー発生の位置

CURSOR 宣言の SELECT が有効でなければ
→ CURSOR 自体が不完全
→ PLS-00341
→ %ROWTYPE も不完全
→ PLS-00320

【原因①】CURSOR 内 SELECT で未存在テーブル(最頻出)

症状

DECLARE
  CURSOR c IS SELECT id, name FROM non_existent_table;
BEGIN
  NULL;
END;
/
-- PLS-00201: identifier 'NON_EXISTENT_TABLE' must be declared
-- PLS-00341: declaration of cursor 'C' is incomplete

根本原因

PLS-00201(テーブル未存在)。

解決

テーブル名確認・修正:

DECLARE
  CURSOR c IS SELECT id, name FROM existing_table;
BEGIN
  NULL;
END;
/

未存在参照は PLS-00201: identifier must be declared の記事も参照してください。


【原因②】CURSOR 内 SELECT で未存在列

症状

DECLARE
  CURSOR c IS SELECT id, name, non_existent_col FROM emp;
BEGIN
  NULL;
END;
/
-- PLS-00201: identifier 'NON_EXISTENT_COL' must be declared
-- PLS-00341: declaration of cursor 'C' is incomplete

診断

SELECT column_name FROM user_tab_columns 
WHERE table_name = 'EMP';
-- 実存の列を確認

解決

列名修正:

DECLARE
  CURSOR c IS SELECT id, name FROM emp;
BEGIN
  NULL;
END;
/

【原因③】スペルミス

症状

DECLARE
  CURSOR c IS SELECT id FROM empployees;   -- タイポ (employees)
BEGIN
  NULL;
END;
/
-- PLS-00201 + PLS-00341

解決

タイポ修正、IDE オートコンプリート活用。


【原因④】スキーマ違い

シナリオ

-- SCOTT ユーザーで
DECLARE
  CURSOR c IS SELECT * FROM hr.employees;   -- HR スキーマ
BEGIN
  NULL;
END;
/
-- SCOTT に hr.employees の SELECT 権限なし
-- → PLS-00201 + PLS-00341

解決A: 権限確認 + シノニム

-- HR ユーザーで
GRANT SELECT ON hr.employees TO scott;

-- SCOTT で
CREATE SYNONYM employees FOR hr.employees;

-- PL/SQL 再コンパイル

解決B: プレフィックス使用

DECLARE
  CURSOR c IS SELECT * FROM hr.employees;   -- 明示
BEGIN
  NULL;
END;
/

注意: PL/SQL ではロール経由の権限は無効、直接権限必要。


【原因⑤】自己参照的な RETURN 型(Oracle 公式が明記)

症状(超典型)

DECLARE
  CURSOR c1 RETURN c1%ROWTYPE IS SELECT * FROM emp;
--            ↑            ↑
--        c1 の定義に  c1 自身を参照
BEGIN
  NULL;
END;
/
-- PLS-00341

Oracle 公式ドキュメントillegal と明記。

解決A: RETURN 省略(推奨)

DECLARE
  CURSOR c1 IS SELECT * FROM emp;   -- 暗黙的な型
BEGIN
  NULL;
END;
/

解決B: 別の型参照

DECLARE
  CURSOR c1 RETURN emp%ROWTYPE IS SELECT * FROM emp;   -- テーブル型
BEGIN
  NULL;
END;
/

【原因⑥】INVALID なテーブル/ビュー

シナリオ

-- ビューが INVALID
SELECT status FROM user_objects WHERE object_name = 'EMP_VIEW';
-- INVALID

DECLARE
  CURSOR c IS SELECT * FROM emp_view;
BEGIN
  NULL;
END;
/
-- PLS-00201 + PLS-00341

解決

ALTER VIEW emp_view COMPILE;

-- または一括
BEGIN
  UTL_RECOMP.recomp_parallel(4);
END;
/

INVALID 関連は PLS-00905: object is invalid の記事も参照してください。


【原因⑦】権限なし(SELECT 権限不足)

シナリオ

-- SCOTT ユーザーで
DECLARE
  CURSOR c IS SELECT * FROM hr.employees;
BEGIN
  NULL;
END;
/
-- SCOTT に SELECT 権限なし
-- → PLS-00201 + PLS-00341

解決

-- HR で
GRANT SELECT ON hr.employees TO scott;

-- または PL/SQL では直接権限必要

権限系は PLS-00201: identifier must be declared の記事、ORA-01031: insufficient privileges の記事も参照してください。


【原因⑧】大文字小文字(引用符付き識別子)

シナリオ

-- Rails マイグレーションで
CREATE TABLE "MyTable" ("myCol" NUMBER);
-- 引用符付き = case-sensitive

DECLARE
  CURSOR c IS SELECT mycol FROM mytable;   -- 実は MYCOL / MYTABLE
BEGIN
  NULL;
END;
/
-- PLS-00201 + PLS-00341

解決

A. 引用符付きで参照:

CURSOR c IS SELECT "myCol" FROM "MyTable";

B. リネーム(推奨):

ALTER TABLE "MyTable" RENAME TO my_table;

【原因⑨】REF CURSOR 型不整合

シナリオ

DECLARE
  TYPE strong_cur IS REF CURSOR RETURN emp%ROWTYPE;
  rc strong_cur;
BEGIN
  OPEN rc FOR SELECT id, name FROM emp;
  -- 強型は完全一致必要
  -- SELECT の列と emp%ROWTYPE が不一致
  -- → PLS-00341
END;
/

解決

弱型 SYS_REFCURSOR 使用(推奨):

DECLARE
  rc SYS_REFCURSOR;
BEGIN
  OPEN rc FOR SELECT id, name FROM emp;
END;
/

REF CURSOR 関連は PLS-00306: wrong number or types of arguments の記事も参照してください。


【原因⑩】ブロック構造での宣言位置

シナリオ

DECLARE
  r c%ROWTYPE;   -- ← c がまだ宣言されていない
  CURSOR c IS SELECT * FROM emp;
BEGIN
  NULL;
END;
/
-- PLS-00341(前方参照)

解決

宣言順序を正しく:

DECLARE
  CURSOR c IS SELECT * FROM emp;   -- 先に宣言
  r c%ROWTYPE;                     -- 参照は後
BEGIN
  NULL;
END;
/

診断ツール完全リファレンス

SHOW ERRORS

CREATE OR REPLACE PROCEDURE p IS ...;
SHOW ERRORS PROCEDURE p;

USER_ERRORS

SELECT line, position, text
FROM user_errors
WHERE name = 'MY_PROC'
ORDER BY sequence;

CURSOR 内 SELECT 検証

-- CURSOR 内 SELECT を単独で実行
SELECT id, name FROM my_table WHERE ...;

-- 実行できるかで問題箇所判定

ALL_TAB_COLUMNS

-- 列存在確認
SELECT column_name, data_type
FROM all_tab_columns
WHERE table_name = 'EMP'
  AND owner = 'HR'
ORDER BY column_id;

ALL_TABLES / ALL_VIEWS

-- テーブル/ビュー存在確認
SELECT owner, table_name FROM all_tables 
WHERE table_name = 'EMP';

SELECT owner, view_name FROM all_views 
WHERE view_name = 'EMP_VIEW';

エラースタック解析

def find_root_cause(error_message):
    lines = error_message.split('\n')
    for line in lines:
        # ORA-06550 は位置情報のみ
        if 'ORA-06550' in line:
            continue
        # PLS-00341 は多くの場合カスケード
        if 'PLS-00341' in line:
            continue
        # PLS-00320 もカスケード
        if 'PLS-00320' in line:
            continue
        # それ以外の PLS-*/ORA-* が根本原因
        if 'PLS-' in line or 'ORA-' in line:
            return line
    return None

6つの解決策 完全リファレンス

解決策① SELECT 単独検証

-- カーソルの SELECT 文だけを実行
SELECT id, name FROM my_table WHERE ...;

-- 実行成功なら CURSOR の問題(構文/宣言位置)
-- 実行失敗ならまず SELECT を修正

解決策② スキーマプレフィックス

CURSOR c IS SELECT * FROM hr.employees;   -- 明示

解決策③ シノニム作成

CREATE SYNONYM employees FOR hr.employees;
-- または
CREATE PUBLIC SYNONYM employees FOR hr.employees;

解決策④ 弱型 SYS_REFCURSOR

DECLARE
  rc SYS_REFCURSOR;   -- 弱型(推奨)
BEGIN
  OPEN rc FOR SELECT id, name FROM emp;
END;

解決策⑤ 宣言位置修正

DECLARE
  CURSOR c IS SELECT ...;   -- 先に
  r c%ROWTYPE;              -- 後で
BEGIN
  ...
END;

解決策⑥ INVALID 再コンパイル

ALTER VIEW emp_view COMPILE;
ALTER PACKAGE my_pkg COMPILE;

-- 一括
BEGIN
  UTL_RECOMP.recomp_parallel(4);
END;
/

Rails / Java / Python 対応

Rails ActiveRecord

エラースタック解析:

begin
  ActiveRecord::Base.connection.execute("BEGIN my_proc; END;")
rescue ActiveRecord::StatementInvalid => e
  if e.message.include?("PLS-00341")
    # 根本原因抽出(最初の非-06550 非-00341 エラー)
    root = e.message.split("\n").find do |l|
      l.match(/(PLS-|ORA-)/) && 
        !l.match(/ORA-06550/) && 
        !l.match(/PLS-00341/) &&
        !l.match(/PLS-00320/)
    end
    Rails.logger.error "根本原因: #{root}"
  end
end

Rails 8 系の詳細は Rails 8 アップグレードガイドの記事も参照してください。

Java (JDBC)

try {
    stmt.execute("BEGIN my_proc; END;");
} catch (SQLException e) {
    if (e.getMessage().contains("PLS-00341")) {
        // 根本原因抽出
        for (String line : e.getMessage().split("\n")) {
            if (line.matches(".*(PLS-|ORA-)\\d+.*") 
                && !line.contains("ORA-06550")
                && !line.contains("PLS-00341")
                && !line.contains("PLS-00320")) {
                logger.error("根本原因: " + line);
                break;
            }
        }
    }
}

Python (oracledb)

import oracledb
import re

def find_cursor_error_root(message):
    for line in message.split('\n'):
        if any(skip in line for skip in ['ORA-06550', 'PLS-00341', 'PLS-00320']):
            continue
        if re.search(r'(PLS-|ORA-)\d+', line):
            return line.strip()
    return None

try:
    cursor.execute("BEGIN my_proc; END;")
except oracledb.DatabaseError as e:
    error_obj, = e.args
    if "PLS-00341" in error_obj.message:
        root = find_cursor_error_root(error_obj.message)
        print(f"根本原因: {root}")

実践シナリオ

シナリオ1:本番エラーの緊急診断

本番エラー:
ORA-06550: line 5, column 20:
PLS-00201: identifier 'CUSTOMER_MASTER' must be declared
ORA-06550: line 5, column 3:
PL/SQL: Item ignored
ORA-06550: line 6, column 3:
PLS-00341: declaration of cursor 'CUR_CUST' is incomplete

診断:
1. 根本原因: PLS-00201(CUSTOMER_MASTER 未存在)
2. カスケード: PLS-00341(cursor 不完全)
3. 対処: シノニム作成 or フルパス使用

シナリオ2:新規カーソル開発時の検証

-- 1. まず SELECT を単独実行
SQL> SELECT student_id, subject_id, subject_title
     FROM regist.enrol e, regist.subject s
     WHERE e.subject_id = s.subject_id;
-- OK

-- 2. その後 CURSOR 化
DECLARE
  CURSOR subj_cur IS 
    SELECT student_id, subject_id, subject_title
    FROM regist.enrol e, regist.subject s
    WHERE e.subject_id = s.subject_id;
BEGIN
  NULL;
END;
/
-- OK

シナリオ3:スキーマ間 CURSOR

-- HR で権限付与
GRANT SELECT ON hr.employees TO scott;
GRANT SELECT ON hr.departments TO scott;

-- SCOTT でシノニム作成
CREATE SYNONYM employees FOR hr.employees;
CREATE SYNONYM departments FOR hr.departments;

-- SCOTT で CURSOR
CREATE PROCEDURE list_emps IS
  CURSOR c IS 
    SELECT e.id, e.name, d.name AS dept_name
    FROM employees e JOIN departments d 
      ON e.dept_id = d.id;
BEGIN
  FOR r IN c LOOP
    DBMS_OUTPUT.PUT_LINE(r.name || ' - ' || r.dept_name);
  END LOOP;
END;
/

シナリオ4:REF CURSOR 設計

-- パッケージ定義
CREATE OR REPLACE PACKAGE data_pkg IS
  -- 弱型(推奨、柔軟)
  PROCEDURE get_data(rc OUT SYS_REFCURSOR);
END;
/

CREATE OR REPLACE PACKAGE BODY data_pkg IS
  PROCEDURE get_data(rc OUT SYS_REFCURSOR) IS
  BEGIN
    OPEN rc FOR SELECT id, name FROM emp;
  END;
END;
/

-- 呼び出し
DECLARE
  rc SYS_REFCURSOR;
  v_id NUMBER;
  v_name VARCHAR2(100);
BEGIN
  data_pkg.get_data(rc);
  LOOP
    FETCH rc INTO v_id, v_name;
    EXIT WHEN rc%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(v_id || ' - ' || v_name);
  END LOOP;
  CLOSE rc;
END;
/

シナリオ5:Docker Oracle でのテスト

docker exec -it oracle-xe sqlplus scott/tiger <<EOF
-- ❌ 失敗パターン
DECLARE
  CURSOR c IS SELECT * FROM non_existent_table;
BEGIN
  NULL;
END;
/
-- PLS-00201 + PLS-00341

-- ✅ 成功パターン
CREATE TABLE test_t (id NUMBER);

DECLARE
  CURSOR c IS SELECT * FROM test_t;
BEGIN
  NULL;
END;
/
-- OK
EOF

Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。

シナリオ6:Rails での CURSOR ストアド

# ストアドプロシージャで REF CURSOR
class UserRepo
  def self.fetch_all
    ActiveRecord::Base.connection.exec_query(<<-SQL)
      SELECT id, name FROM users
    SQL
  end
end

# エラーハンドリング
begin
  UserRepo.fetch_all
rescue ActiveRecord::StatementInvalid => e
  if e.message.include?("PLS-00341")
    Rails.logger.error "Cursor declaration issue"
  end
end

Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、find/find_by/where の記事も参照してください。

シナリオ7:CI/CD 検証

- name: Compile PL/SQL
  run: |
    sqlplus -s $DB_USER/$DB_PW <<EOF
    WHENEVER SQLERROR EXIT SQL.SQLCODE
    @compile_all.sql
    SELECT COUNT(*) FROM user_errors;
    EOF

Kamal 2 デプロイの詳細は Kamal 2 デプロイの記事を参照してください。

シナリオ8:カーソルパラメータ

CREATE PROCEDURE list_by_dept(p_dept_id NUMBER) IS
  -- パラメータ付き CURSOR
  CURSOR c(dept NUMBER) IS 
    SELECT id, name FROM emp WHERE dept_id = dept;
  r c%ROWTYPE;
BEGIN
  OPEN c(p_dept_id);
  LOOP
    FETCH c INTO r;
    EXIT WHEN c%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(r.name);
  END LOOP;
  CLOSE c;
END;
/

シナリオ9:Java Spring での対処

@Service
public class CursorService {
    private static final Set<String> CASCADE_ERRORS = 
        Set.of("ORA-06550", "PLS-00341", "PLS-00320");
    
    public String findRootCause(SQLException e) {
        return Arrays.stream(e.getMessage().split("\n"))
            .filter(line -> line.matches(".*(PLS-|ORA-)\\d+.*"))
            .filter(line -> CASCADE_ERRORS.stream()
                .noneMatch(line::contains))
            .findFirst()
            .orElse("Unknown");
    }
}

シナリオ10:Autonomous DB での対応

Autonomous DB でも同じ動作
- カーソル SELECT の権限確認
- ADMIN 経由の権限付与
- シノニムでスキーマ間参照

トラブルシューティング

エラースタックが長い

PLS-00341/PLS-00320 は無視、それ以外の PLS-* を見る。

CURSOR の SELECT は動く

ブロック構造での宣言位置を確認、前方参照を修正。

REF CURSOR 強型で頻発

弱型 SYS_REFCURSOR に変更推奨。

PL/SQL 権限問題

ロール経由不可直接権限必要。

Rails でのエラー

エラースタック全体をログ、根本原因を抽出。

PostgreSQL からの移行

PG は CURSOR より SETOF、Oracle 移行時は CURSOR 設計必要。


よくある質問(FAQ)

Q1. PLS-00341 と PLS-00320 の違い

  • 00341: CURSOR 宣言不完全
  • 00320: 型宣言不完全(多くはカスケード)

Q2. PLS-00201 との関係

PLS-00341 の根本原因として PLS-00201 が発生することが多い。

Q3. RETURN 型は必要か

通常不要、SELECT で暗黙的に決定。

Q4. 自己参照 RETURN 型

Oracle 公式が illegalCURSOR c RETURN c%ROWTYPE IS ... は不可。

Q5. REF CURSOR の推奨

弱型 SYS_REFCURSOR 推奨、柔軟性高い。

Q6. Rails での対応

エラースタック解析、根本原因抽出。

Q7. Java での対応

正規表現でスタック解析、cascade error 除外。

Q8. Python での対応

oracledb.DatabaseError の message 解析。

Q9. スキーマ間 CURSOR

SELECT 権限 + シノニムが必要。

Q10. INVALID オブジェクト

再コンパイルで解消することが多い。

Q11. CURSOR パラメータ

通常のパラメータと同様、CURSOR c(param TYPE) 形式。

Q12. パフォーマンスへの影響

コンパイルエラー、実行時パフォーマンス影響なし。


参考リンク

Oracle 公式


まとめ

PLS-00341: declaration of cursor is incomplete or malformed の要点を再整理します。

エラーの本質

CURSOR 宣言が不完全 or 不正
→ 多くの場合、SELECT 内の識別子未定義が根本原因
→ PLS-00201 の結果として PLS-00341 発生(カスケード)
→ さらに PLS-00320 も併発

エラーメッセージの読み方

PLS-00341: declaration of cursor 'CURSOR_NAME' is incomplete or malformed
                                   ↑
                            不完全なカーソル

エラーカスケード構造

根本原因: PLS-00201(識別子未宣言)
   ↓
カスケード1: PLS-00341(カーソル不完全)
   ↓
カスケード2: PLS-00320(型宣言不完全)
   ↓
カスケード3: PLS-00597(INTO 型不一致)

姉妹エラー4種

エラー意味
PLS-00341カーソル宣言不完全
PLS-00320型宣言不完全
PLS-00201識別子未宣言
PLS-00597INTO 型不一致

10大原因

#原因対処
未存在テーブル名前確認
未存在列列名確認
スペルミス修正
スキーマ違いシノニム
自己参照 RETURNRETURN 省略
INVALID オブジェクト再コンパイル
SELECT 権限なし権限付与
大文字小文字リネーム
REF CURSOR 型弱型使用
宣言位置順序修正

6つの解決策

-- ① SELECT 単独検証
SELECT id, name FROM my_table;

-- ② スキーマプレフィックス
CURSOR c IS SELECT * FROM hr.employees;

-- ③ シノニム作成
CREATE SYNONYM employees FOR hr.employees;

-- ④ 弱型 SYS_REFCURSOR
DECLARE rc SYS_REFCURSOR;
BEGIN OPEN rc FOR SELECT ...; END;

-- ⑤ 宣言位置修正
DECLARE
  CURSOR c IS SELECT ...;   -- 先
  r c%ROWTYPE;              -- 後
BEGIN NULL; END;
-- ⑥ INVALID 再コンパイル
ALTER VIEW my_view COMPILE;
UTL_RECOMP.recomp_parallel(4);

診断クエリ Top 3

-- ① テーブル/ビュー存在
SELECT owner, table_name FROM all_tables WHERE table_name = 'X';

-- ② 列存在
SELECT column_name FROM all_tab_columns WHERE table_name = 'X';

-- ③ INVALID 確認
SELECT status FROM user_objects WHERE object_name = 'X';

自己参照 RETURN 型の禁止

-- ❌ 不可(Oracle 公式が明記)
CURSOR c1 RETURN c1%ROWTYPE IS SELECT ...;

-- ✅ 推奨(RETURN 省略)
CURSOR c1 IS SELECT ...;

-- ✅ 別型参照
CURSOR c1 RETURN emp%ROWTYPE IS SELECT * FROM emp;

各言語での対応

Rails:  エラースタック解析 + 根本原因抽出
Java:   正規表現でスタック解析
Python: oracledb.DatabaseError message 解析
共通:   PLS-00341/00320 は無視して他 PLS-* を見る

予防のポイント

1. CURSOR の SELECT を単独実行してから宣言
2. RETURN 型は通常省略(暗黙型)
3. スキーマ間参照は SYNONYM
4. REF CURSOR は弱型 SYS_REFCURSOR 推奨
5. 宣言順序(CURSOR → %ROWTYPE 参照)
6. INVALID オブジェクトは即再コンパイル
7. 引用符付き識別子を避ける
8. IDE のオートコンプリート活用
9. アプリでエラースタック解析実装
10. CI/CD でコンパイル検証

これらの知識は、Oracle での PL/SQL 開発・カーソル設計・ストアドプロシージャ・パッケージ設計・Rails / Java / Python アプリ運用・CI/CD パイプライン・データベース設計など、あらゆる場面で活用できます。本記事をブックマークしておけば、PLS-00341 に出会っても冷静に的確に対処できるようになります。


本記事は2026年6月時点の情報をもとに、Oracle Database 19c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。