PL/pgSQL は ブロック化構造 言語です。すべて のキーワードと識別子は大文字と小文字を混ぜて使うことができます。 ブロックは次のように定義します。
[<<label>>] [DECLARE declarations] BEGIN statements END;
ブロックの文節には副ブロックを複数持つことができます。副ブロック は、そのブロックの外部の文から変数を隠蔽することに使用できます。
ブロックの前の宣言節で宣言された変数は、関数呼び出しの度ではなく、 そのブロックに処理が進むときに毎回そのデフォルト値に初期化されます。下記はその例です。
CREATE FUNCTION somefunc() RETURNS INTEGER AS ' DECLARE quantity INTEGER := 30; BEGIN RAISE NOTICE ''Quantity here is %'',quantity; -- Quantity here is 30 quantity := 50; -- -- Create a sub-block -- DECLARE quantity INTEGER := 80; BEGIN RAISE NOTICE ''Quantity here is %'',quantity; -- Quantity here is 80 END; RAISE NOTICE ''Quantity here is %'',quantity; -- Quantity here is 50 RETURN quantity; END; ' LANGUAGE 'plpgsql';
文をまとめるための PL/pgSQL 内の BEGIN/END とトランザクションを制 御するデータベースコマンドである BEGIN/END の意味を取り違えないよ うにしてください。関数とトリガプロシージャはトランザクションを始 めたり、コミットしたりできませんし、 Postgresは入れ子になったトランザクショ ンをもちません。
PL/pgSQLには2種類のコメントがあります。2重ダッシュ '--' はその文 の終りまでをコメントとするコメントの始まりです。 '/*' は次に '*/' が現れるまでのブロック型のコメントの始まりを示します。ブロック型 のコメントは入れ子にはできませんが、二重ダッシュのコメントをブロッ ク型のコメントの内側におくことはでき、また二重ダッシュをつかって、 コメントを区切る '/*' と '*/' を無効にすることもできます。
ブロックまたはそのブロックの副ブロックで使われるすべての変数、行 レコードはブロックの宣言節にて宣言されなければいけません。ただし FORのループにて整数値の範囲内を繰り返すループ変数は除きます。
PL/pgSQL の変数はINTEGER, VARCHAR ,CHAR. といった SQL のデータ型を持つことができます。 すべての変数はデフォルト値としてSQLの NULL 値 が使用されます。
変数宣言のいくつかの例です。
user_id INTEGER; quantity NUMBER(5); url VARCHAR;
宣言には以下の構文を使用します。
name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } value ];
CONSTANT をつけて宣言される変数の値は変更することはできません。 NOT NULL を指定した場合には、変数の値に NULL を割り当てるとラン タイムエラーとなります。すべての変数のデフォルト値が SQLの NULL なので、NOT NULL として宣言される すべての変数はデフォルト値を指定しておかなければいけません。
デフォルト値は関数が呼ばれる度に評価されます。 timestamp型の変数に'now'を割り当 てておくと、変数には関数がバイトコードにプリコンパイルされた時で はなくて実際に関数が呼び出された時間がわたります。
例
quantity INTEGER := 32; url varchar := ''http://mysite.com''; user_id CONSTANT INTEGER := 10;
関数への変数のわたり方は$1, $2 というように識別されます。(最大 16)いくつ かの例を示します。
CREATE FUNCTION sales_tax(REAL) RETURNS REAL AS ' DECLARE subtotal ALIAS FOR $1; BEGIN return subtotal * 0.06; END; ' LANGUAGE 'plpgsql'; CREATE FUNCTION instr(VARCHAR,INTEGER) RETURNS INTEGER AS ' DECLARE v_string ALIAS FOR $1; index ALIAS FOR $2; BEGIN -- Some computations here END; ' LANGUAGE 'plpgsql';
%TYPE と %ROWTYPE 属性を使用することで、 もう一つのデータベース項目と同じデータ型もしくは構成で変数を宣言 することができます。(たとえば、テーブルのフィールド)
%TYPE は変数のデータ型もしくはデータベースのカラ ムを提供します。データベース値をもつ変数を宣言する時にこれを 使用することができます。たとえば、usersテーブル というのがあって、user_idと名前のつけられたカラ ムがあるものとします。users テーブルと同じデータ型の変数を宣 言するには以下のようにします。
user_id users.user_id%TYPE;
%TYPE を使うことで参照しているテーブルのデータ型 を知っている必要があります、これは重要なことで、将来参照して いるもののデータ変更されても(たとえば、user_id の定義を REAL に変更する)関数定義を変更する必要がありません。
指定テーブルの構造をもった行の宣言です。テーブル はデータベース内の既存のテーブルまたはビューの 名前でなければいけません。行のフィールドはドット記述方法でア クセスされます。関数へのパラメータは複合型(完全なテーブルの行) でもかまいません。この場合、対応数識別子 $n は rowtype になり ますが、後述の ALIAS コマンドを使って別名をつけなければいけま せん。
テーブルの行のうち、ユーザ用の行のみがアクセス可能で、oid や その他のシステム用の属性にはアクセスできません。(なぜなら、 ビューから行を持ってくることもあり得るからです。) rowtype の フィールドはchar()などのデータ型のフィールドサイ ズまたは精度を継承します。
DECLARE users_rec users%ROWTYPE; user_id users%TYPE; BEGIN user_id := users_rec.user_id; ... create function cs_refresh_one_mv(integer) returns integer as ' DECLARE key ALIAS FOR $1; table_data cs_materialized_views%ROWTYPE; BEGIN SELECT INTO table_data * FROM cs_materialized_views WHERE sort_key=key; IF NOT FOUND THEN RAISE EXCEPTION ''View '' || key || '' not found''; RETURN 0; END IF; -- The mv_name column of cs_materialized_views stores view -- names. TRUNCATE TABLE table_data.mv_name; INSERT INTO table_data.mv_name || '' '' || table_data.mv_query; return 1; end; ' LANGUAGE 'plpgsql';
RENAME を使用すると変数、レコード、行の名前を変更することができ ます。これは、トリガプロシージャ内でNEWまたはOLDを別の名前で参照 すべき時に有益です。
構文と使用例です。
RENAME oldname TO newname; RENAME id TO user_id; RENAME this_var TO that_var;
PL/pgSQLで使われる式はすべてバックエンドエクゼキュータを使用して 処理されます。定数を含んだ式は実際には実行時に評価される必要があ る場合もあります。(たとえばtimestamp型の 'now')ので、PL/pgSQLパーサによってNULLキーワー ド以外の定数値を識別することはできません。全ての式は、内部的に次 の問い合わせをSPIマネージャを使って実行するこ とによって評価されます。
SELECT 式SPIマネージャを使用してください。式の中では、 変数の識別子の場所はパラメータに置き換えられ、パラメータ配列内の 変数の実際の値はエクゼキュータに渡されます。PL/pgSQL関数内で使わ れる全ての式は一回だけ準備され保存されます。それが起こるたびに、 問い合わせの構文解析が必要であるならば、このルールに対する唯一の 例外は EXECUTE 文です。
Postgres主パーサによって行われる型チェッ クは定数値の解釈にいくつかの副作用があります。詳しく言うと次の
CREATE FUNCTION logfunc1 (text) RETURNS timestamp AS ' DECLARE logtxt ALIAS FOR $1; BEGIN INSERT INTO logtable VALUES (logtxt, ''now''); RETURN ''now''; END; ' LANGUAGE 'plpgsql';と
CREATE FUNCTION logfunc2 (text) RETURNS timestamp AS ' DECLARE logtxt ALIAS FOR $1; curtime timestamp; BEGIN curtime := ''now''; INSERT INTO logtable VALUES (logtxt, curtime); RETURN curtime; END; ' LANGUAGE 'plpgsql';という2つの関数が行うことには違いがあります。 logfunc1()の場合、 Postgres 主パーサは、INSERT 用のプラン を準備する際に、logtableフィールドの対象フィールドが timestamp 型であるので 'now' とい う文字列はtimestamp型として解釈すべきであることを判 断します。従ってこの段階で定数を作成し、バックエンドの生存期間の 間のlogfunc1()の全ての呼び出しにおいて、この 数値が使用されることになります。いうまでもないことですが、これは プログラマが予定したものではありません。
logfunc2()の場合、 Postgres主パーサは、 'now'の型を何にすべきかを判断できませんので、 'now'という文字列をもったtext型を 返します。ローカル変数 curtime に代入する時に、PL/pgSQLインタプリ タは変換用にtext_out() と timestamp_in() 関数を呼び出して、この文字列 を timestap 型にキャストします。
このPostgres主パーサによってなされる型 チェックは、PL/pgSQLがほとんどできた後に実装されました。6.3と6.4 では違いがあり、SPIマネージャの計画準備機能を使用する全ての関数に 影響を与えます。上の方法でローカル変数を使うことが、PL/pgSQLでこ れらの値を正しく解釈させる、現時点で唯一の方法です。
式や文でレコードフィールドを使用する場合は、そのフィールドのデー タ型を関数呼び出しと式の間で変更すべきではありません。1つ以上のテー ブルのイベントを扱うトリガプロシージャを記述するときには気をつけ てください。
下で指定されたPL/pgSQLパーサによって理解されないものは全て、問い合 わせ内にしまわれ、実行するためのデータベースエンジンに送られます。 問い合わせの結果はデータを全く返しません。
変数や行/レコードフィールドへの値の代入は次のように記述されます。
identifier := expression;式の結果のデータ型が変数のデータ型と合わなかった場合、または、変 数が(char(20)といった)サイズ/精度を持つことが分かっていた場合、 PL/pgSQLバイトコードインタプリタが結果の型の出力関数と変数の型の 入力関数を使用することによって、結果の値は暗黙的にキャストされま す。型の入力関数によって生成される実行時のエラーの元になることに 注意してください。
user_id := 20; tax := subtotal * 0.06;
Postgresデータベース内に定義された全て の関数は値を返します。したがって、関数の通常の呼び出し方法は、 SELECT を実行することか(PL/pgSQL内部で SELECT を実行した結果であ る)代入を行うことです。
しかし、関数の結果には興味が無い場合もあります。この場合は PERFORM 文を使用しています。
PERFORM queryこの実行はSPI マネージャを通して SELECT 問い合わせ を実行し、その結果を捨てます。ローカル変数といった識別子はパラメー タに置換されます。
PERFORM create_mv(''cs_session_page_requests_mv'','' select session_id, page_id, count(*) as n_hits, sum(dwell_time) as dwell_time, count(dwell_time) as dwell_count from cs_fact_table group by session_id, page_id '');
PL/pgSQL関数を内部で動的問い合わせを行いたいということがあるかも しれません。もしくは他の関数を生成する関数をお持ちかもしれません。 PL/pgSQLは、EXECUTE文をこういう場合のために用意しています。
EXECUTE query-stringquery-stringにはデータ型が textの文字列を含む実行される queryを指定します。
動的な問い合わせを行うとき、PL/pgSQL ではシングルクォートをエスケー プしなければいけません。詳細な説明として、"オラクルの PL/SQL から の移植"という章で利用できるテーブルを参照すれば労力の節約になりま す。
PL/pgSQL では他の全ての問い合わせとは異なり、EXECUTE文によって queryを実行する場合は、サーバの生存期 間中一度だけ解釈、保存されるわけではありません。代わりに、 queryは文ば実行される時に準備されます。 query-stringは、変数テーブルとフィール ドに及ぼす働きを実行するために手続きの範囲内で動的につくられるこ とができます。
SELECT問い合わせのからの結果は EXECUTE によって捨てられます。そし て、SELECT INTO は EXECUTE 範囲内では現在サポートされません。そし て、結果を動的に作成された SELECT から抜き出す唯一の方法は後で述 べる FOR ... EXECUTE を使用することです。
例
EXECUTE ''UPDATE tbl SET '' || quote_ident(fieldname) || '' = '' || quote_literal(newvalue) || '' WHERE ...'';
この例は、quote_ident(TEXT) と quote_literal(TEXT)関数の使用方 法を示しています。フィールドとテーブル識別子を含んでいる変数は関 数quote_ident()を通らなければいけません。ダ イナミックな問い合わせ文字列のリテラル要素を含んでいる変数は quote_literal()を通らなければいけません。両 方ともシングルクォートもしくはダブルクォートで、そして、どんな埋 め込まれた特殊文字ででも囲まれる入力テキストを返すために適切なス テップをとっています。
大きな動的問い合わせと EXECUTE の例です。
CREATE FUNCTION cs_update_referrer_type_proc() RETURNS INTEGER AS ' DECLARE referrer_keys RECORD; -- Declare a generic record to be used in a FOR a_output varchar(4000); BEGIN a_output := ''CREATE FUNCTION cs_find_referrer_type(varchar,varchar,varchar) RETURNS varchar AS '''' DECLARE v_host ALIAS FOR $1; v_domain ALIAS FOR $2; v_url ALIAS FOR $3; ''; -- -- Notice how we scan through the results of a query in a FOR loop -- using the FOR <record> construct. -- FOR referrer_keys IN select * from cs_referrer_keys order by try_order LOOP a_output := a_output || '' if v_'' || referrer_keys.kind || '' like '''''''''' || referrer_keys.key_string || '''''''''' then return '''''' || referrer_keys.referrer_type || ''''''; end if;''; END LOOP; a_output := a_output || '' return null; end; '''' language ''''plpgsql'''';''; -- This works because we are not substituting any variables -- Otherwise it would fail. Look at PERFORM for another way to run functions EXECUTE a_output; end; ' LANGUAGE 'plpgsql';
GET DIAGNOSTICS variable = item [ , ... ]
このコマンドはシステムステータスインディケータの検索を許可します。
それぞれのitemは、指定された変数(それ
はそれを受信するために正しいデータ型でなければいけません)に割り当
てられる状態値識別しているキーワードです。現在利用できるステータ
ス項目はROW_COUNTです。そして、最後の
SQL問い合わせによって処理される行の番号が
SQLエンジンに送られます。そして、
RESULT_OIDが、最も最近のSQL問い合
わせによって挿入される最後の行の OID です。RESULT_OID
が INSERT 問い合わせの後にだけ役に立つという点に注意してください。
RETURN 式関数は終了し、式の値は上位のエクゼキュー タに返されます。関数の戻り値を未定義にすることはできません。制御 が RETURN 文に到達せず関数の最上位ブロックの終わりに達した場合、 実行時エラーが発生します。
式の結果は自動的に、代入用に記述された関数の戻り値の型にキャスト されます。
制御構造はおそらく PL/SQL の有益な(重要な)部分です。PL/pgSQL の制 御構造で、あなたは非常に柔軟で強力な方法で PostgreSQLのデータを処理することができます。
IF文で、特定の条件によって行動を起こすもので す。PL/pgSQLには3つの IF の形式があります。IF-THEN, IF-THEN-ELSE, IF-THEN-ELSE IF. 注: 全ての PL/pgSQL で IF 文は対応 するEND IF文を必要とします。ELSE-IF文では2 つ必要です。1つは最初の IF でもう1つは2番目(ELSE IF)です。
IF-THEN 文は最も単純な IF の形式です。この条件が真ならば、 THEN と END IF の間の文が実行されます。偽の場合は、 END IF に続いて いる文が実行されます。
IF v_user_id <> 0 THEN UPDATE users SET email = v_email WHERE user_id = v_user_id; END IF;
IF-THEN-ELSE文は IF-THEN に、条件式に評価して FALSE だった場合 に実行すべき文を追加で指定します。
IF parentid IS NULL or parentid = '''' THEN return fullname; ELSE return hp_true_filename(parentid) || ''/'' || fullname; END IF; IF v_count > 0 THEN INSERT INTO users_count(count) VALUES(v_count); return ''t''; ELSE return ''f''; END IF;
IF 文は次の例のようにネストさせることができます。
IF demo_row.sex = ''m'' THEN pretty_sex := ''man''; ELSE IF demo_row.sex = ''f'' THEN pretty_sex := ''woman''; END IF; END IF;
"ELSE-IF"文を使用するとき、 実際には ELSE 文の中に IF 文を入れ 子にしていきます。このため、 END IF 文が、入れ子になった IF 文 それぞれにつき1つ、上位の IF-ELSE 用に1つ必要になります。
例
IF demo_row.sex = ''m'' THEN pretty_sex := ''man''; ELSE IF demo_row.sex = ''f'' THEN pretty_sex := ''woman''; END IF; END IF;
LOOP、WHILE、FORとEXIT文で、PL/pgSQLプログラムの実行の流れを制 御することができます。
[<<label>>]
LOOP
statements
END LOOP;
無条件のループはEXIT文により明示的に終了されなければいけません。
入れ子になったループのある特定の入れ子を終了させるために、EXIT文では、
任意のラベルを使用して指定したレベルの入れ子を終了させることができます。
EXIT [ label ] [ WHEN expression ];labelが指定されていない場合は、最も内側にあるループを終了させ、 END LOOPの後にある文を実行します。labelが指定されている場合、 そのラベルは現在の、または、入れ子になったループブロックの上位レベルのラベ ル名である必要があります。その後、その名前のループまたはブロックは終了 され、制御はそのループ/ブロックに対応する END に後の文に移動し ます。
例
LOOP -- some computations IF count > 0 THEN EXIT; -- exit loop END IF; END LOOP; LOOP -- some computations EXIT WHEN count > 0; END LOOP; BEGIN -- some computations IF stocks > 100000 THEN EXIT; -- illegal. Can't use EXIT outside of a LOOP END IF; END;
WHILE 文で条件式の評価が真になる限り、文のシーケンスを通してルー プすることができます。
[<<label>>]
WHILE expression LOOP
statements
END LOOP;
例
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP -- some computations here END LOOP; WHILE NOT boolean_expression LOOP -- some computations here END LOOP;
[<<label>>] FOR name IN [ REVERSE ] expression .. expression LOOP statements END LOOP;ループは整数値の範囲を繰り返すものです。変数の nameは integer 型でループ内だけに存 在するものとして自動的に作成されます。範囲の下限と上限を与える 2つの式はループに入る時だけ評価されます。繰り返しステップは常 に1です。
FOR ループの例を紹介します。(FOR ループがレコードについて繰り 返しを行う例はSection 24.2.7を参 照してください)
FOR i IN 1..10 LOOP -- some expressions here RAISE NOTICE 'i is %',i; END LOOP; FOR i IN REVERSE 1..10 LOOP -- some expressions here END LOOP;
レコードはrowtypeと似ていますが、構造を事前に定義しません。 SELECT操作で得た実際のデータベースの行を保持するための選択と FOR ループでつかわれます。
RECORD 型の1つの変数は、様々な選択に使うことができます。その中の 行が終わる物が無いときにはランタイムエラーが起こるので、レコード にアクセスする か 1つのレコードフィールドに1つの値を割り当てる ことを試みてください。このように宣言することができます。
name RECORD;
SELECT 結果をすべてレコードや行に代入するには次のようにします。
SELECT INTO target expressions FROM ...;target部分には、レコード、行変数、 カンマで区切られた変数のリストとレコード/行列が入ることができます。これは 通常のPostgresで実装されているSELECT INTOとは全く異なることに注意してください。 また、これはINTOのターゲットに指定されるものは新しいテーブルを作成することを意味します。 (PL/pgSQL関数内でSELECTの結果から新しいテーブルを作成したい場合には、同等の シンタックスであるCREATE TABLE AS SELECTを使用してください。)
行、または変数のリストをターゲットとして使用する場合には、SELECTの結果の 値はターゲットの構造と完全に一致する必要があり、一致しない場合には ランタイムエラーが起こります。FROMキーワードの後にあらゆる有効な修飾や グループ分け、ソートなどを指定することができ、これらはSELECT文に 引き渡すことができます。
一旦レコード、または行がRECORD変数に代入されたら、"."(ドット)表記を使用して そのレコードのフィールドにアクセスすることができます。
DECLARE users_rec RECORD; full_name varchar; BEGIN SELECT INTO users_rec * FROM users WHERE user_id=3; full_name := users_rec.first_name || '' '' || users_rec.last_name;
FOUNDという特殊変数があります。これはSELECT INTOが実行されたら 直ちに使用可能で、SELECT INTO文が成功したか否かの確認を行い boolean 型を返します。
SELECT INTO myrec * FROM EMP WHERE empname = myname; IF NOT FOUND THEN RAISE EXCEPTION ''employee % not found'', myname; END IF;RECORD/ROWがNULLであるかどうかを検査するためにIS NULL(もしくはISNULL) 条件文を使うこともできます。結果が複数行を返すならば、最初の行 だけがターゲットフィールドに移動されます。それ以外は削除されます。
DECLARE users_rec RECORD; full_name varchar; BEGIN SELECT INTO users_rec * FROM users WHERE user_id=3; IF users_rec.homepage IS NULL THEN -- user entered no homepage, return "http://" return ''http://''; END IF; END;
FORループの特別な型を使用することにより、問い合わせの結果を繰り返し取り出し、 それに応じてそのデータを処理することができます。構文は以下の通りです。
[<<label>>]
FOR record | row IN select_clause LOOP
statements
END LOOP;
レコードにSELECT文の結果すべてが代入されてループの本体がそれぞ
れのレコードまたは行のために実行されます。以下は例です。
create function cs_refresh_mviews () returns integer as ' DECLARE mviews RECORD; -- Instead, if you did: -- mviews cs_materialized_views%ROWTYPE; -- this record would ONLY be usable for the cs_materialized_views table BEGIN PERFORM cs_log(''Refreshing materialized views...''); FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP -- Now "mviews" has one record from cs_materialized_views PERFORM cs_log(''Refreshing materialized view '' || mview.mv_name || ''...''); TRUNCATE TABLE mview.mv_name; INSERT INTO mview.mv_name || '' '' || mview.mv_query; END LOOP; PERFORM cs_log(''Done refreshing materialized views.''); return 1; end; ' language 'plpgsql';ループがEXIT文で終了された場合、最後に代入された行はループの後 も引き続きアクセスできます。
レコード処理の繰り返しのもう1つの方法として、FOR-IN EXECUTE文が使用できます。
[<<label>>]
FOR record | row IN EXECUTE text_expression LOOP
statements
END LOOP;
これは元のSELECT文が文字列式として指定されていること以外は、以
前の形式に似ています。つまり評価されて、 それぞれ FOR ループに再
計画されているのです。プログラマあらかじめ計画された問い合わせの
高速性あるいは動的問い合わせの柔軟性のどちらかも選ぶことができま
す。
Postgres の elog 機能にメッセージを投げ 掛けるには RAISE 文を使用してください。
RAISE のレベルには 'format' [, identifier [...]]などがあります。
フォーマットの内の、% はその後のカンマで区切ら
れた識別子用の場所を確保するものとして使われます。とりうるレベルは
DEBUG(実行中のデータベースの出力は静かに抑えられます。)NOTICE(デー
タベースログに出力され、クライアントアプリケーションにも転送されま
す。)、EXCEPTION(データベースログに出力されトランザクションを中断
します。)です。
RAISE NOTICE ''Id number '' || key || '' not found!''; RAISE NOTICE ''Calling cs_create_job(%)'',v_job_id;この最後の例で、v_job_id は % に置き換えられます。
RAISE EXCEPTION ''Inexistent ID --> %'',user_id;これはトランザクションをアボートさせて、データベースログに書き込みます。
Postgresの例外を扱う方式はあまり気の効 いたものではありません。パーサ、プランナ、オプティマイザ、または エクゼキュータがこれ以上文を実行することがでいないと判断すると、 トランザクション全体を中断し、システムはクライアントアプリケーショ ンからの次の問い合わせを受け付けるメインループに戻ります。
これが発生したことを通知するために、エラー機構に引っかけることが できます。しかし、現時点では、(入出力変換エラー、浮動小数点エラー、 分析エラーといったもののうち)何がその中断を引き起こしたのかを通知 することはできません。そして、この段階ではバックエンドは不整合状 態にあるので上位のエクゼキュータに戻したり、データベース全体を壊 してしまうかもしれないようなコマンドを更に発行するとデータベース 全体を壊してしまうかもしれません。また、トランザクションが中断さ れたとはいえ、この段階でトランザクションがアボートしたという情報 はすでにクライアントアプリケーションに送られていますので、操作の 再実行は意味がありません。
従って、現段階で関数やトリガプロシージャ実行中に中断が起きたとき に、PL/pgSQLができることは、どの関数のどこ(行番号と文の型)で発生 したのか知らせるといった追加的な DEBUG レベルのログメッセージを出 力することのみです。