Oracle のパッケージ(PACKAGE)は、関連するプロシージャ・ファンクション・変数・定数・カーソル・例外をひとつの論理的な単位にまとめる仕組みです。他のプログラミング言語の「クラス」や「モジュール」に近い概念で、Oracle開発におけるコードの再利用性・保守性・セキュリティを高める上で欠かせない機能です。
基本的なプロシージャ・ファンクションの作成方法についてはストアドプロシージャ・ファンクションの作成完全ガイドを参照してください。本記事ではパッケージ固有の機能に絞って解説します。
- パッケージ仕様部(SPEC)とパッケージ本体(BODY)の役割と関係
- パッケージ変数・定数・例外・カーソルの宣言方法
- パッケージの初期化ブロック(Initialization Section)
- プロシージャ・ファンクションのオーバーロード
- FORWARD 宣言(前方参照)
- ACCESSIBLE BY(Oracle 12c+)によるアクセス制御
- パッケージの確認・再コンパイル・削除
- 実務でよく使うユーティリティパッケージパターン
パッケージの構造:仕様部と本体
パッケージは仕様部(SPEC)と本体(BODY)の2つのオブジェクトで構成されます。
| オブジェクト | 役割 | 公開範囲 | 必須/任意 |
|---|---|---|---|
| パッケージ仕様部(SPEC) | 外部に公開するインターフェース宣言のみ記述 | 他のスキーマ・プログラムから参照可能 | 必須 |
| パッケージ本体(BODY) | 実装コード(処理ロジック)を記述 | 外部から直接参照不可(隠蔽) | SPEC に実装が必要な要素がある場合は必須 |
SPEC に宣言したプロシージャ・ファンクションは外部(他のパッケージ・SQL等)から呼び出せるパブリックな要素です。BODY にのみ記述した要素はパッケージ内部からしかアクセスできないプライベートな要素になります。
パッケージの作成:基本パターン
-- パッケージ仕様部: 外部に公開するインターフェースのみ宣言
-- 処理の実装(BEGIN ... END)は書かない
CREATE OR REPLACE PACKAGE emp_pkg AS
-- パッケージ定数(パブリック)
c_max_salary CONSTANT NUMBER := 10000000;
-- パッケージ型定義(パブリック)
TYPE emp_rec IS RECORD (
emp_id employees.employee_id%TYPE,
emp_name employees.last_name%TYPE,
salary employees.salary%TYPE
);
TYPE emp_tab IS TABLE OF emp_rec;
-- プロシージャ宣言(パラメータ名・型のみ。処理は BODY に書く)
PROCEDURE hire_employee(
p_last_name IN employees.last_name%TYPE,
p_department IN employees.department_id%TYPE,
p_salary IN employees.salary%TYPE,
p_employee_id OUT employees.employee_id%TYPE
);
-- ファンクション宣言
FUNCTION get_annual_salary(
p_employee_id IN employees.employee_id%TYPE
) RETURN NUMBER;
FUNCTION get_dept_employees(
p_department_id IN employees.department_id%TYPE
) RETURN emp_tab;
END emp_pkg;
/
-- パッケージ本体: SPEC で宣言した要素の実装と、プライベート要素を記述
CREATE OR REPLACE PACKAGE BODY emp_pkg AS
-- プライベート変数(BODY にのみ宣言 → 外部から直接アクセス不可)
g_last_hired_id NUMBER;
-- プライベート関数(BODY 内でのみ使用する補助関数)
FUNCTION validate_salary(p_salary IN NUMBER) RETURN BOOLEAN IS
BEGIN
RETURN p_salary > 0 AND p_salary <= c_max_salary;
END validate_salary;
-- ====== SPEC で宣言したプロシージャの実装 ======
PROCEDURE hire_employee(
p_last_name IN employees.last_name%TYPE,
p_department IN employees.department_id%TYPE,
p_salary IN employees.salary%TYPE,
p_employee_id OUT employees.employee_id%TYPE
) IS
BEGIN
IF NOT validate_salary(p_salary) THEN
RAISE_APPLICATION_ERROR(-20001, '給与が有効範囲外です: ' || p_salary);
END IF;
SELECT emp_seq.NEXTVAL INTO p_employee_id FROM dual;
INSERT INTO employees (employee_id, last_name, department_id, salary, hire_date)
VALUES (p_employee_id, p_last_name, p_department, p_salary, SYSDATE);
g_last_hired_id := p_employee_id; -- プライベート変数に記録
COMMIT;
END hire_employee;
-- ====== SPEC で宣言したファンクションの実装 ======
FUNCTION get_annual_salary(
p_employee_id IN employees.employee_id%TYPE
) RETURN NUMBER IS
v_salary employees.salary%TYPE;
BEGIN
SELECT salary INTO v_salary
FROM employees
WHERE employee_id = p_employee_id;
RETURN v_salary * 12;
EXCEPTION
WHEN NO_DATA_FOUND THEN RETURN NULL;
END get_annual_salary;
FUNCTION get_dept_employees(
p_department_id IN employees.department_id%TYPE
) RETURN emp_tab IS
v_result emp_tab;
BEGIN
SELECT employee_id, last_name, salary
BULK COLLECT INTO v_result
FROM employees
WHERE department_id = p_department_id;
RETURN v_result;
END get_dept_employees;
-- ====== パッケージ初期化ブロック(オプション) ======
-- パッケージが最初にセッションで呼ばれたときに1回だけ実行される
BEGIN
g_last_hired_id := NULL;
-- 必要に応じて初期設定(設定値の読み込み等)を行う
END emp_pkg;
/
-- パッケージの要素は「パッケージ名.要素名」で呼び出す
DECLARE
v_new_id NUMBER;
v_annual NUMBER;
v_emp_list emp_pkg.emp_tab; -- パッケージ型を使う場合も「パッケージ名.型名」
BEGIN
-- プロシージャの呼び出し
emp_pkg.hire_employee(
p_last_name => '山田',
p_department => 10,
p_salary => 350000,
p_employee_id => v_new_id
);
DBMS_OUTPUT.PUT_LINE('新規採用ID: ' || v_new_id);
-- ファンクションの呼び出し
v_annual := emp_pkg.get_annual_salary(p_employee_id => v_new_id);
DBMS_OUTPUT.PUT_LINE('年収: ' || v_annual);
-- コレクション型ファンクション
v_emp_list := emp_pkg.get_dept_employees(p_department_id => 10);
DBMS_OUTPUT.PUT_LINE('部門人数: ' || v_emp_list.COUNT);
END;
/
-- SQL から直接ファンクションを呼ぶことも可能
SELECT emp_pkg.get_annual_salary(100) AS annual_salary FROM dual;
パッケージ変数:セッションスコープのグローバル変数
パッケージ変数はセッションが開始してから終了するまで値を保持します。これはパッケージが持つ重要な特徴のひとつで、プロシージャ内のローカル変数とは異なります。
-- パブリックなパッケージ変数の例
CREATE OR REPLACE PACKAGE session_ctx AS
g_user_id NUMBER;
g_tenant_id NUMBER;
g_lang VARCHAR2(10) := 'JA'; -- デフォルト値あり
PROCEDURE set_context(p_user_id NUMBER, p_tenant_id NUMBER);
FUNCTION get_user_id RETURN NUMBER;
END session_ctx;
/
CREATE OR REPLACE PACKAGE BODY session_ctx AS
PROCEDURE set_context(p_user_id NUMBER, p_tenant_id NUMBER) IS
BEGIN
g_user_id := p_user_id;
g_tenant_id := p_tenant_id;
END;
FUNCTION get_user_id RETURN NUMBER IS
BEGIN
RETURN g_user_id;
END;
END session_ctx;
/
-- 使用例: セッション変数として利用
BEGIN
session_ctx.set_context(p_user_id => 42, p_tenant_id => 1);
END;
/
-- 同一セッション内ならどこからでもアクセス可能
SELECT session_ctx.get_user_id() AS current_user_id FROM dual;
-- → 42(セッション中に値が保持される)
パッケージ変数はセッションごとに独立したメモリ空間を持ちます。そのため、並列処理や接続プール(DRCP・Thin接続プール等)では、セッションの再利用時に前回の値が残っている場合があります。重要な状態管理には
DBMS_APPLICATION_INFO や SYS_CONTEXT の使用も検討してください。
プロシージャ・ファンクションのオーバーロード
パッケージ内では、同じ名前でパラメータの型・数・順序が異なる複数のサブプログラムを定義できます(オーバーロード)。これにより呼び出し元が引数の型を意識しなくてもよくなります。
CREATE OR REPLACE PACKAGE format_pkg AS
-- 同じ名前 format_value に複数の定義
FUNCTION format_value(p_val IN NUMBER) RETURN VARCHAR2; -- 数値版
FUNCTION format_value(p_val IN DATE) RETURN VARCHAR2; -- 日付版
FUNCTION format_value(p_val IN NUMBER,
p_fmt IN VARCHAR2) RETURN VARCHAR2; -- 書式指定版
END format_pkg;
/
CREATE OR REPLACE PACKAGE BODY format_pkg AS
-- 数値: カンマ区切りで整形
FUNCTION format_value(p_val IN NUMBER) RETURN VARCHAR2 IS
BEGIN
RETURN TO_CHAR(p_val, 'FM999,999,999,990');
END;
-- 日付: YYYY/MM/DD 形式に整形
FUNCTION format_value(p_val IN DATE) RETURN VARCHAR2 IS
BEGIN
RETURN TO_CHAR(p_val, 'YYYY/MM/DD');
END;
-- 数値 + 書式: 書式を明示
FUNCTION format_value(p_val IN NUMBER, p_fmt IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN TO_CHAR(p_val, p_fmt);
END;
END format_pkg;
/
-- 呼び出し例(Oracleが引数の型から自動的に適切な版を選択)
SELECT format_pkg.format_value(1234567) FROM dual; -- '1,234,567'
SELECT format_pkg.format_value(SYSDATE) FROM dual; -- '2025/04/03'
SELECT format_pkg.format_value(9876.54, 'FM9999.00') FROM dual; -- '9876.54'
FORWARD 宣言(前方参照)
パッケージ本体内で、定義より前にサブプログラムを呼び出す場合、FORWARD 宣言が必要です。相互参照がある場合や、プライベート関数をファイルの後半にまとめたい場合に使います。
CREATE OR REPLACE PACKAGE BODY calc_pkg AS
-- FORWARD 宣言: is_valid_amount の本体は後方に定義されているが
-- この時点で呼べるようにする
FUNCTION is_valid_amount(p_amount NUMBER) RETURN BOOLEAN; -- これが FORWARD 宣言
-- FORWARD 宣言した関数を使う側
PROCEDURE process_payment(p_amount IN NUMBER) IS
BEGIN
IF NOT is_valid_amount(p_amount) THEN -- ← 後方に定義された関数を参照
RAISE_APPLICATION_ERROR(-20010, '金額が無効です');
END IF;
-- ... 支払い処理 ...
END process_payment;
-- is_valid_amount の実際の実装(本体は後ろに来る)
FUNCTION is_valid_amount(p_amount NUMBER) RETURN BOOLEAN IS
BEGIN
RETURN p_amount > 0 AND p_amount <= 9999999;
END is_valid_amount;
END calc_pkg;
/
ACCESSIBLE BY 句(Oracle 12c 以降)
ACCESSIBLE BY 句を使うと、パッケージにアクセスできる呼び出し元を特定のプログラムに制限できます。セキュリティを強化し、意図しない呼び出しを防ぐために有効です。Oracle 12c Release 1 から導入された機能です。
-- 内部処理用パッケージ(直接呼び出し禁止)
CREATE OR REPLACE PACKAGE internal_billing_pkg
ACCESSIBLE BY (PACKAGE billing_api_pkg, PROCEDURE billing_admin_proc)
AS
PROCEDURE charge_account(p_account_id NUMBER, p_amount NUMBER);
FUNCTION calculate_tax(p_amount NUMBER) RETURN NUMBER;
END internal_billing_pkg;
/
-- 呼び出しが許可されたパッケージ(billing_api_pkg)からは呼べる
CREATE OR REPLACE PACKAGE billing_api_pkg AS
PROCEDURE process_invoice(p_invoice_id NUMBER);
END billing_api_pkg;
/
CREATE OR REPLACE PACKAGE BODY billing_api_pkg AS
PROCEDURE process_invoice(p_invoice_id NUMBER) IS
v_amount NUMBER;
v_tax NUMBER;
BEGIN
-- internal_billing_pkg へのアクセスが許可されている
v_tax := internal_billing_pkg.calculate_tax(v_amount);
internal_billing_pkg.charge_account(1, v_amount + v_tax);
END;
END billing_api_pkg;
/
-- 許可されていないパッケージから呼ぼうとするとコンパイルエラー
CREATE OR REPLACE PACKAGE another_pkg AS
PROCEDURE test;
END;
/
CREATE OR REPLACE PACKAGE BODY another_pkg AS
PROCEDURE test IS BEGIN
internal_billing_pkg.charge_account(1, 100); -- ← コンパイルエラー
-- PLS-00904: insufficient privilege to access object INTERNAL_BILLING_PKG
END;
END;
/
パッケージの確認・再コンパイル・削除
-- パッケージ一覧と状態確認(VALID / INVALID)
SELECT object_name, object_type, status, last_ddl_time
FROM user_objects
WHERE object_type IN ('PACKAGE', 'PACKAGE BODY')
ORDER BY object_name, object_type;
-- INVALID なオブジェクトの確認
SELECT object_name, object_type, status
FROM user_objects
WHERE status = 'INVALID'
AND object_type IN ('PACKAGE', 'PACKAGE BODY');
-- コンパイルエラーの確認(SQL*Plus / SQL Developer) SHOW ERRORS PACKAGE emp_pkg; SHOW ERRORS PACKAGE BODY emp_pkg; -- または USER_ERRORS ビューで確認(アプリから確認する場合) SELECT name, type, line, position, text FROM user_errors WHERE name = 'EMP_PKG' ORDER BY type, line, position; -- 手動再コンパイル ALTER PACKAGE emp_pkg COMPILE; -- SPEC のみ ALTER PACKAGE emp_pkg COMPILE BODY; -- BODY のみ ALTER PACKAGE emp_pkg COMPILE PACKAGE; -- SPEC と BODY 両方 -- スキーマ全体の INVALID オブジェクトを一括再コンパイル EXEC DBMS_UTILITY.COMPILE_SCHEMA(schema => USER, compile_all => FALSE);
-- ソースコードの確認 SELECT text FROM user_source WHERE name = 'EMP_PKG' AND type = 'PACKAGE' -- または 'PACKAGE BODY' ORDER BY line; -- パッケージの削除(BODY のみ削除 or 両方削除) DROP PACKAGE BODY emp_pkg; -- BODY のみ(SPEC は残る) DROP PACKAGE emp_pkg; -- SPEC と BODY を両方削除 -- Oracle 23ai: IF EXISTS で存在確認不要 DROP PACKAGE IF EXISTS emp_pkg;
実務でよく使うパッケージパターン
共通ユーティリティパッケージ
プロジェクト全体で使う汎用関数をまとめたパッケージは、コードの重複排除に効果的です。
CREATE OR REPLACE PACKAGE util_pkg AS
-- 文字列操作
FUNCTION to_half(p_str IN VARCHAR2) RETURN VARCHAR2; -- 全角→半角
FUNCTION trim_all(p_str IN VARCHAR2) RETURN VARCHAR2; -- 全角スペース含む除去
-- 数値変換(安全版)
FUNCTION to_num_safe(p_str IN VARCHAR2,
p_default IN NUMBER DEFAULT NULL) RETURN NUMBER;
-- 日付変換(安全版)
FUNCTION to_date_safe(p_str IN VARCHAR2,
p_fmt IN VARCHAR2 DEFAULT 'YYYY-MM-DD',
p_default IN DATE DEFAULT NULL) RETURN DATE;
-- エラーログ
PROCEDURE log_error(p_proc_name IN VARCHAR2,
p_err_code IN NUMBER,
p_err_msg IN VARCHAR2);
END util_pkg;
/
CREATE OR REPLACE PACKAGE BODY util_pkg AS
FUNCTION to_num_safe(p_str IN VARCHAR2, p_default IN NUMBER DEFAULT NULL)
RETURN NUMBER IS
BEGIN
RETURN TO_NUMBER(p_str);
EXCEPTION
WHEN VALUE_ERROR THEN RETURN p_default;
END;
FUNCTION to_date_safe(p_str IN VARCHAR2,
p_fmt IN VARCHAR2 DEFAULT 'YYYY-MM-DD',
p_default IN DATE DEFAULT NULL)
RETURN DATE IS
BEGIN
RETURN TO_DATE(p_str, p_fmt);
EXCEPTION
WHEN VALUE_ERROR THEN RETURN p_default;
END;
PROCEDURE log_error(p_proc_name IN VARCHAR2,
p_err_code IN NUMBER,
p_err_msg IN VARCHAR2) IS
PRAGMA AUTONOMOUS_TRANSACTION; -- メイントランザクションに影響しない
BEGIN
INSERT INTO error_log (log_dt, proc_name, err_code, err_msg)
VALUES (SYSTIMESTAMP, p_proc_name, p_err_code, p_err_msg);
COMMIT;
END;
-- BODY プライベートな実装は省略
FUNCTION to_half(p_str IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN p_str; END;
FUNCTION trim_all(p_str IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN RETURN TRIM(REPLACE(p_str, ' ', ' ')); END;
END util_pkg;
/
定数パッケージ(マジックナンバーの排除)
システム全体で使う定数値をパッケージにまとめると、変更時の修正箇所を1カ所に集約できます。
-- 定数のみのパッケージは BODY が不要
CREATE OR REPLACE PACKAGE const_pkg AS
-- 注文ステータス
c_status_draft CONSTANT NUMBER := 0;
c_status_confirmed CONSTANT NUMBER := 1;
c_status_shipped CONSTANT NUMBER := 2;
c_status_cancelled CONSTANT NUMBER := 9;
-- エラーコード
c_err_invalid_amount CONSTANT NUMBER := -20001;
c_err_not_found CONSTANT NUMBER := -20002;
c_err_duplicate CONSTANT NUMBER := -20003;
-- 設定値
c_max_retry CONSTANT NUMBER := 3;
c_batch_size CONSTANT NUMBER := 1000;
END const_pkg;
/
-- 使用例
UPDATE orders SET status = const_pkg.c_status_confirmed
WHERE order_id = 12345;
IF v_retry_count > const_pkg.c_max_retry THEN
RAISE_APPLICATION_ERROR(const_pkg.c_err_invalid_amount, 'リトライ上限超過');
END IF;
まとめ
Oracle のパッケージは、プロシージャ・ファンクション・変数・定数・型・カーソルを論理的にまとめる強力な仕組みです。
- SPEC/BODY 分離により、実装の隠蔽と公開インターフェースの明確化ができる
- パッケージ変数でセッションスコープの状態管理が可能
- オーバーロードで同名の処理を型・引数数で使い分けできる
- ACCESSIBLE BY(Oracle 12c+)でアクセス制御を強化できる
- 実務では「共通ユーティリティパッケージ」「定数パッケージ」がよく使われる設計パターン
パッケージ内でのエラー処理のベストプラクティスはORA-06502(PL/SQL 数値または値のエラー)の記事とストアドプロシージャ・ファンクション完全ガイドの例外処理セクションを合わせて参照してください。

