Postgresql通用触发器函数?

neg*_*nbe 6 postgresql triggers primary-key

我是 Postgresql 新手,所以如果我的问题没有意义,请原谅。

我正在尝试找到一种方法将我的数据库结构迁移到Postgresql,特别是,我发现函数非常方便,并且希望使我的众多触发器更容易编写。

在我的数据库中,我使用标准last_modifiedlast_modified_by字段来跟踪更改。我还使用带有增量序列的标准主键。

有内置语法可将序列链接到主键 ID,但由于last_modified无论如何我都必须为字段编写触发器,所以我想知道是否可以有一个通用函数来一次更新所有内容。

示例:表ANIMAL有字段AMIMAL_ID(主键,带有序列SEQ_ANIMAL)、字段LAST_MODIFIEDLAST_MODIFIED_BY

同样,我有一个PLANT包含字段PLANT_ID(主键,带有序列SEQ_PLANT)、字段LAST_MODIFIED等等的表LAST_MODIFIED_BY

我想创建一个通用函数,在我需要创建的 4 个触发器中调用。我希望得到这样的东西:

插入函数之前:

CREATE FUNCTION TRIGGER_BI(p_pkField text, p_Sequence text) RETURNS TRIGGER AS $$
DECLARE
        curtime timestamp := now();
BEGIN
    NEW.LAST_UPDATED := curtime;
    NEW.LAST_UPDATED_BY := current_user;
    NEW.p_pkField := nextval(p_Sequence);
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
Run Code Online (Sandbox Code Playgroud)

更新前功能:

CREATE FUNCTION TRIGGER_BU() RETURNS TRIGGER AS $$
DECLARE
        curtime timestamp := now();
BEGIN
    NEW.LAST_UPDATED := curtime;
    NEW.LAST_UPDATED_BY := current_user;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
Run Code Online (Sandbox Code Playgroud)

现在,表的触发器ANIMAL:插入之前:

CREATE TRIGGER ANIMAL
BEFORE INSERT
    FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BI("ANIMAL_ID", "SEQ_ANIMAL");
Run Code Online (Sandbox Code Playgroud)

更新前:

CREATE TRIGGER ANIMAL
    BEFORE UPDATE
        FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BU();
Run Code Online (Sandbox Code Playgroud)

表的触发器PLANT:插入之前:

CREATE TRIGGER PLANT
BEFORE INSERT
    FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BI("PLANT_ID", "SEQ_PLANT");
Run Code Online (Sandbox Code Playgroud)

更新前:

CREATE TRIGGER PLANT
    BEFORE UPDATE
        FOR EACH ROW EXECUTE PROCEDURE TRIGGER_BU();
Run Code Online (Sandbox Code Playgroud)

是否有可能以任何方式获得通用的东西?

是的!正确的语法是什么?额外奖励:很可能有一个函数来完成所有工作,并且默认为空参数,如果为空,则不会更新序列。

是的,但是等等!这种方法有哪些缺点?(性能、安全性、还有其他需要考虑的因素)?

不!那么我真的需要每个触发器的功能吗?

更新: 我显式创建序列,因为我可能希望在多个表之间共享序列。这个想法是使用共享序列作为唯一的父表,其中多个子表的主键上有一个外键到父表。请毫不犹豫地评论这种方法,但我的基本理解是访问序列的下一个值比管理外键要有效得多......

更新 2: 我发现了一些非常有趣的东西,几乎让我到达那里 - 只是我的setValue功能不起作用......

这里是通用触发器:

CREATE OR REPLACE FUNCTION TRIGGER_FUNC() RETURNS TRIGGER AS $$
DECLARE
    p_pkField  text;
    p_Sequence text;
    pkValue    int;
BEGIN
    EXECUTE format('SELECT ($1).%I::int', TG_ARGV[0]) USING NEW INTO pkValue;
    p_Sequence := quote_ident(TG_ARGV[1]);
    IF pkValue IS NULL THEN
        SELECT setfieldValue(pg_typeof(NEW), TG_ARGV[0], nextval(p_Sequence));
    END IF;
    NEW.LAST_UPDATED := curtime;
    NEW.LAST_UPDATED_BY := current_user;
    RETURN NEW;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE
SECURITY DEFINER;
Run Code Online (Sandbox Code Playgroud)

我在这里setValue找到了该函数解决方案的提示并尝试对其进行调整,但它不起作用 - 我只是使用了错误的调用吗?或者我可以在方法中使用一些额外的知识来使其更简单吗?(我已经使用了我正在设置一个值的事实,但我也许可以做得更好?!)bigint

这是(非工作)代码:

CREATE OR REPLACE FUNCTION public.setfieldValue(anyelement, text, bigint)
 RETURNS anyelement
 LANGUAGE plpgsql
AS $function$
DECLARE 
  _name text;
  _values text[];
  _value text;
  _attnum int;
BEGIN
  FOR _name, _attnum
     IN SELECT a.attname, a.attnum
          FROM pg_catalog.pg_attribute a 
         WHERE a.attrelid = (SELECT typrelid
                               FROM pg_type
                              WHERE oid = pg_typeof($1)::oid) 
  LOOP
    IF _name = $2 THEN
      _value := $3;
    ELSE
      EXECUTE 'SELECT (($1).' || quote_ident(_name) || ')::text' INTO _value USING $1;
    END IF;
    _values[_attnum] :=  COALESCE('"' || replace(replace(_value, '"', '""'), '''', '''''') || '"', ''); 
  END LOOP;
  EXECUTE 'SELECT (' || quote_ident(pg_typeof($1)::text) || ' ''(' || array_to_string(_values,',') || ')'').*' INTO $1; 
  RETURN $1;
END;
$function$;
Run Code Online (Sandbox Code Playgroud)

kli*_*lin 5

用于手动设置主键的默认值。将主键声明为serial( 或bigserial) 并使用内置机制来处理此类列。不必担心主键的值不连续。主键仅用于明确标识行,仅此而已。

除此之外,您不能以这种方式执行此操作,因为触发器函数不能声明参数。

是,用于设置许多表中公共列的值。您可以使用相同的触发器函数进行插入和更新。例子:

CREATE OR REPLACE FUNCTION generic_trigger() -- function without arguments
RETURNS TRIGGER AS $$
BEGIN
    NEW.last_modified := now();
    NEW.last_modified_by := current_user;
    RETURN NEW;     -- important!
END;
$$
LANGUAGE 'plpgsql';

create table table_a 
    (a_id serial primary key, last_modified timestamp, last_modified_by text);
create table table_b 
    (b_id serial primary key, last_modified timestamp, last_modified_by text);

create trigger table_a_trigger
before insert or update on table_a
for each row execute procedure generic_trigger();

create trigger table_b_trigger
before insert or update on table_b
for each row execute procedure generic_trigger();

insert into table_a default values;

select * from table_a;

 a_id |      last_modified      | last_modified_by 
------+-------------------------+------------------
    1 | 2015-10-26 19:14:34.642 | postgres
(1 row) 
Run Code Online (Sandbox Code Playgroud)

也许您有非常重要的理由在触发器中设置主键的值(请参阅 jpmc26 的评论)。在这种情况下,主键应声明为integer( bigint) 且不default expression带触发器函数应如下所示:

create or replace function generic_trigger()
returns trigger as $$
begin
    new.last_modified := now();
    new.last_modified_by := current_user;
    if tg_op = 'INSERT' then
        if tg_table_name = 'table_a' then 
            new.a_id:= nextval('table_a_a_id_seq');
        elsif tg_table_name = 'table_b' then 
            new.b_id:= nextval('table_b_b_id_seq');
        end if;
    end if;
    return new;
end;
$$
language 'plpgsql';

insert into table_a (a_id) values (15);
select * from table_a;

 a_id |      last_modified      | last_modified_by 
------+-------------------------+------------------
    1 | 2015-10-26 19:14:34.642 | postgres
    2 | 2015-10-26 21:15:49.243 | postgres
(2 rows)
Run Code Online (Sandbox Code Playgroud)

阅读有关触发程序的更多信息。