【发布时间】:2017-12-20 23:41:59
【问题描述】:
我有几个使用触发器相互交互的表,而我目前处理触发器执行的方式使用pg_trigger_depth() < 2,这很丑陋。
我真的希望最终触发器只运行一次,并且在所有每行的事情都发生后最后运行。不幸的是,CONSTRAINT TRIGGERs 只是 FOR EACH ROW,FOR STATEMENT 触发器实际上在触发器中的每个语句触发一次,而不仅仅是每个启动它的初始语句一次。
我已经查看了有关该主题的其他几个 SO 问题,但没有找到与我正在做的事情足够相似的东西。
设置如下:
CREATE TABLE report(
report_tk SERIAL PRIMARY KEY,
report_id UUID NOT NULL,
report_name TEXT NOT NULL,
report_data INT NOT NULL,
report_subscribers TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
valid_range TSTZRANGE NOT NULL DEFAULT '(,)',
EXCLUDE USING GIST ((report_id :: TEXT) WITH =, report_name WITH =, valid_range WITH &&)
);
CREATE TABLE report_subscriber(
report_id INT NOT NULL REFERENCES report ON DELETE CASCADE;
subscriber_name TEXT NOT NULL,
needs_sync BOOLEAN NOT NULL DEFAULT TRUE,
EXCLUDE USING GIST (subscriber_name WITH =, valid_range WITH &&)
);
CREATE OR REPLACE FUNCTION sync_subscribers_to_report()
RETURNS TRIGGER LANGUAGE plpgsql SET SEARCH_PATH TO dwh, public AS $$
BEGIN
RAISE INFO 'Running sync to report trigger';
BEGIN
CREATE TEMPORARY TABLE lock_sync_subscribers_to_report(
) ON COMMIT DROP;
RAISE INFO 'syncing to report, stack depth is: %', pg_trigger_depth();
UPDATE report r
SET report_subscribers = x.subscribers
FROM (
SELECT
report_tk
, array_agg(DISTINCT u.subscriber_name ORDER BY u.subscriber_name) AS subscribers
FROM report_subscriber s
WHERE s.report_tk IN (
SELECT DISTINCT report_tk
FROM report_subscriber s2
WHERE s.needs_sync
)
GROUP BY s.report_tk
) x
WHERE r.report_tk = x.report_tk;
RAISE INFO 'turning off sync flag, stack depth is: %', pg_trigger_depth();
UPDATE report_subscriber
SET needs_sync = FALSE
WHERE needs_sync = TRUE;
RETURN NULL;
EXCEPTION WHEN DUPLICATE_TABLE THEN
RAISE INFO 'skipping recursive call, stack depth is: %', pg_trigger_depth();
RETURN NULL;
END;
END;
$$;
CREATE TRIGGER sync_subscribers_to_report
AFTER INSERT OR UPDATE OR DELETE
ON report_subscriber
FOR STATEMENT
EXECUTE PROCEDURE sync_subscribers_to_report();
因此,通过此设置,我希望能够:
- 插入报告记录
- 保证报告名称在任何一个时间点只能存在一次(valid_range 上的 EXCLUDE)
- 在订阅者表中插入报告订阅者
- 保证订阅者一次不能订阅多个报告。
- 允许多人订阅一份报告。
- 每当向订阅者表添加记录时,将名称添加到报告表中的订阅者列表中。
- 每当从订阅者表中删除记录时,从报告表中的订阅者列表中删除该名称。
- 每当从报告表中删除记录时,删除相应的订阅者记录(由
ON DELETE CASCADE处理
如果在单个语句中对订阅者表进行了大量编辑(常见情况),最好只运行一个简单的查询,使用来自订阅者表。
我最初的解决方案是在订阅者表中添加一个needs_update 标志并触发该标志以进行更新,然后关闭该标志。当然,这会导致触发器再次触发,我用pg_trigger_depth() < 2 停止了该触发器(2 是因为插入可能是由系统中的其他触发器引起的)。
除了丑陋之外,令人讨厌的是,触发器函数中的语句导致更多的FOR EACH STATEMENT 触发发生。
我使用我在其他 SO 答案之一 (https://stackoverflow.com/a/8950639/2340769) 中看到的技巧尝试了不同版本的标志,即创建临时表并捕获重复表异常以防止进一步执行。不过,我认为它并没有真正改善这个问题。
有没有办法以干净的方式做我想做的事情?虽然这是一个明显的玩具示例,但我的实际应用程序确实需要构建数据的“打包数组”表示,并且以一种有效的方式这样做会很棒。
【问题讨论】:
-
我有一个想法,我今天早上尝试过,我真的希望能奏效。我尝试在
SQL_DROP上创建一个EVENT TRIGGER,希望在ON COMMIT DROP删除临时表后触发,但触发器不会因为自动删除而触发,仅针对显式删除。 -
其他任何东西都需要
needs_sync标志,还是只是为了触发这个触发器? -
它只是为了触发。