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


8

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

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

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)

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

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をターゲットにしています。


クロスは、PostgreSQLの一般的なメーリングリストに投稿さpostgresql.org/message-id/...
フランソワボーソレイユ

現在のところ、動的にフィールドにアクセスする唯一の方法NEWは、列名をキーとする値hstore(NEW)としてフィールドを使用してアクセスすることhstoreです。それらはすべてキャストさtextれ、元のタイプで操作したい場合はキャストバックする必要があるので、これは残念です。または、動的レコードアクセスをより適切にサポートするPL / Pythonなどの別の手続き型言語でトリガーを作成することもできます。
クレイグリンガー、

@CraigRinger:そうですね。今日、あなたは悲観的すぎます。動的SQLには方法があります。
Erwin Brandstetter 2014年

回答:


14

実際、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はオプションです。それは普遍的に機能するので、それを使用します。タイプがわかっていれば、キャストせずに作業できます...

識別子はその時点ですでにエスケープされているため、format()with を使用し%sます。
それ以外の場合はformat()%ISQLインジェクションから保護するためにwith を使用します。

または、Postgres 9.3以降では、列をキーとしてNEWJSONに変換しto_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(効果を表示するEXCEPTION代わりにNOTICE)。

関連:

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.