web-dev-qa-db-ja.com

列名をパラメーターとして取り、行を変更するトリガー関数

Postgres 9.4でトリガー関数を記述しようとしています。このようなもの(まだ機能していません):

  CREATE FUNCTION set_point_from_coords(source _regclass, target _regclass) 
    RETURNS trigger AS $func$
  BEGIN
    NEW.target := ST_SetSRID(ST_Point(NEW.source[1], NEW.source[2]), 4326);
    RETURN NEW;
  END;
  $func$ LANGUAGE plpgsql

この場合、targetgeometry型の列で、sourceは10進数の配列です。

行がcoords配列で挿入されている場合、それをpointに変換します。上記は列名をハードコーディングした場合に機能しますが、同じ関数を使用して、異なるテーブルと異なる列のペアに対して実行したいと考えています。また、INSERT自体を直接制御することはできません。
これが私の実験の一部です: http://sqlfiddle.com/#!15/dddcd/1

これを見つけた 関連するブログ投稿 解析に本当に苦労しています。

挿入/更新の前ではなく、実行後に実行することもできます。

7
Ivan

問題

あなたはすべての起こり得る合併症が一緒になる場所を選ばなければなりませんでした。

  • SQL(またはPL/pgSQL)ではidentifiersをパラメーター化することはできません。そのためには、EXECUTEを使用した動的SQLが必要です。

  • ただし、トリガー関数の特別なplpgsql変数NEWは、EXECUTEで実行される動的コード内では表示されません。

  • また、_CREATE TRIGGER_のトリガー関数に引数として列名を渡すことにより、さらに複雑になります。

  • また、ターゲット列を動的にするだけでは不十分で、行の別の動的列からソース値をフェッチする必要があります。

関係するすべての問題を回避する方法を知らない限り、より単純なアプローチを試してみます。トリガーごとに個別のトリガー関数を記述し、動的SQLなしでターゲット列に割り当てます。

解決

つまり、それはcanoneのコード行で実行できます-そして、多くの説明行。質問の元の例では、次のテーブル定義を想定しています。

_CREATE TABLE tbl (
  tbl_id serial PRIMARY KEY,
  geom geometry,
  coords double precision[]
);
_

これを機能させるには、追加のモジュール hstore が(データベースごとに1回)インストールされている必要があります。または、同じ目的で、ドキュメントに記載されていない json_populate_record()/jsonb_populate_record() の機能を使用することもできます。

_CREATE OR REPLACE FUNCTION trg_demo()
  RETURNS trigger AS 
$func$
BEGIN
   EXECUTE format('SELECT ($1 #= hstore(%L, ST_SetSRID(ST_Point($1.%2$I[1], $1.%2$I[2]), 4326)::text)).*'
                , TG_ARGV[0], TG_ARGV[1])  -- target (geom), source (coords)
   USING  NEW
   INTO   NEW;
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;_

引き金:

_CREATE TRIGGER demo
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW EXECUTE PROCEDURE trg_demo('geom', 'coords');
_

ここで何が起こっているのか理解できない場合は、上記のアドバイスを検討してください。

説明

geometryboldの動的計算をフォーマットして、これを理解しやすくしました。以下の単純なケースと比較してください。

db <> fiddle here-PostGISがインストールされていないため、pointではなくgeometryを使用します。
古い sqlfiddle

これはさらに単純なバージョンで、単にtext値を割り当てるだけです(ターゲット列geomtextである必要があります)。 boldの簡略化された部分:

_   EXECUTE format('SELECT ($1 #= hstore(%L, $1.%I::text)).*'
                , TG_ARGV[0], TG_ARGV[1])  -- target (geom), source (coords)
   USING  NEW
   INTO   NEW;_

コア機能は hstore演算子_#=_ to(ドキュメントごと)です。

recordのフィールドをhstoreの一致する値に置き換えます

Target and sourceはどちらも新しい行の列であり、問​​題が複雑になります。ソース値が定数である場合、単純に次のことができます。

_NEW := NEW #= hstore(TG_ARGV[0], 'POINT(123.0 456.0)');
_

関連:

ただし、列名を解決してソース値をフェッチするには、動的SQLが必要です。

  • format() を使用して、クエリ文字列を安全に連結します。

  • _TG_ARGV[0]_および_TG_ARGV[1]_ _CREATE TRIGGER_によって渡された引数の(0ベース!)テキスト配列への最初の2つの要素にアクセスします。

  • _%I_は、format()に渡された引数をidentifier(SQLインジェクションに対して安全)として連結します。

  • _$1_は、EXECUTE句でUSINGに渡された値を参照します。

  • hstoretextを取るため、ジオメトリを計算した後、_::text_へのキャストが必要です。

  • Plpgsqlは行をターゲットの列ごとに割り当てるため、割り当てのために行NEWを分解する必要があります。

13