「ストアドプロシージャ」って聞くと、なんだか技術者っぽくて難しそうな響きですよね。
最初にこの言葉を聞いたとき、「え、何それ?SQLを普通に書くのと何が違うの?」と思った人もいるはず。
実は、私も最初はそんな感じでした。
でも、少しずつ学んでいくうちに、「これ、めちゃくちゃ便利じゃん!」 と思うようになりました。
そして、いざ使いこなせるようになると… なんかちょっとカッコよくないですか?
そう、ストアドプロシージャは「データベースの秘密兵器」なんです。
SQLを毎回書かなくても、あらかじめ決めた処理をサクッと呼び出せるし、データベースの負荷も減らせる。
そして何より、「毎回同じ処理を書くのが面倒すぎる…」 という悩みを解決してくれる救世主!
この記事では、そんなストアドプロシージャの基本の書き方から、実際に業務で使える応用テクニックまで、「ゼロからちゃんと理解できる」 ように解説していきます。
SQL初心者でも 「お、ストアドプロシージャ使えたぞ!」 ってなるように、分かりやすく説明するので安心してくださいね。
では、一緒にストアドプロシージャの世界に飛び込んでみましょう!
はじめに:ストアドプロシージャって何それ、美味しいの?
「ストアドプロシージャ」って初めて聞いたとき、なんとなく響きはカッコいいけど、よくわからない…そんなふうに思いませんでしたか?
私は正直、「名前が長いし、しかも“プロシージャ”?え、治療法の話?」と完全に構えてしまった記憶があります。
でも、実際に業務で触れ始めると、「あれ?これ便利じゃん」→「おお、めっちゃ楽できるぞ」→「え、これもっと早く知りたかった…!」 という三段活用みたいな感情変化が起こりました(笑)
ストアドプロシージャとは、簡単に言えば 「データベースに覚えさせておく処理のレシピ」 みたいなものです。
よく使うSQLの手順をあらかじめ登録しておいて、あとから呼び出せるようにする仕組みですね。
お弁当の作り方を手順付きでメモしておいて、毎朝それ通りにサクッと作れるようになる、あの感じです。
特に業務で「同じようなSQL処理を何回も実行してるなぁ…」と感じたことがある人にとっては、ストアドプロシージャは救世主のような存在です。
一度作ってしまえば、再利用できるし、保守しやすいし、何より「わざわざアプリ側から長いSQL書かなくていい」という快適さ。もう、戻れません。
この章では、そんな“ちょっととっつきにくそう”に見えるストアドプロシージャを、初心者でもスッと理解できるように、できるだけ噛み砕いて紹介していきます。
記事を読み終わるころには、「ストアドプロシージャ、使えるとちょっとかっこいいな」と思えるようになるはずです。
それでは、まずは基本のキから一緒に学んでいきましょう!
ストアドプロシージャの基本
「ストアドプロシージャって便利らしいけど…結局どう書くの?」という人のために、この章では“書き方の基本のキ”を一緒に見ていきましょう!
そもそもどうやって作るの?
まずは、PostgreSQLでストアドプロシージャを書くには CREATE PROCEDURE 文を使います。
たとえば、こんな感じ:
CREATE PROCEDURE hello_world()
LANGUAGE plpgsql
AS $$
BEGIN
RAISE NOTICE 'Hello, world!';
END;
$$;
これは何をしているかというと、”Hello, world!” という文字列を表示するだけの、超シンプルなプロシージャです(定番の“こんにちは、世界”ですね)。
最初はこのくらい小さく始めて、徐々にパラメータを増やしたり、SQLを書き足したりしていけばOKです。
パラメータを使ってみよう
ストアドプロシージャの強みのひとつが「呼び出すたびに値を変えられる」こと。
つまり、「昨日のユーザーだけ」「2024年のデータだけ」といった動的な処理ができるんです。
たとえば、こんな感じ:
CREATE PROCEDURE get_user_by_id(IN uid INT)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT * FROM users WHERE id = uid;
END;
$$;
IN uid INT の部分で「呼び出すときにユーザーIDを渡してくださいね」という意味になります。
実行するときはこんな感じにします:
CALL get_user_by_id(3);
この書き方を覚えると、まるでSQLに“関数”が生えたような気持ちになりますね。
ちょっとプログラマっぽくてテンション上がります。
条件分岐とループもできる!
「ストアドプロシージャって、SELECTしかできないの?」と思われがちですが、実はif文やループも書けちゃいます。
つまり、ちょっとした業務ロジックならSQLだけで完結できる!
IF uid IS NULL THEN
RAISE NOTICE 'User ID is missing';
ELSE
SELECT * FROM users WHERE id = uid;
END IF;
とか、
FOR i IN 1..10 LOOP
RAISE NOTICE 'Count: %', i;
END LOOP;
みたいな処理もできます。
SQLのなかでこんなに柔軟なロジックが書けると、いちいちアプリ側に処理を渡さなくて済むようになるので、結果的に効率もアップします。
ストアドファンクションとの違いって?
ここで「procedure じゃなくて function も見たことあるんだけど…」と気づいた方、鋭いです!
実際、PostgreSQLでは ストアドプロシージャ(procedure) と ストアドファンクション(function) の両方が使えます。
ざっくり違いを言うと:
分類 | returnあり? | 呼び出し方 | トランザクション制御 |
---|---|---|---|
procedure | なし | CALL | 可能 |
function | あり | SELECT | 不可(関数内でCOMMITなどできない) |
処理の複雑さや使いどころによって使い分けましょう!
この章では、ストアドプロシージャの「見た目」や「基本の構文」、「できることの幅広さ」をカジュアルに押さえてみました。
次の章では、実際に業務で役立ちそうなプロシージャを一緒に書いてみましょう!
実際にストアドプロシージャを作成してみよう
さて、基本構文やできることが分かったところで、いよいよ実践パートです。
「読むだけじゃわからん!書いてみたいんだ!」というあなたの気持ち、わかります。
ということで、ここではよくある業務シーンを例に、実際にストアドプロシージャを書いてみましょう。
ユーザー一覧を取得する、シンプルな処理から始めよう
まずは「users」テーブルからすべてのユーザー情報を取得する、超基本的なプロシージャを作ってみます。
CREATE PROCEDURE get_all_users()
LANGUAGE plpgsql
AS $$
BEGIN
SELECT * FROM users;
END;
$$;
実行は CALL get_all_users(); です。…と言いたいところですが、PostgreSQLのストアドプロシージャでは SELECT の結果はクライアント側に返ってきません。
「SELECTの結果を返したいなら、function(関数)を使いましょう」というやや意地悪なルールがあります。
なので、INSERTやUPDATE、複雑な処理系をストアドプロシージャで使い、データ取得はfunctionを選ぶと整理しやすいです。
データを更新してみよう:ユーザー名を変更する例
それでは、ストアドプロシージャらしく「データを直接変更する処理」を作ってみましょう。
ここでは、ユーザーIDを指定して、そのユーザーの名前を変更するプロシージャです。
CREATE PROCEDURE update_user_name(IN uid INT, IN new_name TEXT)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE users SET name = new_name WHERE id = uid;
END;
$$;
実行はこんな感じ:
CALL update_user_name(2, '山田太郎');
このように、アプリケーション側でいちいちUPDATE文を組み立てなくても、CALL 一発で処理ができるようになるのです。カッコいい!
トランザクションを扱ってみよう:失敗したらロールバック!
複数の処理をまとめて行い、途中でエラーがあれば全部取り消す。
これを可能にするのがトランザクションです。そしてストアドプロシージャでは、この制御も可能です。
CREATE PROCEDURE transfer_points(IN from_id INT, IN to_id INT, IN amount INT)
LANGUAGE plpgsql
AS $$
BEGIN
-- 残高から減らす
UPDATE users SET points = points - amount WHERE id = from_id;
-- 残高に加算
UPDATE users SET points = points + amount WHERE id = to_id;
-- 残高不足だった場合は例外処理(本当はもっと厳密にチェックすべし)
END;
$$;
実際の業務では、途中で処理が失敗しても「片方だけ更新されちゃった…」みたいな悲劇を防ぐため、トランザクション制御が欠かせません。
プロシージャ内で明示的に BEGIN, COMMIT, ROLLBACK を使いたい場合は CALL の外側で制御するか、複数のプロシージャに分ける手もあります。
あなたの業務に合わせてカスタマイズ!
この章で紹介したのは、あくまで一例です。あなたの業務で「毎回同じSQL打ってる…」という処理があれば、それはストアドプロシージャに変えるチャンス!
最初は「使いどころがわからない」と思うかもしれませんが、慣れてくると「これも、あれもプロシージャ化したい!」とウズウズしてくるはず!
次の章では、もう一歩進んで「応用テクニック」に挑戦してみましょう! プロシージャにロジックを詰め込むと、データベースってこんなにパワフルなのか…と感動するかもしれません。
ストアドプロシージャの応用テクニック
ここまで読んで、「なんだ、ストアドプロシージャってそんなに難しくないじゃん」と思い始めていたら、こっちのものです。
さあ、ここからはワンランク上の技を手に入れて、“ちょっとできる人感”をまといにいきましょう。
動的SQLで柔軟なクエリを実行しよう
状況によって実行したいSQLが変わること、ありますよね? たとえば、テーブル名や条件が可変な場合など。
そんなときに使えるのが 動的SQL(Dynamic SQL)!
CREATE PROCEDURE search_by_column(IN col_name TEXT, IN val TEXT)
LANGUAGE plpgsql
AS $$
BEGIN
EXECUTE format('SELECT * FROM users WHERE %I = %L', col_name, val);
END;
$$;
ここでは、EXECUTE format() を使ってSQL文を組み立てています。%I や %L は識別子や文字列をエスケープしてくれる安全なフォーマットです。
いわば、SQL版のテンプレートエンジン的存在。ちょっとスマートでしょ?
エラーハンドリングで安心設計を
ストアドプロシージャにも try-catch 的な仕組みがあります。それが EXCEPTION 句!
CREATE PROCEDURE safe_update_points(IN uid INT, IN points INT)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE users SET points = points + points WHERE id = uid;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE '更新に失敗しました。ユーザーID: %, ポイント: %', uid, points;
END;
$$;
エラーを未然に防ぐのも大事だけど、「起きたときに慌てない」 のがプロのたしなみ。
ログにメッセージを残して、後から見返せるようにするのもアリです。
複雑なビジネスロジックもおまかせ!
条件分岐、ループ、例外処理…ここまでくれば、業務でよくあるバッチ処理や定期レポート生成も書けるようになってきます。
たとえば:
- ポイントが0未満のユーザーにメール通知
- 一定期間ログインしていないユーザーを非アクティブに
- データを月単位にアーカイブしてパーティションに振り分け
などなど、「え、それアプリ側じゃなくてDBでやっちゃうの?」 という処理を押し込めるのがストアドプロシージャの真骨頂。
しかも、サーバー間の通信回数を減らせるので、スピード感がまるで違う。
スケジューラーとの組み合わせで自動化
PostgreSQL単体ではスケジューリング機能はないのですが、cron + CALL や pgAgent などと組み合わせることで、自動バッチ処理も可能になります。
「毎日深夜2時に月額料金の更新処理を走らせる」なんてのも、ストアドプロシージャ + cron で簡単に実現。
「自分の手を動かさずに処理が終わってる」 って…ちょっとニヤけちゃうやつですよね。
この章では、ストアドプロシージャの“便利ワザ”をいくつか紹介しました。
慣れてきたら「SQLでここまでできるのか…」と感動すら覚えるはず。
次の章では、パフォーマンスを意識した書き方や、処理の高速化について見ていきましょう!
ストアドプロシージャのパフォーマンス最適化
ここまで来たあなたは、すでにストアドプロシージャでちょっとした処理は書けるはず。
でも、「動く」だけで満足してしまうのはもったいない。
せっかくなら 「速くて軽くてスマートな処理」 を目指して、ワンランク上のストアドプロシージャを目指しましょう!
インデックスを味方につける
データベースにおける高速化の王道、それがインデックス。
WHERE句で特定のカラムを使ってデータを絞り込む処理が多い場合、該当カラムにインデックスがあるだけで、体感速度が劇的に変わります。
Before(ノーインデックス)
SELECT * FROM users WHERE email = 'taro@example.com';
-- → 数万件スキャンされて遅い!
After(インデックス追加)
CREATE INDEX idx_users_email ON users(email);
-- → ピンポイントに検索できて速い!
ストアドプロシージャでよく呼ばれる処理こそ、インデックスとセットで考えるのが鉄則です。
不要な処理を避ける:考えるな、疑え
たとえば、条件によっては実行しなくてもいいUPDATEやDELETEを無意識に書いていませんか?
それ、たった1行でもテーブル全体をロックしてしまうことも。
必要かどうか、ちゃんと“疑う”コードを書きましょう。
IF EXISTS (SELECT 1 FROM users WHERE id = uid AND name != new_name) THEN
UPDATE users SET name = new_name WHERE id = uid;
END IF;
この小さなIF文ひとつで、無駄な更新を回避できます。こういうのが“できるプロっぽさ”です。
複数行処理は1回でまとめる:ループよりSQLを信じよう
ストアドプロシージャでは、ついループを使って行ごとにINSERTやUPDATEをしてしまいがち。
でも、RDBは「1回でまとめて処理するのが得意」な構造なので、できるだけセットで処理しましょう。
悪い例:1件ずつ処理するループ
FOR rec IN SELECT * FROM pending_users LOOP
UPDATE users SET status = 'active' WHERE id = rec.id;
END LOOP;
良い例:まとめて更新
UPDATE users
SET status = 'active'
WHERE id IN (SELECT id FROM pending_users);
速い・軽い・かっこいい三拍子そろってるのは、やっぱりこっち。
実行計画を見て改善点を探す:EXPLAIN ANALYZEのススメ
ストアドプロシージャの中身が重たいと感じたら、EXPLAIN ANALYZE を使って実行計画を確認してみましょう。
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'taro@example.com';
これで、どの処理に時間がかかっているのかが“見える化”されます。
ボトルネックを正しく見つけられれば、改善ポイントもハッキリしてきます。
メンテナンス性もパフォーマンスのうち
高速に動くストアドプロシージャを書いたとしても、後から読んだ自分が「これ何やってるんだっけ…」となっては台無し。
読みやすい変数名、適度なコメント、処理の分割(長くなりすぎたら別プロシージャ化)など、“未来の自分”に優しい設計が、結果として長く速く使われるコードにつながります。
この章では、「とりあえず動けばいい」から一歩進んで、「賢く動くプロシージャ」を目指すためのポイントを紹介しました。
ここまで来たら、あなたもストアドプロシージャ使いとしてかなり頼もしい存在です!
次章では、実際にハマりがちなエラーとその対処法を紹介していきます。
ちょっとしたつまずきも、あらかじめ知っておけば怖くありませんよ。
よくあるトラブルとその解決法
ストアドプロシージャ、どんどん書けるようになってきたぞ!…と思った矢先に、 「え、エラー?なにこれ意味わかんない」 という冷や汗体験、ありますよね。
ここでは、ストアドプロシージャを書いているとよく出くわす「お決まりのハマりポイント」と、「焦らずこう見直そう」という対処法を紹介します!
procedure does not exist エラー
状況
呼び出したつもりのプロシージャが存在しないよって言われる定番のやつ。
CALL update_user_info(1, 'Yamada');
-- ERROR: procedure update_user_info(integer, text) does not exist
原因と対策
- 引数のデータ型が違う → INT vs BIGINT のような細かい型違いにも敏感
- スキーマを明示していない → CALL public.update_user_info(…) のように書くと解決することも
- 実はまだ作ってなかった or 作成エラーで失敗していた → \df や pg_proc を確認!
syntax error at or near エラー
状況
書いたはずのSQLが「文法的にダメ」と怒られるケース。
ERROR: syntax error at or near "BEGIN"
原因と対策
- $…$ で囲むのを忘れている
- セミコロン(;)の位置がおかしい
- LANGUAGE の指定漏れや、そもそも構文の順番が違う
対策: エディタのSQL構文チェック機能を使う、または最初はシンプルなプロシージャから試すのが◎です!
column does not exist/variable does not exist
状況
変数やカラム名をミスって、PostgreSQLに「そんなの知らん」と言われるパターン。
ERROR: column "newvalue" does not exist
原因と対策
- 変数名とSQLのカラム名が混在して混乱している
- := と = を使い分けられていない(PL/pgSQLでは代入は :=)
- 大文字・小文字の違い → PostgreSQLは大文字を自動的に小文字に変換するけど、”MyColumn”のようにダブルクオートで囲むと厳密に判別!
意図せずトランザクションが分断される
状況
プロシージャ内で COMMIT や ROLLBACK を試みるとエラーに。
ERROR: cannot begin/end transactions in a procedure
原因と対策
- PostgreSQLのfunctionではトランザクション制御ができません!procedureのほうを使いましょう!
- 複数のトランザクションが必要なら、クライアントアプリ側でトランザクションを管理するように分離する
「処理はされているけど、結果が見えない」問題
状況
SELECT を書いたはずなのに、結果が返ってこない…?
原因と対策
ストアドプロシージャ(procedure)ではSELECTの結果は返さない仕様です。 データを返したい場合は、function の利用がおすすめ!
デバッグしたい!けど方法がわからん
対策・コツまとめ
- RAISE NOTICE を使って処理の流れを出力しよう(ログに「ここまで来たよ!」って痕跡を残す)
- pgAdmin のデバッガ機能を使う(GUI派にはおすすめ!)
- 処理を少しずつ確認して動かしてみる「テスト→追加→テスト」方式が安心
エラーに出会うと焦る気持ち、めっちゃわかります。
でも慌てず、「何をしようとして何ができなかったのか」を丁寧に読み解くと、たいていはケアレスミスか仕様の理解不足だったりします。
「むしろ1回エラーを見たことがある人のほうが強くなる」ので、エラー経験は宝です。これからは一歩ずつ“バグとの付き合い方”も上達していきましょう!
まとめ:ストアドプロシージャ、使えるとやっぱりカッコいい
ここまで読んでくださったあなた、もう立派な「ストアドプロシージャ初心者卒業生」です。最初は「ストアド…なに?」と戸惑っていたとしても、今では CREATE PROCEDURE を自信を持って書けるはず!
この記事では、ストアドプロシージャの基本的な仕組みから、実践的な活用方法、そしてパフォーマンスやエラー対策まで、一通り網羅してきました。
おさらいとして、ポイントを振り返ってみましょう:
- 「ストアドプロシージャ」はSQL処理をデータベースに記憶させて呼び出せる便利な仕組み
- 複雑なロジックや繰り返し処理をまとめてカプセル化できる
- IN, OUT パラメータで柔軟に動作を制御できる
- 動的SQLやエラーハンドリング、トランザクションも使える“ちょっとしたプログラム”
- 読みやすく、効率的で、安全なコードを書くには、最適化と丁寧な設計が大切
使えば使うほど、「これはアプリではなくデータベース側で処理したほうがスマートだな」という感覚が身についてきますし、コードの整理にもつながってきます。
そして何より、「SQLだけでここまでできるの⁉︎」というちょっとした感動があったはず。
次に学ぶなら?
ここからさらに深掘りしたい!という方におすすめのステップもご紹介します。
- ストアドファンクションとの使い分け(データを返すならこちら)
- トリガーとの連携:自動実行の仕組みを学ぶとさらに世界が広がります
- セキュリティや権限管理:誰がどのプロシージャを実行できるかの制御
- パフォーマンスチューニングの実践:EXPLAIN ANALYZE や統計情報の活用
また、PostgreSQL特有の便利機能や、新しいバージョンの更新内容もウォッチしておくと、業務でもすぐ活かせる知識になりますよ。

最初はよく分からなかったものが、「あ、これ便利かも!」と思えたときのワクワク感。私自身も経験したその感覚を、この記事を通じて少しでも共有できていたら、これ以上ない喜びです。
ストアドプロシージャは、書けるようになると本当に仕事がラクになります。そして何より、「SQLって、けっこう奥深くて面白いな」と感じてもらえたら、それが一番のゴールです。
コメント