【Oracle】パッケージ(PACKAGE)の作成・活用完全ガイド|仕様部・本体・オーバーロード・ACCESSIBLE BY・実務パターンまで解説

【Oracle】パッケージ(PACKAGE)の作成・活用完全ガイド|仕様部・本体・オーバーロード・ACCESSIBLE BY・実務パターンまで解説 Oracle

Oracle のパッケージ(PACKAGE)は、関連するプロシージャ・ファンクション・変数・定数・カーソル・例外をひとつの論理的な単位にまとめる仕組みです。他のプログラミング言語の「クラス」や「モジュール」に近い概念で、Oracle開発におけるコードの再利用性・保守性・セキュリティを高める上で欠かせない機能です。

基本的なプロシージャ・ファンクションの作成方法についてはストアドプロシージャ・ファンクションの作成完全ガイドを参照してください。本記事ではパッケージ固有の機能に絞って解説します。

この記事でわかること

  • パッケージ仕様部(SPEC)とパッケージ本体(BODY)の役割と関係
  • パッケージ変数・定数・例外・カーソルの宣言方法
  • パッケージの初期化ブロック(Initialization Section)
  • プロシージャ・ファンクションのオーバーロード
  • FORWARD 宣言(前方参照)
  • ACCESSIBLE BY(Oracle 12c+)によるアクセス制御
  • パッケージの確認・再コンパイル・削除
  • 実務でよく使うユーティリティパッケージパターン
スポンサーリンク

パッケージの構造:仕様部と本体

パッケージは仕様部(SPEC)本体(BODY)の2つのオブジェクトで構成されます。

オブジェクト 役割 公開範囲 必須/任意
パッケージ仕様部(SPEC) 外部に公開するインターフェース宣言のみ記述 他のスキーマ・プログラムから参照可能 必須
パッケージ本体(BODY) 実装コード(処理ロジック)を記述 外部から直接参照不可(隠蔽) SPEC に実装が必要な要素がある場合は必須

SPEC に宣言したプロシージャ・ファンクションは外部(他のパッケージ・SQL等)から呼び出せるパブリックな要素です。BODY にのみ記述した要素はパッケージ内部からしかアクセスできないプライベートな要素になります。

パッケージの作成:基本パターン

STEP 1:パッケージ仕様部(SPEC)の作成
-- パッケージ仕様部: 外部に公開するインターフェースのみ宣言
-- 処理の実装(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;
/
STEP 2:パッケージ本体(BODY)の作成
-- パッケージ本体: 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_INFOSYS_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 宣言が必要です。相互参照がある場合や、プライベート関数をファイルの後半にまとめたい場合に使います。

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 から導入された機能です。

ACCESSIBLE BY でアクセス制限する例
-- 内部処理用パッケージ(直接呼び出し禁止)
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;

実務でよく使うパッケージパターン

共通ユーティリティパッケージ

プロジェクト全体で使う汎用関数をまとめたパッケージは、コードの重複排除に効果的です。

util_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カ所に集約できます。

const_pkg:定数専用パッケージ(BODY不要)
-- 定数のみのパッケージは 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 数値または値のエラー)の記事とストアドプロシージャ・ファンクション完全ガイドの例外処理セクションを合わせて参照してください。