7

次のスキーマがあるとします。

create table account_type_a (
  id SERIAL UNIQUE PRIMARY KEY,
  some_column VARCHAR
);

create table account_type_b (
  id SERIAL UNIQUE PRIMARY KEY,
  some_other_column VARCHAR
);

create view account_type_a view AS select * from account_type_a;
create view account_type_b view AS select * from account_type_b;

ビューの更新を可能にする汎用トリガー関数を plpgsql で作成しようとしています。

create trigger trUpdate instead of UPDATE on account_view_type_a
for each row execute procedure updateAccount();    
create trigger trUpdate instead of UPDATE on account_view_type_a
for each row execute procedure updateAccount();

私の失敗した努力は次のとおりです。

create function updateAccount() returns trigger as $$
declare
  target_table varchar := substring(TG_TABLE_NAME from '(.+)_view');
  cols varchar;
begin
  execute 'select string_agg(column_name,$1) from information_schema.columns
           where table_name = $2' using ',', target_table into cols;
  execute 'update ' || target_table || ' set (' || cols || ') =  select ($1).*
           where id = ($1).id' using NEW;
  return NULL;
end;
$$ language plpgsql;

問題はupdate発言です。ここで機能する構文を思い付くことができません。私はこれを PL/Perl にうまく実装しましたが、plpgsql のみのソリューションに興味があります。
何か案は?

アップデート

@Erwin Brandstetter が示唆したように、これが私の PL/Perl ソリューションのコードです。彼の提案のいくつかを取り入れました。

create function f_tr_up() returns trigger as $$
  use strict;
  use warnings;
  my $target_table = quote_ident($_TD->{'table_name'}) =~ s/^([\w]+)_view$/$1/r;
  my $NEW = $_TD->{'new'};
  my $cols = join(',', map { quote_ident($_) } keys $NEW);
  my $vals = join(',', map { quote_literal($_) } values $NEW);
  my $query = sprintf(
    "update %s set (%s) = (%s) where id = %d",
    $target_table,
    $cols,
    $vals,
    $NEW->{'id'});
  spi_exec_query($query);
return;
$$ language plperl;
4

2 に答える 2

14

@Garyの回答は技術的には正しいですが、PostgreSQLこの形式をサポートしていることに言及していません。

UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)

もう一度説明書をUPDATE読んでください。

動的 SQL で彼をやり遂げるのはまだ難しいです。指定しなかったので、ビューが基になるテーブルと同じ列で構成される単純なケースを想定しています。

CREATE VIEW tbl_view AS SELECT * FROM tbl;

問題

  • 特別なレコードNEWは の中に表示されませんEXECUTE。の句をNEW使用して単一のパラメーターとして渡します。USINGEXECUTE

  • 説明UPDATEしたように、 list-form には個別のが必要です。サブセレクトを使用して、レコードを個々の列に分割します。

    UPDATE ...
    FROM  (SELECT ($1).*) x
    

    (括弧$1はオプションではありません。) これによりstring_agg()、カタログ テーブルから作成された 2 つの列リストを簡単に使用できます。

  • 行の値全体を個々の列に割り当てることはできません。マニュアル:

    標準によれば、ターゲット列名の括弧で囲まれたサブリストのソース値は、正しい列数を生成する任意の行値式にすることができます。PostgreSQL は、ソース値が行コンストラクターまたは sub- であることのみを許可しますSELECT

  • INSERTよりシンプルに実装されます。ビューとテーブルの構造が同一であると仮定して、列定義リストを省略します。(改善される可能性があります。以下を参照してください。)

解決

私はそれを輝かせるためにあなたのアプローチに多くの更新を加えました.

のトリガー機能UPDATE:

CREATE OR REPLACE FUNCTION f_trg_up()
  RETURNS TRIGGER AS
$func$
DECLARE
   tbl  text := quote_ident(TG_TABLE_SCHEMA) || '.'
             || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$'));
   cols text;
   vals text;
BEGIN
   SELECT INTO cols, vals
          string_agg(quote_ident(attname), ', ')
         ,string_agg('x.' || quote_ident(attname), ', ')
   FROM   pg_attribute
   WHERE  attrelid = tbl::regclass
   AND    NOT attisdropped   -- no dropped (dead) columns
   AND    attnum > 0;        -- no system columns

   EXECUTE format('
   UPDATE %s t
   SET   (%s) = (%s)
   FROM  (SELECT ($1).*) x
   WHERE  t.id = ($2).id'
   , tbl, cols, vals) -- assuming unique "id" in every table
   USING NEW, OLD;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

のトリガー機能INSERT:

CREATE OR REPLACE FUNCTION f_trg_ins()
  RETURNS TRIGGER AS
$func$
DECLARE
    tbl text := quote_ident(TG_TABLE_SCHEMA) || '.'
             || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$'));
BEGIN
   EXECUTE 'INSERT INTO ' || tbl || ' SELECT ($1).*'
   USING NEW;

   RETURN NEW;  -- don't return NULL unless you know what you're doing
END
$func$ LANGUAGE plpgsql;

トリガー:

CREATE TRIGGER trg_instead_up
INSTEAD OF UPDATE ON a_view
FOR EACH ROW EXECUTE PROCEDURE f_trg_up();

CREATE TRIGGER trg_instead_ins
INSTEAD OF INSERT ON a_view
FOR EACH ROW EXECUTE PROCEDURE f_trg_ins();

INSERTおよび を示すSQL FiddleUPDATEです。

主なポイント

  • テーブル参照を明確にするために、スキーマ名を含めます。複数のスキーマの同じデータベースに、同じテーブル名の複数のインスタンスが存在する可能性があります!

  • pg_attributeの代わりにクエリを実行しinformation_schema.columnsます。これは移植性が低くなりますが、はるかに高速であり、テーブル OID を使用できます。

  • テーブル名は、動的 SQL のクエリを作成する場合のように文字列として処理される場合、 SQLiに対して安全ではありません。quote_ident()またはformat()またはオブジェクト識別子 typeでエスケープします。これには、特別なトリガー関数の変数TG_TABLE_SCHEMATG_TABLE_NAME!

  • オブジェクト識別子型regclassにキャストして、テーブル名が有効であることをアサートし、カタログ ルックアップ用の OID を取得します。

  • オプションformat()で、動的クエリ文字列を安全に構築するために使用します。

  • カタログ テーブルに対する最初のクエリに動的 SQL は必要ありません。より速く、より簡単に。

  • 何をしているのかわからない場合は、これらのトリガー関数RETURN NEWの代わりに使用してください。RETURN NULL(現在の行NULLの をキャンセルします。)INSERT

  • この単純なバージョンでは、すべてのテーブル (およびビュー) に という名前の一意の列があると想定していますid。より洗練されたバージョンでは、主キーを動的に使用する場合があります。

  • の関数を使用すると、セットが同じである限り、UPDATEビューとテーブルの列を任意の順序にすることができます。の関数INSERTは、ビューとテーブルの列が同じ順序であると想定しています。INSERT任意の順序を許可する場合は、 と同様に、列定義リストをコマンドに追加しますUPDATE

  • 更新版は、追加idで使用することにより、列の変更もカバーしています。OLD

于 2013-03-12T00:32:28.600 に答える
1

set (col1,col2) = select val1,val2Postgresql は、構文を使用した複数の列の更新をサポートしていません。

postgresqlで同じことを達成するには、使用します

update target_table
set col1 = d.val1,
    col2 = d.val2
from source_table d
where d.id = target_table.id

これにより、使用している列名リストを個々のフィールドに繰り返す必要があるため、動的クエリの作成が少し複雑になります。文字列を再度分割するよりも配列の方が処理しやすいので、array_agg代わりに使用することをお勧めします。string_agg

Postgresql の UPDATE 構文

array_agg 関数に関するドキュメント

于 2013-03-11T16:09:39.443 に答える