【问题标题】:How to generate a single new row from a multi-row update in a trigger?如何从触发器中的多行更新生成单个新行?
【发布时间】:2023-04-10 09:08:01
【问题描述】:

基本上我有几个不同的表,它们共享一个数组字段job_ids,每个表都有自己的status,它也是一个整数。在job 表上,id 是它的主键。

Postgres 12.5 版

table1

CREATE TABLE table1 (
id serial NOT NULL,
jobs_ids _int4 NULL,
status int4 NULL,
CONSTRAINT cable_pkey PRIMARY KEY (id)

job:

CREATE TABLE job (
id serial NOT NULL,
description varchar NULL,
created_at timestamptz NULL DEFAULT now(),
modified_at timestamptz NULL DEFAULT now(),
CONSTRAINT job_pkey PRIMARY KEY (id)

我的触发函数:

CREATE OR REPLACE FUNCTION new_job_id_table1()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN

if new.status= 3 and new.jobs_ids = '{}' then 
   insert into job (description, type_id)
   select 'automated jobid with id=' || new.id, 1;

   new.jobs_ids := array[ max(b.id) ] from job b
   where new.jobs_ids = '{}' and b.type_id  = 1;
end if;
RETURN NEW; 

END;
$function$;

触发器,设置为更新前运行:

create trigger job_id_trigger_table1
before update of status on table1
for each row execute function new_job_id_table1();

用户通过 qgis 更新内容,但通常是:

update table1 set status = 3 where id = $whatever_id_they_picked;

它在job 表中创建一个新条目,并将该id 分配给table1 中的更新行。

如果用户将table1 中的一行更新为状态 3,它可以正常工作。但是,当用户一次更新多行时,它什么也不做。
如果一次更新多行,我只想为table1 中的几行生成一个作业ID,而不是(当前)多个。

如何使触发器与多行更新一起工作?

【问题讨论】:

  • 请提供显示数据类型和约束的有效CREATE TABLE 脚本,而不是关于所述表的散文。第一个非常有用,第二个没多大用。并且始终是您的 Postgres 版本。
  • @ErwinBrandstetter 我已经编辑了操作
  • 我看到一个函数定义,但没有触发器定义。 “用户更新功能”也是一个不明确的定义。什么“特点”? UPDATE? INSERT?究竟什么是“同时”?在同一个命令中?太模糊了。
  • @ErwinBrandstetter 再次编辑

标签: sql postgresql database-design triggers


【解决方案1】:

设置

首先我简化并改进了您的测试设置:

CREATE TABLE table1 (
  id        serial PRIMARY KEY
, jobs_ids  int4[]
, status    int4 NOT NULL  -- !
);

-- DROP TABLE job;
CREATE TABLE job (
  id           serial PRIMARY KEY
, type_id      int NOT NULL DEFAULT 1  -- was missing
, description  varchar
, created_at   timestamptz DEFAULT now()
, modified_at  timestamptz DEFAULT now()
);

还理顺了您的原始触发器以了解发生了什么。如果以下解决方案不适合您,您可以将其用作后备:

db小提琴here

解决方案

使用AFTER trigger with transition tables

触发功能:

CREATE OR REPLACE FUNCTION job_id_trigger_table1()
  RETURNS trigger
  LANGUAGE plpgsql AS
$func$
BEGIN
   WITH sel AS (
      SELECT n.id
      FROM   updated_new n
      JOIN   updated_old o USING (id)  -- never update id!
      WHERE  n.status = 3
      AND    o.status <> 3
      AND    n.jobs_ids = '{}'
      )
   , ins AS (
      INSERT INTO job (description, type_id)
      SELECT 'automated jobid for IDs ' || array_agg(s.id)::text, 1
      FROM   sel s
      HAVING count(*) > 0  -- skip all if no matching rows
      RETURNING id
      )
    UPDATE table1 t
    SET    jobs_ids = ARRAY[i.id]
    FROM   sel s, ins i
    WHERE  t.id = s.id;
      
   RETURN NULL;  -- for AFTER trigger
END
$func$;

触发器:

CREATE TRIGGER job_id_trigger_table1
AFTER UPDATE ON table1
REFERENCING OLD TABLE AS updated_old
            NEW TABLE AS updated_new
FOR EACH STATEMENT
WHEN (pg_trigger_depth() = 0)  -- only immediate update!
EXECUTE FUNCTION job_id_trigger_table1();

db小提琴here

job 中为在单个命令中更新的任意数量(合格!)行创建单行。

我检查了OLDNEW 转换关系,以确保仅在更新中实际更改状态后才触发。如果您不需要这个,请删除 OLD 关系。

很遗憾,我们不能立即限制为UPDATE OF status,因为那是currently not implemented

该标准允许将转换表与特定列一起使用 UPDATE 触发,但随后应该在 转换表取决于触发器的列列表。这是 PostgreSQL 目前没有实现。

我加入OLDNEW 转换关系USING id。创建一个极端情况,在更新table1.id 后可能会出错。通常,这种情况永远不会发生,但请采取预防措施。

为了避免更新循环,我在触发器中添加了WHEN (pg_trigger_depth() = 0)。见:

该函数仅使用带有(数据修改)CTE 的单个 DDL 语句。应该是最有效的。见:

如果未找到符合条件的行,则会跳过 INSERTUPDATE。什么都没有发生。

我在小提琴中添加了几个测试来验证它是否像宣传的那样工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-03
    • 1970-01-01
    相关资源
    最近更新 更多