AFTER INSERT/UPDATE/DELETE必须分开写,因三类操作对聚合值的影响逻辑完全不同:INSERT只加NEW.amount(需防NULL),UPDATE需先减OLD再加NEW(均需判NULL),DELETE只减OLD.amount;混写必致重复计算或漏减。

能,但必须用AFTER触发器分三类操作单独处理、主键覆盖全部维度、显式防NULL,且TRUNCATE和历史数据必须额外补全——漏掉任一环节,汇总值就错或丢。
为什么AFTER INSERT/UPDATE/DELETE必须分开写
聚合逻辑对三类操作的响应完全不同,混在一个触发器里极易重复计算或漏减:
- AFTER INSERT:只加
NEW.amount,但必须先判断NEW.amount IS NOT NULL,否则total = total + NULL会让整行变NULL - AFTER UPDATE:必须两步——先
SET total = total - OLD.amount(同样要OLD.amount IS NOT NULL),再SET total = total + NEW.amount - AFTER DELETE:只减
OLD.amount,不判断NULL会导致减去NULL,结果不可控
多维聚合表的主键和ON CONFLICT怎么设
主键必须覆盖全部聚合维度,否则并发更新会冲突或覆盖:
- 比如按
sale_date、product_id、region聚合,汇总表主键就得是(sale_date, product_id, region) - INSERT时不能只用
UPDATE,得用INSERT ... ON CONFLICT (sale_date, product_id, region) DO UPDATE SET total = EXCLUDED.total + EXCLUDED.amount - 注意
EXCLUDED是PostgreSQL关键字,不是变量名;字段名别漏写EXCLUDED.前缀
NULL值和TRUNCATE怎么兜底
这两处最常被忽略,出错后现象隐蔽:
-
SUM()遇到全NULL行返回NULL,但汇总表字段若定义为NOT NULL,就会写入失败。必须在建表时设默认值:total NUMERIC(12,2) DEFAULT 0 -
TRUNCATE完全不触发任何DML触发器,哪怕清空整张源表,汇总表也毫无反应。要么禁用TRUNCATE权限,改用DELETE FROM;要么额外建EVENT TRIGGER监听sql_drop - 新建汇总表后,必须立刻执行初始化:
INSERT INTO summary_table SELECT sale_date, product_id, region, COALESCE(SUM(amount), 0) FROM sales GROUP BY sale_date, product_id, region ON CONFLICT DO UPDATE SET total = EXCLUDED.total
触发器函数里别碰SELECT ... GROUP BY
增量更新是唯一可行路径,全量重算会拖垮写性能:
- 错误做法:在触发器里写
SELECT product_id, SUM(amount) FROM sales GROUP BY product_id——每次INSERT都扫全表,IO爆炸 - 正确做法:只基于
NEW或OLD单行数据做原子更新,例如UPDATE summary_table SET total = total + NEW.amount WHERE product_id = NEW.product_id - 如果目标行可能不存在,必须用
INSERT ... ON CONFLICT替代UPDATE,否则静默失败
真正难的不是写触发器,而是想清楚“谁在什么时候改了什么值、该往哪加、该从哪减、旧值还在不在、NULL算不算数、删表命令有没有绕过它”。这些细节没对齐,跑一周后数据就偏了,还查不出原因。

















