今、俺たちはまるでシークレットエージェントみたいなもんだよね ― 関数やプロシージャがミッションをこなしてる。データを処理したり、計算したり、データベースの中でちょっとしたマジックをやってる。でも、もし何かうまくいかなかったらどうやって気づく?どのタイミングで「データのマッサージバス」が壊れたか、どうやって分かる?そこで登場するのがロギングとエラー処理だよ。
覚えてる?もうPostgreSQLやPL/pgSQLでどうやって「会話」したりログを取ったりできるか、ちょっと触れたよね:
RAISE NOTICE: 落ち着いたフレンドリーなトーン ― 「おい、ここは大丈夫だけど、ちょっと見てみてもいいかもよ」。RAISE WARNING: ちょっと声が大きくなる ― 「あれ、なんか変かも。ちょっと覗いてみて」。RAISE EXCEPTION: パニックサイレン ― 「ストップ!アルゴリズムがピンチ!全部ぶっ壊れる前に止めたよ」。
それぞれのレベルにはちゃんと役割があるから、どれを使うかちゃんと選ぶのが大事だよ。
コードの中でこうやって使う:
DO $$
BEGIN
-- NOTICEレベル(全部OK、ただのお知らせ)
RAISE NOTICE 'ただのお知らせ:データ処理が始まったよ';
-- WARNINGレベル(なんか怪しい)
RAISE WARNING '警告:カラムのデータフォーマットが正しくないかも';
-- EXCEPTIONレベル(クリティカルエラー)
RAISE EXCEPTION 'エラー:入力値が不正だよ!';
END $$;
使い分けのタイミング:
RAISE NOTICE― デバッグや落ち着いた情報出力用。RAISE WARNING― データが怪しい時の警告用。RAISE EXCEPTION― クリティカルなエラーで関数の実行を止めたい時。
RAISE EXCEPTIONでのエラー処理
RAISE EXCEPTIONは君の非常停止ボタン。何かおかしい時に関数の実行を止めてエラーを伝えられる。
基本の使い方はこんな感じ:
RAISE EXCEPTION 'エラーメッセージをここに書く';
でも、もっと分かりやすくしたいなら変数も使える:
DECLARE
input_value INTEGER;
BEGIN
input_value := NULL;
IF input_value IS NULL THEN
RAISE EXCEPTION 'エラー:入力値がNULLだよ。INTEGERが欲しかった';
END IF;
END;
メッセージのフォーマット
変数をメッセージの中に直接入れることもできる:
DECLARE
var1 TEXT := 'データ';
var2 INTEGER := 42;
BEGIN
RAISE EXCEPTION '処理中にエラー:%、ID %', var1, var2;
END;
出力例:処理中にエラー:データ、ID 42。
例:データのバリデーション
例えば、年齢を受け取るプロシージャがあるとする。年齢がマイナスなら、エラーを出すのが普通だよね:
CREATE OR REPLACE FUNCTION validate_age(age INTEGER)
RETURNS VOID AS $$
BEGIN
IF age < 0 THEN
RAISE EXCEPTION '年齢はマイナスにできないよ:%', age;
END IF;
END;
$$ LANGUAGE plpgsql;
-- 関数の呼び出し
SELECT validate_age(-5); -- エラーになる
RAISE NOTICEでのお知らせ
RAISE EXCEPTIONがサイレンなら、RAISE NOTICEは肩をポンと叩く感じ。関数の中で何が起きてるかコメントを入れるのに使える。
RAISE NOTICEを使うタイミング:
- デバッグ情報の出力(例えば変数の今の値とか)。
- 処理のステップ開始や計算結果のお知らせ。
例:インフォメーションメッセージ
CREATE OR REPLACE FUNCTION calculate_discount(price NUMERIC, discount_rate NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
final_price NUMERIC;
BEGIN
RAISE NOTICE '割引前の価格:%', price;
RAISE NOTICE '割引率:%', discount_rate;
final_price := price - (price * discount_rate);
RAISE NOTICE '最終価格:%', final_price;
RETURN final_price;
END;
$$ LANGUAGE plpgsql;
-- 関数の呼び出し
SELECT calculate_discount(100, 0.2);
-- 出力例:
-- NOTICE: 割引前の価格:100
-- NOTICE: 割引率:0.2
-- NOTICE: 最終価格:80
実践例:計画とロギング
例えば、複雑なデータ処理プロシージャがあって、今どのステップか知りたいとき:
CREATE OR REPLACE FUNCTION process_data_step_by_step()
RETURNS VOID AS $$
BEGIN
RAISE NOTICE 'ステップ1:データ準備';
-- ここに最初のステップのロジック
RAISE NOTICE 'ステップ2:データバリデーション';
-- ここに2番目のステップのロジック
RAISE NOTICE 'ステップ3:データ保存';
-- ここに3番目のステップのロジック
END;
$$ LANGUAGE plpgsql;
-- 関数の呼び出し
SELECT process_data_step_by_step();
-- ログにステップごとの実行が表示される
もう一つ例を出すね。 例えば、あるショップが一定金額以上の注文だけ割引を提供する場合:
CREATE OR REPLACE FUNCTION apply_discount(order_amount NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
IF order_amount < 50 THEN
RAISE EXCEPTION 'エラー:注文金額は50以上じゃないとダメ、今の金額:%', order_amount;
END IF;
RETURN order_amount * 0.9; -- 10%割引を適用
END;
$$ LANGUAGE plpgsql;
-- 関数の呼び出し
SELECT apply_discount(30); -- エラー:注文金額は50以上じゃないとダメ
よくあるミス
ミス1: パラメータなしでメッセージをロギングする。
大きなプロシージャだと、これじゃ何が起きたか分かりにくい:
RAISE NOTICE 'エラーが発生した'; -- なんで?どこで?どうやって?
アドバイス: いつもコンテキストを追加しよう:
RAISE NOTICE 'process_data()関数でエラー:入力値:%', input_value;
ミス2: RAISE EXCEPTIONをRAISE WARNINGで十分な場面で使っちゃう。
例外を乱用すると、ちょっとしたことでコードが止まってデータ処理が大変になるよ。
コツ: ロギングレベルは意識して使い分けよう。デバッグにはNOTICE、クリティカルな時はEXCEPTION。
ミス3: ロギング自体を全くしない。
これは暗い部屋で鍵を探すようなもん。ログがないと複雑な処理のデバッグはほぼ無理ゲー。
コツ: 関数が大きくて複雑な時は特に、重要なステップでRAISE NOTICEを入れよう。
GO TO FULL VERSION