web-dev-qa-db-ja.com

トリガー関数で動的テーブル名を使用したINSERT

次のような方法を実現する方法がわかりません。

CREATE OR REPLACE FUNCTION fnJobQueueBEFORE() RETURNS trigger AS $$
    DECLARE
        shadowname varchar := TG_TABLE_NAME || 'shadow';
    BEGIN
        INSERT INTO shadowname VALUES(OLD.*);
        RETURN OLD;
    END;
$$
LANGUAGE plpgsql;

つまり動的に生成された名前でテーブルに値を挿入する。
上記のコードを実行すると、次の結果が得られます。

ERROR:  relation "shadowname" does not exist
LINE 1: INSERT INTO shadowname VALUES(OLD.*)

変数がテーブル名として展開/許可されていないことを示唆しているようです。 Postgresのマニュアルにはこれについての言及はありません。

私はすでにEXECUTEを次のように実験しました:

  EXECUTE 'INSERT INTO ' || quote_ident(shadowname) || ' VALUES ' || OLD.*;

しかし運がない:

ERROR:  syntax error at or near ","
LINE 1: INSERT INTO personenshadow VALUES (1,sven,,,)

RECORDタイプが失われたようです:OLD.*は文字列に変換されて再解析されるようで、あらゆる種類の型の問題(NULL値など)につながります。

何か案は?

31
sschober

PostgreSQL 9.1以降

format() には、識別子をエスケープする組み込みの方法があります。以前よりシンプル:

_CREATE OR REPLACE FUNCTION foo_before()
  RETURNS trigger AS
$func$
BEGIN
   EXECUTE format('INSERT INTO %I.%I SELECT $1.*'
                , TG_TABLE_SCHEMA, TG_TABLE_NAME || 'shadow')
   USING OLD;

   RETURN OLD;
END
$func$  LANGUAGE plpgsql;
_

VALUES 式でも機能します。

db <> fiddle ここ
古い sqlfiddle。

主なポイント

  • format() または quote_ident() を使用して識別子を引用し(自動的かつ必要な場合のみ)、それによって SQLインジェクション および単純な構文違反。
    これは、独自のテーブル名でも必要です
  • テーブル名をスキーマ修飾します。現在の _search_path_設定 に応じて、裸のテーブル名は別の方法で別のスキーマの同じ名前の別のテーブルに解決される場合があります。
  • 動的DDLステートメントにはEXECUTEを使用します。
  • USING句を使用してvaluesを安全に渡します。
  • plpgsqlでの動的コマンドの実行 に関する詳細なマニュアルを参照してください。
  • トリガーには、トリガー関数の_RETURN OLD;_が必要です_BEFORE DELETE_マニュアルの詳細はこちら

OLD非表示EXECUTEなので、ほぼ成功したバージョンではエラーメッセージが表示されます。また、試したように分解された行の個々の値を連結したい場合は、quote_literal()を使用してすべての列のテキスト表現を準備し、有効な構文を保証する必要があります。列名を事前に知っている列名を処理したり、システムカタログをクエリしたりする必要もあります。これは、シンプルで動的なトリガー関数を使用するという考えに反します...

私のソリューションは、これらの複雑化をすべて回避します。また、少し簡略化しました。

PostgreSQL 9.0以前

format()はまだ使用できないため、次のようになります。

_CREATE OR REPLACE FUNCTION foo_before()
  RETURNS trigger AS
$func$
BEGIN
    EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA)
                    || '.' || quote_ident(TG_TABLE_NAME || 'shadow')
                    || ' SELECT $1.*'
    USING OLD;

    RETURN OLD;
END
$func$  LANGUAGE plpgsql;
_

関連:

52

動的INSTEAD OF DELETEトリガーを探していたので、これに遭遇しました。質問と回答に感謝して、Postgres 9.3のソリューションを投稿します。

CREATE OR REPLACE FUNCTION set_deleted_instead_of_delete()
RETURNS TRIGGER AS $$
BEGIN
    EXECUTE format('UPDATE %I set deleted = now() WHERE id = $1.id', TG_TABLE_NAME)
    USING OLD;
    RETURN NULL;
END;
$$ language plpgsql;
1
robkorv