【问题标题】:Postgresql trigger to update a column in a table when another table gets inserted or updated当插入或更新另一个表时,Postgresql 触发器更新表中的列
【发布时间】:2018-01-12 06:08:37
【问题描述】:

基本上我有table_a 和table_b。 table_b 由 table_a 的特征组成,它们共享 section_id 列和状态。

table_a 的 section_id 是主键,所以是唯一的,但是 table_b 可以有多个 section_id,但它们都共享相同的状态

用户插入和更新 table_b,我想在 table_a 中捕获状态更改

CREATE TRIGGER table_b_aiu
AFTER INSERT OR UPDATE
ON table_b
FOR EACH ROW
WHEN (((new.status = 100) OR (new.status = 200)))
EXECUTE PROCEDURE table_b_aiu();

CREATE OR REPLACE FUNCTION table_b_aiu()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE table_a a
SET status = 100
FROM table_b b
WHERE (b.status = 100 or b.status = 200) 
AND a.section_id = b.section_id;
    RETURN new; 
END;
$BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;

这样做的问题在于,它不仅更新了新更新或插入的行,还更新了整个数据集,而这并不是有意的。如何只更新新行?我试过在触发器上提出论据,但它不接受 new.section_id

【问题讨论】:

  • section_id 是行的唯一标识符吗?对相关表格进行定义也会很有帮助。
  • @C.Arendt section_id 是 table_a 的主键,但不是 table_b 的主键
  • 触发器上的参数是什么意思?像这样:EXECUTE PROCEDURE table_b_aiu(new.section_id);?您可以访问该行而不将其作为参数传递。您可以简单地执行where a.section_id = new.section_id 并删除from table_b,因为它不需要。
  • @ŁukaszKamiński 在删除 from 并尝试 new.section_id 后,我得到了 Provider 错误:更改属性时出现 PostGIS 错误:错误:记录“new”没有字段“sectionid”
  • 不应该是section_id 而不是sectionid

标签: sql postgresql


【解决方案1】:

所以有几件事。对于这类问题,访问表定义以进行测试肯定会有所帮助。我已经模拟了我期望的表格设计。此外,包含“期望的行为”也很有帮助(即,您是否打算始终将状态设置为 100?不管 table_b 发生了什么?)。此外,update 语句必然会命中整个table_a,这就是为什么需要唯一约束来获得唯一记录的原因。如果您错误地将状态设置为 100 而应该是 SET status = new.status,那将是一个不同的问题(但可能看起来更新是“击中整个表”,请参见下面的示例 - 特别是 section_id=3)。

希望以下内容说明了准确的行为。我怀疑您的触发器应该设置为status=new.status

CREATE TABLE table_a
(
 section_id serial
 , status integer
 , CONSTRAINT pk_table_a PRIMARY KEY (section_id)
);

CREATE TABLE table_b
(
 id serial
 , section_id integer
 , status integer
 , CONSTRAINT pk_table_b_aiu PRIMARY KEY (id)
 )
 ;


CREATE OR REPLACE FUNCTION table_b_aiu()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE table_a a
SET status = 100 -- intentional??
WHERE (new.status = 100 or new.status = 200) 
AND a.section_id = new.section_id;

    RETURN new; 
END;
$BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;

CREATE TRIGGER table_b_aiu
AFTER INSERT OR UPDATE
ON table_b
FOR EACH ROW
WHEN (((new.status = 100) OR (new.status = 200)))
EXECUTE PROCEDURE table_b_aiu();


INSERT INTO table_a (section_id, status)
values (1,100)
, (2,200)
, (3,201)
, (4, 202)
returning *;

| section_id | status |
| 1 | 100 |
| 2 | 200 |
| 3 | 201 |
| 4 | 202 |

INSERT INTO table_b (section_id, status)
values (1,101), (2,100), (3,200), (4,201)
returning *;

| id | section_id | status | 
| 1 | 1 | 101 | 
| 2 | 2 | 100 | 
| 3 | 3 | 200 |
| 4 | 4 | 201 |

select *
from table_a;

| section_id | status |
| 1 | 100 |
| 4 | 202 |
| 2 | 100 |
| 3 | 100 |

注意:new.status in (100,200) 是多余的,但我认为您想要安全(以防有人在没有 when 语句的情况下设置触发器。

我的建议:如果您想将表本身的状态选项限制为 100 / 200,我建议为只有这些选项的 status 表创建一个外键。

【讨论】:

  • 好的,所以预期的行为是无论如何都将状态 100 留在 table_a 上。我想将状态从 100 更改回其他值的唯一原因是 table_b 上的行无效并被删除,但我想这是另一个触发因素
  • 您绝对可以将该逻辑放在同一个触发器中。一般来说,我认为这会更可取,尽管如果可能以一种避免触发器的方式进行关系设计,那可能还是更可取的。以上是否以您期望的方式工作? (如果你需要测试,你可以SET search_path=otherschema; 来避免你已经定义了表的任何模式)。
  • 另一个想法 - 您正在让用户插入日志表并更新父级。可以让用户插入或更新父表并使用触发器创建日志表(这是我通常遵循的方法)。请参阅倒数第二个示例here
  • 是的 a.section_id = new.section_id 行成功了。如何将删除逻辑放在同一个触发器中?
  • 有很多方法可以做到这一点!可能最好在带有逻辑描述的新问题中制定。总的来说,postgres docs 非常好(also here)。可能是if/else 逻辑的一些变体,其中包含更新语句。您可以使用IF TG_OP = 'DELETE' 来判断您的触发器是否执行了ON DELETE(当然,您也需要在删除时执行触发器)。
【解决方案2】:

试试这个功能(虽然没有测试)

CREATE OR REPLACE FUNCTION table_b_aiu()
RETURNS trigger AS
$BODY$
BEGIN
    UPDATE table_a
    SET status = 100
    WHERE section_id = NEW.section_id
    RETURN new;
END;
$BODY$

直到section_id 成为table_a 上的主键就足够了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-05
    • 2013-01-24
    • 2011-02-10
    • 2014-02-23
    • 1970-01-01
    • 2021-01-12
    相关资源
    最近更新 更多