web-dev-qa-db-ja.com

動的列名を持つ列の割り当て

設定する列の名前を(BEFORE UPDATE)トリガー、OLD値に設定し、入ってくるものはすべて無視します。以下を試しました。

CREATE OR REPLACE FUNCTION prevent_column_update() RETURNS TRIGGER AS $$
  DECLARE
    col TEXT := TG_ARGV[0];
  BEGIN
    EXECUTE format('SELECT ($1).%I INTO ($2).%I', col, col) USING OLD, NEW;
    RETURN NEW;
  END;
  $$ LANGUAGE plpgsql;

そして、次のように使用します:

CREATE TRIGGER request_protect_date_price_value
  BEFORE UPDATE OF date_price ON requests
  FOR EACH ROW EXECUTE PROCEDURE prevent_column_update('date_price');

しかし、更新すると失敗します:

ERROR:  syntax error at or near "("
LINE 1: SELECT ($1).date_price INTO ($2).date_price
                                    ^
QUERY:  SELECT ($1).date_price INTO ($2).date_price
5
chamini2

エラーは、INTO句がnot SQLコマンドの一部であることです。これはplpgsqlコマンドEXECUTEの一部です。

そして、動的フィールド名は、SQLでもPL/pgSQLでも、現在は不可能です。ただし、この制限を回避する方法はいくつかあります。

コンセプトの証明

組み込みの JSON関数json_populate_record() を使用して同様のトリックを行うことができますが、これは現在文書化されておらず、Postgresの将来のバージョンでは削除される可能性があります。

確実な方法は、文書化された#= operator追加の hstore モジュールの。データベースごとに1回モジュールをインストールする

CREATE EXTENSION IF NOT EXISTS hstore;

次に:

CREATE OR REPLACE FUNCTION prevent_column_update()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col text := quote_ident(TG_ARGV[0]);
   _old_val text;
   _new_val text;
BEGIN
   EXECUTE format('SELECT $1.%1$I, $2.%1$I', _col)
   INTO    _old_val, _new_val  -- part of plpgsql command
   USING   OLD, NEW;

   IF _old_val IS DISTINCT FROM _new_val THEN  -- only if it actually changed
      NEW := NEW #= hstore(_col, _old_val);    -- hstore operator #=
   END IF;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

hstoreはテキスト文字列で動作することに注意してください。他のデータタイプの値はtextにキャストされて戻されます。これは、考えられるすべてのデータタイプで機能します。ただし、一部のタイプでは問題が発生する可能性があります(浮動小数点数の丸めエラーなど)。

そして、このトリガー定義は、ケースを完全にするためのものです。

CREATE TRIGGER tbl_upbef_nope
BEFORE UPDATE ON tbl  -- your table here
FOR EACH ROW
EXECUTE PROCEDURE prevent_column_update('date_price');

この例では、列名はcasesensitiveです。これは、列名が識別子ではなく文字列として渡されるためです。

私がすること

各テーブルに動的SQLを含まない新しいプレーントリガー関数を作成するだけです。手間が減り、パフォーマンスが向上します。コードの重複に関する箇条書きをバイトします。

CREATE OR REPLACE FUNCTION prevent_column_update()
  RETURNS TRIGGER AS
$func$
BEGIN
   NEW.date_price:= OLD.date_price;  -- unconditionally
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

引き金:

CREATE TRIGGER tbl_upbef_nope
BEFORE UPDATE OF date_price ON tbl  -- your column and table here
FOR EACH ROW EXECUTE PROCEDURE prevent_column_update();  -- no param

チェックをトリガー自体に移動したので、列が更新されない限り、関数は実行されません。これは、同じテーブルの追加のトリガーによって回避される可能性があることに注意してください( マニュアルの引用 )。

トリガーは、リストされた列の少なくとも1つがUPDATEコマンドのターゲットとして言及されている場合にのみ起動します。

そのため、そのような追加のトリガーを除外できない場合は、UPDATEで無条件にトリガーを起動し、代わりにトリガー関数の変更を確認してください。

6