ちょっと前のレベルで、PostgreSQLのプロシージャと関数について話したよね。今回はもっと深く掘り下げていこう。
関数とプロシージャは独立して動かすこともできるけど、たいていはその連携がシステム全体の成功を左右するんだ。一番便利なのは、関数を他の関数から呼び出してデータを渡したり、実行結果を受け取ったりできることだよ。
関数 vs プロシージャ:何が違うの?
PostgreSQLで関数とプロシージャがどう違うか、思い出してみよう:
関数(
FUNCTION):- 値を返す。
SELECTで使える。- 計算やデータ変換によく使われる。
プロシージャ(
PROCEDURE):- 直接値を返さない。
- データの挿入、更新、削除みたいな操作に使う。
CALLコマンドで呼び出す。
関数間のデータの受け渡し
実践に進もう。まずは関数とプロシージャ間でデータを渡す基本的な例から。基本的に、関数間のデータ受け渡しはパラメータや返り値を使ってやるよ。
関数の中で別の関数を呼ぶのはこんな感じ:
CREATE OR REPLACE FUNCTION get_student_name(student_id INT)
RETURNS TEXT AS $$
DECLARE
student_name TEXT;
BEGIN
-- 学生IDで名前を取得する
SELECT name INTO student_name FROM students WHERE id = student_id;
-- 名前を返す
RETURN student_name;
END;
$$ LANGUAGE plpgsql;
この関数は他の関数から呼び出せる:
CREATE OR REPLACE FUNCTION welcome_student(student_id INT)
RETURNS TEXT AS $$
DECLARE
message TEXT;
BEGIN
-- 他の関数で学生の名前を取得
message := 'ようこそ, ' || get_student_name(student_id) || '!';
-- 挨拶メッセージを返す
RETURN message;
END;
$$ LANGUAGE plpgsql;
get_student_name関数は、学生のID(student_id)から名前を返す。- もう一つの関数
welcome_studentでは、その名前を使って挨拶メッセージを作ってる。
メモ: SELECT INTOでデータを取り出すと、PL/pgSQLの変数にクエリ結果が入るよ。
関数からプロシージャを呼ぶ例
今度は、関数からプロシージャを呼び出す方法を見てみよう。例えば、学生がシステムにログインした時間を記録するプロシージャがあるとする:
CREATE OR REPLACE PROCEDURE log_student_entry(student_id INT)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO log_entries(student_id, entry_time)
VALUES (student_id, NOW());
END;
$$;
このプロシージャを関数から呼び出して、ログインを記録しつつメッセージを返す:
CREATE OR REPLACE FUNCTION student_login(student_id INT)
RETURNS TEXT AS $$
BEGIN
-- ログ記録用プロシージャを呼ぶ
CALL log_student_entry(student_id);
-- メッセージを返す
RETURN '学生のログインが正常に記録されたよ。';
END;
$$ LANGUAGE plpgsql;
実践的な相互作用の例
例1:注文の合計金額計算とログ記録
オンライン注文システムを扱っているとしよう。注文の合計金額を計算する関数がある:
CREATE OR REPLACE FUNCTION calculate_order_total(order_id INT)
RETURNS NUMERIC AS $$
DECLARE
total NUMERIC;
BEGIN
-- 注文の全アイテムを合計
SELECT SUM(price * quantity) INTO total
FROM order_items
WHERE order_id = order_id;
RETURN total;
END;
$$ LANGUAGE plpgsql;
注文の合計金額を保存するプロシージャ:
CREATE OR REPLACE PROCEDURE log_order_total(order_id INT, total NUMERIC)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO order_totals(order_id, total)
VALUES (order_id, total);
END;
$$;
これらを組み合わせてみよう:
CREATE OR REPLACE FUNCTION process_order(order_id INT)
RETURNS TEXT AS $$
DECLARE
total NUMERIC;
BEGIN
-- 合計金額を計算する関数を呼ぶ
total := calculate_order_total(order_id);
-- プロシージャで合計金額を記録
CALL log_order_total(order_id, total);
RETURN '注文が正常に処理されたよ。';
END;
$$ LANGUAGE plpgsql;
例2:学生の最高評価を取得してプロフィールを更新
最高評価を取得する関数:
CREATE OR REPLACE FUNCTION get_highest_rating(student_id INT)
RETURNS INT AS $$
DECLARE
max_rating INT;
BEGIN
-- 学生の最高評価を探す
SELECT MAX(rating) INTO max_rating
FROM ratings
WHERE student_id = student_id;
RETURN max_rating;
END;
$$ LANGUAGE plpgsql;
学生プロフィールを更新するプロシージャ:
CREATE OR REPLACE PROCEDURE update_student_profile(student_id INT, max_rating INT)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE students
SET highest_rating = max_rating
WHERE id = student_id;
END;
$$;
これらを呼び出す関数:
CREATE OR REPLACE FUNCTION refresh_student_profile(student_id INT)
RETURNS TEXT AS $$
DECLARE
max_rating INT;
BEGIN
-- 最高評価を取得
max_rating := get_highest_rating(student_id);
-- 学生プロフィールを更新
CALL update_student_profile(student_id, max_rating);
RETURN 'プロフィールが正常に更新されたよ。';
END;
$$ LANGUAGE plpgsql;
相互作用でよくあるミス
一番ありがちなミスは、関数とプロシージャ間でデータ型が合ってないこと。例えば、プロシージャがNUMERIC型を期待してるのにINTEGERを渡すと、PostgreSQLは型不一致のエラーを出す。データ型がちゃんと合ってるか、いつも確認しよう。
もう一つのミスは、関数Aが関数Bを呼び、BがまたAを呼ぶみたいな循環呼び出し。これやると無限ループになってシステムが落ちるよ。
実用的な意味
なんでこんな相互作用が必要なの? 実際には、関数とプロシージャは複雑なシステムの「ブロック」みたいなもの。コードを独立したパーツに分けられるから、デバッグや再利用、テストがめっちゃ楽になる。例えば:
- 面接で「複雑な操作を実行するためにプロシージャを呼ぶ関数を書いて」と言われることがある。こういう実践的なスキルを見せられるとポイント高いよ。
- ネットショップやログシステム、CRMみたいな本番アプリを作るとき、関数とプロシージャの連携をうまく設計できるとコードがすごくシンプルになる。
関数とプロシージャの連携をもっと深く知りたいなら、PL/pgSQLの公式ドキュメントも見てみてね。
GO TO FULL VERSION