分配具有动态列名的列

cha*_*ni2 5 postgresql trigger dynamic-sql plpgsql postgresql-9.5

我得到了要在 ( 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;
Run Code Online (Sandbox Code Playgroud)

并使用如下:

CREATE TRIGGER request_protect_date_price_value
  BEFORE UPDATE OF date_price ON requests
  FOR EACH ROW EXECUTE PROCEDURE prevent_column_update('date_price');
Run Code Online (Sandbox Code Playgroud)

但是当更新它失败时:

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
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 6

错误是该INTO子句不是SQL 命令的一部分。它是 plpgsql 命令的一部分EXECUTE

动态字段名目前是不可能的,无论是在 SQL 还是 PL/pgSQL 中。但是有一些方法可以解决这个限制:

概念证明

您可以(ab)使用内置的JSON 函数json_populate_record()来实现类似的技巧,但目前没有记录在案,可能会在 Postgres 的未来版本中删除。

确定的方法是使用附加hstore模块的记录#=操作符。每个数据库安装一次模块

CREATE EXTENSION IF NOT EXISTS hstore;
Run Code Online (Sandbox Code Playgroud)

然后:

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;
Run Code Online (Sandbox Code Playgroud)

请注意,hstore使用文本字符串进行操作。其他数据类型的值被强制转换为text和返回,这适用于我能想到的任何数据类型。但它可能会导致某些类型的问题(例如浮点数的舍入错误)。

这个触发器定义使案例完整:

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

在此示例中,列名区分大小写,因为它是作为字符串而不是标识符传递的。

我会怎么做

只需为每个表编写一个没有动态 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;
Run Code Online (Sandbox Code Playgroud)

扳机:

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
Run Code Online (Sandbox Code Playgroud)

我将检查移到触发器本身,因此除非更新列,否则该函数甚至不会执行。请注意,这可以通过同一个表上的其他触发器来规避,因为(引用手册):

仅当至少列出的列之一被提及作为UPDATE命令的目标时,触发器才会触发。

因此,如果您不能排除此类额外的触发器,请UPDATE无条件地触发触发器并检查触发器函数中的更改。

  • 我想我会_字节的子弹_,谢谢你的建议。 (2认同)
  • @chamini2:打错字了,嗯?那个可以留下来。:) (2认同)

归档时间:

查看次数:

6392 次

最近记录:

9 年,3 月 前