0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PostgreSQLのストアドプロシージャ入門〜PL/pgSQLの基礎とFUNCTIONとの違い〜

0
Posted at

はじめに

PostgreSQLでは、業務ロジックをDB側に持たせる手段として「ストアドプロシージャ(PROCEDURE)」と「ストアドファンクション(FUNCTION)」があります。両者は似ていますが役割が異なるため、違いを整理しつつPL/pgSQLの基礎から解説します。

この後の流れ

  1. ストアドプロシージャとは何か
  2. PROCEDUREとFUNCTIONの違い
  3. PL/pgSQLの基本構文
  4. 制御構文(条件分岐・ループ)
  5. トランザクション制御(COMMIT/ROLLBACK)
  6. 応用:エラーハンドリングと例外処理
  7. まとめ

1. ストアドプロシージャとは何か

DBサーバー内に保存され、呼び出すことで実行できる一連のSQL処理・ロジックのまとまりです。アプリケーション側(Python等)に書いていたロジックの一部をDB内に移すことで、以下のメリットがあります。

メリット 内容
ネットワーク往復の削減 複数SQLを1回の呼び出しで完結できる
ロジックの一元管理 複数アプリから同じ処理を共通利用できる
トランザクション制御 手続き内で明示的にCOMMIT/ROLLBACKできる(FUNCTIONにはできない)

2. PROCEDUREとFUNCTIONの違い

PostgreSQL 11以降、PROCEDUREFUNCTIONは明確に使い分けられています。

項目 FUNCTION PROCEDURE
戻り値 必須(RETURNSで型指定) 基本的に不要(OUTパラメータで代替可)
呼び出し方 SELECT my_func(...) CALL my_proc(...)
トランザクション制御 内部でCOMMIT/ROLLBACK不可 内部でCOMMIT/ROLLBACK可能
主な用途 値の計算・変換・SELECT内での利用 バッチ処理、複数ステップの業務トランザクション

判断基準: 「値を1つ計算して返したいだけ」ならFUNCTION、「複数のテーブル更新を含む一連の業務処理をトランザクション単位で実行したい」ならPROCEDUREを選びます。

3. PL/pgSQLの基本構文

PostgreSQL独自の手続き型言語がPL/pgSQLです。基本構造は以下の通りです。

CREATE OR REPLACE PROCEDURE update_stock(
    p_product_id INT,
    p_quantity INT
)
LANGUAGE plpgsql
AS $$
BEGIN
    UPDATE products
    SET stock = stock - p_quantity
    WHERE product_id = p_product_id;

    INSERT INTO stock_log (product_id, change_amount, changed_at)
    VALUES (p_product_id, -p_quantity, NOW());
END;
$$;

呼び出しは以下の通りです。

CALL update_stock(101, 5);

FUNCTIONの場合は戻り値を伴います。

CREATE OR REPLACE FUNCTION get_stock(p_product_id INT)
RETURNS INT
LANGUAGE plpgsql
AS $$
DECLARE
    v_stock INT;
BEGIN
    SELECT stock INTO v_stock FROM products WHERE product_id = p_product_id;
    RETURN v_stock;
END;
$$;
SELECT get_stock(101);

4. 制御構文(条件分岐・ループ)

CREATE OR REPLACE PROCEDURE apply_discount(p_product_id INT)
LANGUAGE plpgsql
AS $$
DECLARE
    v_price NUMERIC;
BEGIN
    SELECT price INTO v_price FROM products WHERE product_id = p_product_id;

    IF v_price >= 10000 THEN
        UPDATE products SET price = price * 0.9 WHERE product_id = p_product_id;
    ELSIF v_price >= 3000 THEN
        UPDATE products SET price = price * 0.95 WHERE product_id = p_product_id;
    ELSE
        RAISE NOTICE '割引対象外の商品です: %', p_product_id;
    END IF;
END;
$$;

複数行に対するループ処理(FOR文)の例です。

CREATE OR REPLACE PROCEDURE recalculate_all_stock()
LANGUAGE plpgsql
AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT product_id FROM products LOOP
        UPDATE products
        SET stock = (SELECT COALESCE(SUM(change_amount), 0) FROM stock_log WHERE product_id = rec.product_id)
        WHERE product_id = rec.product_id;
    END LOOP;
END;
$$;

5. トランザクション制御(COMMIT/ROLLBACK)

PROCEDURE内では明示的にトランザクションを制御できます。これがFUNCTIONにはない最大の特徴です。

CREATE OR REPLACE PROCEDURE process_orders_batch()
LANGUAGE plpgsql
AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT order_id FROM orders WHERE status = '未処理' LOOP
        BEGIN
            UPDATE orders SET status = '処理済' WHERE order_id = rec.order_id;
            COMMIT;  -- 1件ごとに確定させる(バッチ処理での部分コミット)
        EXCEPTION WHEN OTHERS THEN
            ROLLBACK;
            RAISE NOTICE '注文ID % の処理に失敗しました: %', rec.order_id, SQLERRM;
        END;
    END LOOP;
END;
$$;

注意: COMMIT/ROLLBACKはトップレベルのPROCEDURE呼び出し(CALL)のコンテキストでのみ有効です。他のPROCEDURE/FUNCTIONから呼ばれるネストした状態では使えません。

6. 応用:エラーハンドリングと例外処理

CREATE OR REPLACE PROCEDURE transfer_stock(
    p_from_product_id INT,
    p_to_product_id INT,
    p_quantity INT
)
LANGUAGE plpgsql
AS $$
DECLARE
    v_from_stock INT;
BEGIN
    SELECT stock INTO v_from_stock FROM products WHERE product_id = p_from_product_id;

    IF v_from_stock < p_quantity THEN
        RAISE EXCEPTION '在庫不足です。product_id=%, 現在庫=%', p_from_product_id, v_from_stock;
    END IF;

    UPDATE products SET stock = stock - p_quantity WHERE product_id = p_from_product_id;
    UPDATE products SET stock = stock + p_quantity WHERE product_id = p_to_product_id;

    COMMIT;
EXCEPTION WHEN OTHERS THEN
    RAISE NOTICE 'エラーが発生したためロールバックします: %', SQLERRM;
    ROLLBACK;
END;
$$;

RAISE EXCEPTIONで意図的にエラーを発生させ、EXCEPTION WHEN OTHERSでキャッチしてロールバックする、という基本パターンです。

7. まとめ

観点 FUNCTION PROCEDURE
値の計算・変換
複数テーブルの一括更新
トランザクション制御 不可 可能
SELECT文の中で使う 可能 不可(CALLでのみ実行)
バッチ処理・夜間ジョブ 不向き 向いている

さいごに

PostgreSQLのPROCEDUREは、アプリ側で書きがちな「複数更新+条件分岐+トランザクション」というロジックをDB側に寄せられる強力な機能です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?