web-dev-qa-db-ja.com

フィールド名のみが指定されたNEWまたはOLDフィールドにアクセスする方法は?

検証トリガーを書いています。トリガーは、配列の合計が別のフィールドと等しいことを検証する必要があります。この検証には多くのインスタンスがあるため、単一のプロシージャを記述して、チェックするフィールドのセットがそれぞれ異なる複数のトリガーを作成します。

たとえば、次のスキーマがあります。

_CREATE TABLE daily_reports(
     start_on date
   , show_id uuid
   , primary key(start_on, show_id)

     -- _graph are hourly values, while _count is total for the report
   , impressions_count bigint not null
   , impressions_graph bigint[] not null

   -- interactions_count, interactions_graph
   -- Twitter_interactions_count, Twitter_interactions_graph
);
_

検証では、impressions_count = sum(impressions_graph)であることを確認する必要があります。

Plpgsql内からNEWからフィールドに動的にアクセスする方法がわからないので、行き詰まっています。

_CREATE FUNCTION validate_sum_of_array_equals_other() RETURNS TRIGGER AS $$
DECLARE
  total bigint;
  array_sum bigint;
BEGIN
  -- TG_NARGS = 2
  -- TG_ARGV[0] = 'impressions_count'
  -- TG_ARGV[1] = 'impressions_graph'

  -- How to access impressions_count and impressions_graph from NEW?
  RETURN NEW;
END
$$ LANGUAGE plpgsql;

CREATE TRIGGER validate_daily_reports_impressions
ON daily_reports BEFORE INSERT OR UPDATE
FOR EACH ROW EXECUTE
  validate_sum_of_array_equals_other('impressions_count', 'impressions_graph');
_

_EXECUTE 'SELECT $1 FROM NEW' INTO total USING TG_ARGV[0]_を実行して 動的コマンドの実行 を試しましたが、PL/PGsqlはNEWが不明な関係であると文句を言っています。

特にPostgreSQL 9.1をターゲットにしています。

実際、NEWは明確に定義された複合型であるため、単純で単純な属性表記で任意の列にアクセスできます。 SQL自体は動的識別子(テーブル名や列名など)を許可しません。ただし、PL/pgSQL関数で 動的SQLをEXECUTE とともに使用できます。

デモ

_CREATE OR REPLACE FUNCTION trg_demo1()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text;
   _col_name  text := quote_ident(TG_ARGV[0]);  -- escape identifier
BEGIN
   EXECUTE format('SELECT ($1).%s::text', _col_name)
   USING NEW
   INTO  _col_value;

   -- do something with _col_value ...

   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', _col_name, _col_value;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;
_

textへのキャストはオプションです。普遍的に機能するため、それを使用します。タイプを知っている場合、キャストせずに作業できます...

_%s_でformat()を使用します。これは、識別子がその時点ですでにエスケープされているためです。
または、SQLインジェクションから保護するために、format()を_%I_とともに使用します。

または、Postgres 9.3以降では、 to_json()を使用してNEWをJSONに変換できます および列をキーとしてアクセス:

_CREATE OR REPLACE FUNCTION trg_demo2()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text := to_json(NEW) ->> TG_ARGV[0];  -- no need to escape identifier
BEGIN
   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', TG_ARGV[0], _col_value;
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;
_

列名はSQL文字列に連結されないため、SQLインジェクションは不可能であり、名前をエスケープする必要はありません。

db <> fiddle hereEXCEPTIONの代わりにNOTICEを使用して、効果を表示します) 。

関連:

14