【Oracle】PLS-00302の原因と解決方法|component must be declared

【Oracle】PLS-00302の原因と解決方法|component must be declared Oracle

PLS-00302: component 'xxx' must be declared は、Oracle PL/SQLでパッケージ、レコード、カーソル、オブジェクト型などの「部品」を参照した時に、その部品が宣言されていない、または現在の場所から見えない場合に発生するコンパイルエラーです。

たとえば pkg_order.close_order と呼び出した時、pkg_order 自体は見えていても、close_order がパッケージ仕様部に公開されていなければPLS-00302になります。レコード変数の rec.customer_name、カーソル行の row.amount、オブジェクト型の属性参照でも同じ考え方です。

先に結論
PLS-00302では、参照している左側のオブジェクトは見えていることが多いです。見るべきなのは、その中のコンポーネント名が正しいか、仕様部に公開されているか、列名や別名が実際に存在するか、コンパイル済みの定義が最新かです。名前そのものが見えない場合は PLS-00201の原因と解決方法 も確認してください。
スポンサーリンク

PLS-00302とは

Oracle公式のエラー説明では、PLS-00302は「component must be declared」、つまり指定したコンポーネントを宣言する必要があるという意味です。対処は、コンポーネントのスペル、宣言、そしてブロック構造内の正しい位置に宣言されているかを確認することです。

ここでいうコンポーネントは、単独の変数名だけではありません。パッケージ内のプロシージャ・ファンクション・定数・型、レコードのフィールド、カーソルやROWTYPEの列、オブジェクト型の属性やメソッドなど、ドットで参照する右側の名前を広く指します。

参照例 PLS-00302で疑う場所 確認方法
pkg_order.close_order パッケージ仕様部に close_order があるか USER_SOURCE / ALL_PROCEDURES
rec.customer_name レコード型に customer_name があるか 型定義、SELECT列、別名
row.total_amount カーソルやROWTYPEの列名が正しいか カーソルSELECT句、ALL_TAB_COLUMNS
obj.status_name オブジェクト型の属性・メソッドがあるか USER_TYPE_ATTRS / 型定義

まず確認する順番

PLS-00302は原因候補が広いため、エラー行だけを眺めるより、参照している名前を分解して確認します。a.b の形なら、a が何で、b がその中に存在するのかを切り分けます。

順番 確認すること 見る場所
1 エラー行のドット参照 pkg.procrec.col など
2 左側の名前が何か パッケージ、レコード、カーソル行、オブジェクト型
3 右側のコンポーネントが存在するか 仕様部、型定義、SELECT句、列定義
4 外部から見える宣言か パッケージ仕様部、スコープ、権限
5 定義が最新か USER_ERRORS、再コンパイル、依存オブジェクト

PLS-00201・PLS-00306との違い

PLS-00302は、PLS-00201やPLS-00306と近い文脈で出ます。ただし、見るべき場所が少し違います。

エラー 意味 典型例
PLS-00201 識別子そのものが見えない pkg_order が存在しない、権限がない
PLS-00302 見えているものの中に対象コンポーネントがない pkg_order.close_order が仕様部にない
PLS-00306 呼び出し先はあるが引数が合わない 引数の数、型、OUT/IN OUTが違う

呼び出し先の名前解決なら PLS-00201、引数不一致なら PLS-00306 を見ます。PLS-00302はその中間で、「入れ物はあるが、その中の部品がない」と考えると整理しやすいです。

パッケージ仕様部に公開していないケース

最も多いのは、パッケージ本体にはプロシージャを書いたが、仕様部に宣言していないケースです。Oracleのパッケージは、外部から参照できる公開部品を仕様部に宣言します。本体だけに書いたサブプログラムは、同じパッケージ内の内部処理としては使えても、外部からは呼び出せません。

package-private-procedure.sql
CREATE OR REPLACE PACKAGE pkg_order AS
  PROCEDURE create_order(p_order_id IN NUMBER);
END pkg_order;
/

CREATE OR REPLACE PACKAGE BODY pkg_order AS
  PROCEDURE create_order(p_order_id IN NUMBER) AS
  BEGIN
    NULL;
  END;

  PROCEDURE close_order(p_order_id IN NUMBER) AS
  BEGIN
    NULL;
  END;
END pkg_order;
/

-- NG: close_orderは仕様部にないため外部から見えない
BEGIN
  pkg_order.close_order(1001);
END;
/
-- PLS-00302

外部から呼び出す必要があるなら、仕様部にも宣言します。内部専用でよい処理なら、外部から呼び出さないよう呼び出し側を直します。

package-public-procedure.sql
CREATE OR REPLACE PACKAGE pkg_order AS
  PROCEDURE create_order(p_order_id IN NUMBER);
  PROCEDURE close_order(p_order_id IN NUMBER);
END pkg_order;
/

パッケージの考え方は Oracleのストアドプロシージャとファンクションの違い でも整理しています。基本構文から確認したい場合は PL/SQLの基本 も参考になります。

仕様部に公開されているかSQLで確認する

パッケージ仕様部に対象サブプログラムがあるかは、ソースを直接見る方法と、データディクショナリから確認する方法があります。本番障害時は、まず ALL_PROCEDURES で参照可能なサブプログラムを確認し、必要に応じて ALL_SOURCEUSER_SOURCE で仕様部の宣言を確認します。

package-public-member-check.sql
DEFINE owner_name   = 'APP_SCHEMA'
DEFINE package_name = 'PKG_ORDER'

-- 参照可能なパッケージ内サブプログラムを確認
SELECT owner,
       object_name AS package_name,
       procedure_name,
       object_type
FROM all_procedures
WHERE owner = UPPER('&owner_name')
  AND object_name = UPPER('&package_name')
ORDER BY procedure_name;

-- 仕様部の宣言をソースで確認
SELECT line, text
FROM all_source
WHERE owner = UPPER('&owner_name')
  AND name = UPPER('&package_name')
  AND type = 'PACKAGE'
ORDER BY line;
ALL_ARGUMENTSだけで判断しない
Oracle 12c以降では、引数なしのプロシージャは ALL_ARGUMENTS に行が出ないことがあります。そのため、引数を持たない公開プロシージャの存在確認は ALL_PROCEDURES や仕様部ソースも併用します。引数の型や個数まで見る時に ALL_ARGUMENTS を使う、という分担にすると誤判定を減らせます。

パッケージ仕様部と本体の不一致

仕様部に宣言した名前と、本体で定義した名前が微妙に違う場合も混乱します。この場合はPLS-00302だけでなく、PLS-00323やPLS-00905、ORA-04063に発展することがあります。

package-spec-body-mismatch.sql
CREATE OR REPLACE PACKAGE pkg_report AS
  PROCEDURE export_csv(p_report_id IN NUMBER);
END pkg_report;
/

CREATE OR REPLACE PACKAGE BODY pkg_report AS
  -- NG: 仕様部はexport_csvだが、本体はexport_textになっている
  PROCEDURE export_text(p_report_id IN NUMBER) AS
  BEGIN
    NULL;
  END;
END pkg_report;
/

パッケージ変更後に関連エラーが出る場合は、PLS-00905の原因と解決方法ORA-04063の原因と解決方法ORA-06508の原因と解決方法 も確認すると切り分けやすいです。

レコード項目名を間違えているケース

レコード型のフィールド名を間違えると、PLS-00302になります。テーブル列名に似せたつもりでも、レコード型側で別名を付けている場合は、その別名が正です。

record-field-name.sql
DECLARE
  TYPE t_customer IS RECORD (
    customer_id NUMBER,
    full_name   VARCHAR2(100)
  );
  v_customer t_customer;
BEGIN
  -- NG: nameというフィールドはない
  DBMS_OUTPUT.PUT_LINE(v_customer.name);

  -- OK
  DBMS_OUTPUT.PUT_LINE(v_customer.full_name);
END;
/

レコード型をパッケージ仕様部で公開している場合は、呼び出し側が同じ型を使っているかも確認します。見た目が同じフィールド構成でも、別の型として宣言されていれば別物です。

record-field-check.sql
-- テーブルの%ROWTYPEを使っている場合は、実列名を確認
SELECT column_name, data_type, data_length
FROM all_tab_columns
WHERE owner = 'APP_SCHEMA'
  AND table_name = 'CUSTOMERS'
ORDER BY column_id;

-- カーソルや独自RECORDの場合は、SELECT句やTYPE定義の別名が正
-- テーブル列名と同じとは限らない点に注意

カーソル・ROWTYPEの列名や別名が違うケース

カーソルFORループや %ROWTYPE では、SELECT句の列名または別名が参照名になります。計算列や関数呼び出しには明示的な別名を付けておくと、PLS-00302を避けやすくなります。

cursor-column-alias.sql
DECLARE
  CURSOR c_orders IS
    SELECT order_id,
           amount * tax_rate AS tax_amount
    FROM orders;
BEGIN
  FOR r IN c_orders LOOP
    -- NG: taxという列名はカーソルにない
    DBMS_OUTPUT.PUT_LINE(r.tax);

    -- OK: SELECT句の別名を使う
    DBMS_OUTPUT.PUT_LINE(r.tax_amount);
  END LOOP;
END;
/

テーブル列の有無を確認する場合は ALL_TAB_COLUMNS、カーソルのSELECT句なら実際のSQLを見ます。ビューやシノニムを挟んでいる場合は、参照先の列定義が変わっていないかも確認してください。

オブジェクト型の属性・メソッドが見えないケース

オブジェクト型を使っている場合も、存在しない属性やメソッドを参照するとPLS-00302になります。属性名のスペル、型定義の再コンパイル、呼び出している型が想定通りかを確認します。

object-type-component.sql
CREATE OR REPLACE TYPE order_obj AS OBJECT (
  order_id NUMBER,
  status   VARCHAR2(20)
);
/

DECLARE
  v_order order_obj := order_obj(1001, 'DONE');
BEGIN
  -- NG: status_nameという属性はない
  DBMS_OUTPUT.PUT_LINE(v_order.status_name);

  -- OK
  DBMS_OUTPUT.PUT_LINE(v_order.status);
END;
/

別スキーマ・権限・シノニムで勘違いしているケース

別スキーマのパッケージを呼ぶ時、パッケージ自体は見えていても、想定している版と違うパッケージを参照していることがあります。シノニムの向き、スキーマ修飾、EXECUTE権限、デプロイ先のオブジェクト定義を確認します。

schema-synonym-check.sql
SELECT owner, object_name, object_type, status
FROM all_objects
WHERE object_name = 'PKG_ORDER'
ORDER BY owner, object_type;

SELECT owner, synonym_name, table_owner, table_name
FROM all_synonyms
WHERE synonym_name = 'PKG_ORDER';

SELECT owner, table_name, grantee, privilege
FROM all_tab_privs
WHERE table_name = 'PKG_ORDER'
  AND privilege = 'EXECUTE';

権限まわりで迷う場合は ORA-01031の原因と解決方法 も参考になります。ただしPLS-00302では、権限そのものより「参照している定義が想定と違う」ケースも多いです。

調査SQLセット

本番や検証環境でPLS-00302が出たら、まず対象パッケージや型がどのように見えているかを確認します。

pls-00302-investigation.sql
DEFINE owner_name   = 'APP_SCHEMA'
DEFINE package_name = 'PKG_ORDER'

-- パッケージの存在と状態
SELECT owner, object_name, object_type, status
FROM all_objects
WHERE owner = UPPER('&owner_name')
  AND object_name = UPPER('&package_name')
ORDER BY object_type;

-- 公開サブプログラムと引数
SELECT owner,
       object_name AS package_name,
       procedure_name,
       object_type
FROM all_procedures
WHERE owner = UPPER('&owner_name')
  AND object_name = UPPER('&package_name')
ORDER BY procedure_name;

-- 引数情報
SELECT owner,
       package_name,
       object_name,
       overload,
       argument_name,
       position,
       data_type,
       in_out
FROM all_arguments
WHERE owner = UPPER('&owner_name')
  AND package_name = UPPER('&package_name')
ORDER BY object_name, overload, sequence;

-- コンパイルエラー
SELECT name, type, line, position, text
FROM all_errors
WHERE owner = UPPER('&owner_name')
  AND name = UPPER('&package_name')
ORDER BY sequence;

ALL_ARGUMENTS は、参照可能なプロシージャやファンクションの引数情報を返します。Oracle公式の説明にもある通り、関数の戻り値は ARGUMENT_NAME がNULL、POSITION = 0 として扱われることがあります。引数の不一致まで進んだ場合は PLS-00306 側の観点で確認します。

PLS-00302が出やすい書き方

書き方 なぜ危ないか 安全な寄せ方
パッケージ本体だけに処理を追加して外部から呼ぶ 仕様部にないため外部公開されない 外部APIは仕様部に宣言する
SELECT式に別名を付けずカーソル行で参照する 参照名が分かりにくくなる 計算列・関数列には必ず別名を付ける
ローカルRECORDとパッケージRECORDを混用する 同じ形でも同じ型とは限らない 呼び出し先が要求する型を使う
シノニム越しにパッケージを呼ぶ 想定と違うスキーマの定義を見やすい 障害時は完全修飾名で確認する
INVALIDなパッケージを放置する 仕様部・本体・依存関係が崩れている可能性 ALL_ERRORS を確認して再コンパイル

よくある原因と対処一覧

原因 確認すること 対処
パッケージ仕様部にない 仕様部に対象名が宣言されているか 外部公開が必要なら仕様部へ追加
スペルミス 大文字小文字、アンダースコア、略称 定義名に合わせる
本体だけにある内部処理を呼んだ PACKAGE BODYだけの宣言か 外部から呼ばない、または仕様部に公開
レコード項目名が違う TYPE定義やSELECT別名 正しいフィールド名を使う
カーソル列名が違う SELECT句の列名・別名 別名を付けて参照名を固定
別スキーマの別定義を見ている シノニム、スキーマ修飾 完全修飾名で確認
パッケージがINVALID ALL_ERRORS / USER_ERRORS 原因を直して再コンパイル

修正チェックリスト

項目 OKの状態
エラー行の a.b を分解した a の種類と b の意味が分かっている
パッケージ仕様部を確認した 外部公開が必要なサブプログラムが仕様部にある
レコード・カーソル列名を確認した 参照名が型定義またはSELECT別名と一致している
シノニムとスキーマを確認した 想定しているオブジェクトを参照している
コンパイルエラーを確認した USER_ERRORS が空、または原因を把握している
関連エラーを切り分けた PLS-00201、PLS-00306、PLS-00905と混同していない

よくある質問

パッケージ本体にはプロシージャがあるのにPLS-00302になります

外部から呼ぶには、パッケージ仕様部に宣言されている必要があります。PACKAGE BODYだけにあるサブプログラムは、外部公開されていません。

PLS-00201との違いは何ですか?

PLS-00201は識別子そのものが見えない時、PLS-00302は見えている入れ物の中に対象コンポーネントがない時に出ます。

テーブルに列があるのにレコードでPLS-00302になります

レコードやカーソルでは、テーブル列ではなくレコード型定義やSELECT句の別名が参照名になります。実際にそのレコードが持つフィールドを確認してください。

再コンパイルで直りますか?

定義変更後にINVALIDになっているだけなら直ることがあります。ただし、仕様部にない、列名が違う、権限やシノニムが違う場合は定義や呼び出し側の修正が必要です。

ORA-06550も一緒に出ています

ORA-06550はPL/SQLブロック全体の失敗を示す入口です。後続のPLS-00302に出ているコンポーネント名を中心に確認してください。

まとめ

PLS-00302は、パッケージ、レコード、カーソル、オブジェクト型などの中に、参照したコンポーネントが見つからない時のエラーです。まずエラー行のドット参照を分解し、左側が何を指しているか、右側の名前がその中に存在するかを確認します。

特に多いのは、パッケージ本体にだけ書いたプロシージャを外部から呼んでいるケース、レコードやカーソルのフィールド名を間違えているケース、別スキーマやシノニムで想定と違う定義を見ているケースです。仕様部、型定義、SELECT別名、ALL_ERRORS を順番に確認すれば、原因をかなり絞り込めます。

参考

PLS-00302 – Oracle Database Error Help

PL/SQL Packages – Oracle Database PL/SQL Language Reference

ALL_ARGUMENTS – Oracle Database Reference