【完全ガイド】PLS-00341: declaration of cursor is incomplete or malformed の原因と解決方法|PLS-00320 カスケード・自己参照 RETURN 型 徹底解説
- 作成日 2026.08.26
- その他
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
- 1. 結論:CURSOR 内 SELECT を検証
- 2. Oracle CURSOR の宣言構造
- 3. 【原因①】CURSOR 内 SELECT で未存在テーブル(最頻出)
- 4. 【原因②】CURSOR 内 SELECT で未存在列
- 5. 【原因③】スペルミス
- 6. 【原因④】スキーマ違い
- 7. 【原因⑤】自己参照的な RETURN 型(Oracle 公式が明記)
- 8. 【原因⑥】INVALID なテーブル/ビュー
- 9. 【原因⑦】権限なし(SELECT 権限不足)
- 10. 【原因⑧】大文字小文字(引用符付き識別子)
- 11. 【原因⑨】REF CURSOR 型不整合
- 12. 【原因⑩】ブロック構造での宣言位置
- 13. 診断ツール完全リファレンス
- 14. 6つの解決策 完全リファレンス
- 15. Rails / Java / Python 対応
- 16. 実践シナリオ
- 17. トラブルシューティング
- 18. よくある質問(FAQ)
- 19. 参考リンク
- 20. まとめ
エラーカスケードの構造
通常、複数エラーがセットで発生:
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_REFCURSOR | REF CURSOR |
| ⑤ | 宣言位置修正 | ブロック構造 |
| ⑥ | INVALID 再コンパイル | 依存修復 |
併発する PLS-* エラー
| エラー | 意味 |
|---|---|
| PLS-00341 | カーソル宣言不完全 |
| PLS-00320 | 型宣言不完全(カスケード) |
| PLS-00201 | 識別子未宣言(根本原因) |
| PLS-00597 | INTO 型不一致 |
詳細は以下で解説します。
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 公式が illegal、CURSOR 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 公式
- Oracle Database Error Messages: PLS-00341
- Oracle PL/SQL Language Reference: Cursors
- Oracle PL/SQL Language Reference: Cursor Variables
- ALL_TAB_COLUMNS View
まとめ
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-00597 | INTO 型不一致 |
10大原因
| # | 原因 | 対処 |
|---|---|---|
| ① | 未存在テーブル | 名前確認 |
| ② | 未存在列 | 列名確認 |
| ③ | スペルミス | 修正 |
| ④ | スキーマ違い | シノニム |
| ⑤ | 自己参照 RETURN | RETURN 省略 |
| ⑥ | 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)もあわせてご確認ください。
-
前の記事
【完全ガイド】PLS-00302: component must be declared の原因と解決方法|PLS-00201 との違い・シノニム・SPEC 未宣言 徹底解説 2026.08.25
-
次の記事
【完全ガイド】ORA-00376: file X cannot be read at this time の原因と解決方法|データファイル OFFLINE・MEDIA RECOVERY 徹底解説 2026.08.27
コメントを書く