如何在更新链末尾触发触发器?

2024-02-02

我有几个使用触发器相互交互的表,并且我当前处理触发器执行的方式使用pg_trigger_depth() < 2这很丑。 我真的希望最终的触发器只运行一次,并且在所有每行的事情发生之后最后运行。很遗憾,CONSTRAINT TRIGGERs are FOR EACH ROW仅,并且FOR STATEMENT触发器实际上在触发器中的每个语句触发一次,而不仅仅是每个启动它的初始语句触发一次。

我已经浏览了围绕该主题的其他几个问题,但没有找到与我正在做的事情足够相似的东西。

这是设置:

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发生火灾。

我使用在其他答案之一中看到的技巧尝试了不同版本的标志(https://stackoverflow.com/a/8950639/2340769 https://stackoverflow.com/a/8950639/2340769)创建临时表并捕获重复表异常以防止进一步执行。但我认为这并没有真正改善问题。

有没有办法以干净的方式做我想做的事情?虽然这是一个明显的玩具示例,但我的实际应用程序确实需要构建数据的“打包数组”表示形式,并且以有效的方式这样做会很棒。


而不是使用标志report_subscriber就其本身而言,我认为您最好使用一个单独的待处理更改队列。这样做有几个好处:

  • 无触发递归
  • 在引擎盖下,UPDATE只是DELETE + re-INSERT,因此插入队列实际上比翻转标志更便宜
  • 可能便宜很多,因为你只需要排队不同的report_ids,而不是克隆整个report_subscriber记录,并且您可以在临时表中执行此操作,因此存储是连续的并且不需要将任何内容同步到磁盘
  • 翻转标志时无需担心竞争条件,因为队列是当前事务的本地队列(在您的实现中,受UPDATE report_subscriber不一定与您在SELECT...)

因此,初始化队列表:

CREATE FUNCTION create_queue_table() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  CREATE TEMP TABLE pending_subscriber_changes(report_id INT UNIQUE) ON COMMIT DROP;
  RETURN NULL;
END
$$;

CREATE TRIGGER create_queue_table_if_not_exists
  BEFORE INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH STATEMENT
  WHEN (to_regclass('pending_subscriber_changes') IS NULL)
  EXECUTE PROCEDURE create_queue_table();

...在更改到达时对其进行排队,忽略已排队的任何内容:

CREATE FUNCTION queue_subscriber_change() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  IF TG_OP IN ('DELETE', 'UPDATE') THEN
    INSERT INTO pending_subscriber_changes (report_id) VALUES (old.report_id)
    ON CONFLICT DO NOTHING;
  END IF;

  IF TG_OP IN ('INSERT', 'UPDATE') THEN
    INSERT INTO pending_subscriber_changes (report_id) VALUES (new.report_id)
    ON CONFLICT DO NOTHING;
  END IF;
  RETURN NULL;
END
$$;

CREATE TRIGGER queue_subscriber_change
  AFTER INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH ROW
  EXECUTE PROCEDURE queue_subscriber_change();

...并在语句末尾处理队列:

CREATE FUNCTION process_pending_changes() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  UPDATE report
  SET report_subscribers = ARRAY(
    SELECT DISTINCT subscriber_name
    FROM report_subscriber s
    WHERE s.report_id = report.report_id
    ORDER BY subscriber_name
  )
  FROM pending_subscriber_changes c
  WHERE report.report_id = c.report_id;

  DROP TABLE pending_subscriber_changes;
  RETURN NULL;
END
$$;

CREATE TRIGGER process_pending_changes
  AFTER INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH STATEMENT
  EXECUTE PROCEDURE process_pending_changes();

这有一个小问题:UPDATE不提供有关更新顺序的任何保证。这意味着,如果这两个语句同时运行:

INSERT INTO report_subscriber (report_id, subscriber_name) VALUES (1, 'a'), (2, 'b');
INSERT INTO report_subscriber (report_id, subscriber_name) VALUES (2, 'x'), (1, 'y');

...如果他们尝试更新,就有可能陷入僵局report以相反的顺序记录。您可以通过对所有更新强制执行一致的顺序来避免这种情况,但不幸的是没有办法附加ORDER BY to an UPDATE陈述;我认为你需要求助于游标:

CREATE FUNCTION process_pending_changes() RETURNS TRIGGER LANGUAGE plpgsql AS $$
DECLARE
  target_report CURSOR FOR
    SELECT report_id
    FROM report
    WHERE report_id IN (TABLE pending_subscriber_changes)
    ORDER BY report_id
    FOR NO KEY UPDATE;
BEGIN
  FOR target_record IN target_report LOOP
    UPDATE report
    SET report_subscribers = ARRAY(
        SELECT DISTINCT subscriber_name
        FROM report_subscriber
        WHERE report_id = target_record.report_id
        ORDER BY subscriber_name
      )
    WHERE CURRENT OF target_report;
  END LOOP;

  DROP TABLE pending_subscriber_changes;
  RETURN NULL;
END
$$;

如果客户端尝试在同一事务中运行多个语句,这仍然有可能出现死锁(因为更新顺序仅应用于每个语句中,但更新锁将一直保持到提交为止)。你可以通过关闭来解决这个问题(某种程度上)process_pending_changes()仅在事务结束时执行一次(缺点是,在该事务中,您不会看到自己的更改反映在report_subscribers array).

如果您认为值得麻烦填写的话,这里是“提交时”触发器的通用大纲:

CREATE FUNCTION run_on_commit() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  <your code goes here>
  RETURN NULL;
END
$$;

CREATE FUNCTION trigger_already_fired() RETURNS BOOLEAN LANGUAGE plpgsql VOLATILE AS $$
DECLARE
  already_fired BOOLEAN;
BEGIN
  already_fired := NULLIF(current_setting('my_vars.trigger_already_fired', TRUE), '');
  IF already_fired IS TRUE THEN
    RETURN TRUE;
  ELSE
    SET LOCAL my_vars.trigger_already_fired = TRUE;
    RETURN FALSE;
  END IF;
END
$$;

CREATE CONSTRAINT TRIGGER my_trigger
  AFTER INSERT OR UPDATE OR DELETE ON my_table
  DEFERRABLE INITIALLY DEFERRED
  FOR EACH ROW
  WHEN (NOT trigger_already_fired())
  EXECUTE PROCEDURE run_on_commit();
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在更新链末尾触发触发器? 的相关文章

  • Laravel 5.3 Eloquent 事务和外键限制

    我正在从事一个更大的项目 我们在一个 Postgres 数据库中有多个模式 我们在模式之间创建了外键 这是一个例子 gt 我们有公司模式和用户模式 公司模式有company users表 该表对user users表有外键限制 CREATE
  • 如何从 postgresql 函数或触发器发送一些 http 请求

    我需要通过 http 协议 GET 或 POST 请求 从函数或触发器发送数据 是否可以 您可以尝试用 PL Python 编写触发器并使用 urllib2 进行 POST
  • 用数字 1-1000 填充 Postgres 数据库?

    我是 pgAdmin3 的新手 我想用数字 1 1000 填充 pgAdmin3 中的数据库 我该怎么做呢 目前 我创建了一个名为 MyDatabase 的数据库 其中没有任何内容 每行应与其数值相对应 第 1 行应包含 1 第 2 行应包
  • 我可以以编程方式配置 PostgreSQL 以不消除全文搜索中的停用词吗?

    我正在使用 PostgreSQL 全文搜索来进行项目 其中传统停用词 a the if 等 应该被索引和可搜索 这不是默认行为 例如 我可能希望我的用户找到查询 to be or not to be 的结果 The 文档 http www
  • Postgres 平均值计算忽略 null

    这是我的 postgres 表 name revenue John 100 Will 100 Tom 100 Susan 100 Ben 5 rows 在这里 当我计算平均收入时 它返回 100 这显然不是这种情况 而总和 计数 即 400
  • 计算包含字母/数字的行数

    我想要实现的目标很简单 但是解释起来有点困难 我不知道在 postgres 中这是否真的可能 我处于相当基础的水平 SELECT FROM WHERE LEFT JOIN ON HAVING 等等基本的东西 我正在尝试计算包含特定字母 数字
  • Postgresql 的 SQL_NO_CACHE?

    MySQL 关键字是否有等效的 postgresqlSQL NO CACHE 或 SQL Serverdbcc drop clean buffers 即您可以简单地将其包含在 SQL 语句中或作为脚本的一部分吗 UPDATE 这个问题 查看
  • JPA 和 PostqreSQL:长字符串持久化

    谁能告诉我如何使用 JPA 保存长文本 我使用 PostgreSQL 这是我在类中定义很长字符串的方法 Lob private String body 然而 这会产生一个类型的字段字符变化 255 在数据库中 此外 我尝试使用 Column
  • PostgreSQL round(v numeric, s int)

    Which method http en wikipedia org wiki Rounding Tie breakingPostgres 吗round v numeric s int http www postgresql org doc
  • PostgreSQL:使用for循环迭代表行,根据当前行检索列值

    我有以下2张表 CREATE TABLE salesperson t salespersonid numeric 4 0 NOT NULL salespersonname character varying 25 salespersonte
  • Npgsql 参数化查询输出与 PostGIS 不兼容

    我在 Npgsql 命令中有这个参数化查询 UPDATE raw geocoding SET the geom ST Transform ST GeomFromText POINT longitude latitude 4326 3081
  • 如何存储没有年份部分的生日?

    类似问题 Postgres 生日选择 https stackoverflow com questions 6913719 postgres birthdays selection 我们正在设计一项新功能 我们将存储人们生日的月份和日期部分
  • 错误关系不存在

    我得到了 error relation causes does not exist 我的节点应用程序出现错误 这种关系确实存在 我不确定问题出在哪里 我创建了该表 CREATE TABLE causes cause id bigint NO
  • 使用 NLog .NET Core 将日志记录到 PostgreSQL DB

    我尝试将日志记录集成到 NET Core 中的数据库 我能够设置 NLog 并将消息记录到 SQL Server 这很容易 但是当我尝试将 DB 切换到 PostgreSQL 时 似乎没有记录任何内容 以下是startup cs中的代码 p
  • postgresql:插入...(选择*...)

    我不确定它是否是标准 SQL INSERT INTO tblA SELECT id time FROM tblB WHERE time gt 1000 我正在寻找的是 如果 tblA 和 tblB 位于不同的数据库服务器中怎么办 Postg
  • 如何加速spark df.write jdbc到postgres数据库?

    我是 Spark 新手 正在尝试使用 df write 加速将数据帧的内容 可以有 200k 到 2M 行 附加到 postgres 数据库 df write format jdbc options url psql url spark d
  • 使用连接池后如何处理过多的并发连接?

    Scenario 假设您有一个拥有大量流量的网站或应用程序 即使使用数据库连接池 性能也会受到真正的打击 站点 应用程序甚至可能崩溃 因为并发连接太多 Question 人们有什么选择来处理这个问题 我的想法 我在想有这个问题的人可以创建多
  • postgresql 不同的不工作

    我使用以下代码从数据库获取值 但是当我编写这段代码时 测试看看问题出在哪里 我注意到查询没有从数据库中获取不同的值 这是查询 select distinct ca id as id acc name as accName pIsu name
  • SELECT 在 PL/pgSQL 函数中引发异常

    我想在函数内实现循环 但收到此错误 ERROR 查询没有结果数据的目标 代码 CREATE OR REPLACE FUNCTION my function ill int ndx bigint RETURNS int AS DECLARE
  • PostgreSQL:删除数据库但数据库仍然存在[重复]

    这个问题在这里已经有答案了 我是 PostgreSQL 的新手 我尝试着理解它 我熟悉数据库和MySQL 我正在尝试删除我创建的数据库 因为 psql 似乎忽略了我尝试通过 Django 推送的更改 当我执行时 l我得到以下回复 List

随机推荐

  • npm run 脚本中参数的 if-else

    我想调用不同的其他脚本 具体取决于是否给出参数 paramtest if z 1 then echo Foo 1 else echo Bar fi npm 运行参数测试 应该给 酒吧 npm run paramtest 无论如何 应该给出
  • 以编程方式从 Yahoo! 获取联系人地址簿[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何开始使用 MS-Build

    我希望开始使用 MS Build 到目前为止 我有很多手动构建的项目 从 Visual Studio 我想自动化构建过程 并且最好是在我不想安装 Visual Studio 的机器上进行 我开始在 MSDN 上阅读有关 MS Build 的
  • Actor 系统无响应:ThreadPoolExecutor 调度程序仅创建核心线程池,显然忽略最大线程池大小

    更新 我发现如果我设置ThreadPoolExecutor s核心池大小与最大池大小相同 29 个线程 但是 如果我将核心池大小设置为 11 最大池大小设置为 29 那么 Actor 系统只会创建 11 个线程 我该如何配置ActorSys
  • Laravel 服务提供商不受合同约束

    我有以下由服务提供商绑定的合同 接口 但是我收到以下错误 RouteDependencyResolverTrait php 第 81 行中的 ReflectionException 类 App Http Controllers Rocket
  • MongoDB/Mongoose 索引使查询更快还是更慢?

    我有一个这样的文章模型 var ArticleSchema new Schema type String title String content String hashtags String comments type Schema Ob
  • 无法推断概念中的占位符类型

    我正在尝试使用 GCC 8 中的 Concepts TS 复制标准 C 20 概念 以便我可以在标准库中提供它们之前使用它们 我主要复制粘贴最新草稿中的所有内容 然后遇到一个问题 include
  • 将 facebook sdk 链接到 android 项目

    在 Eclipse 中创建 Android 项目并导入 facebook sdk 后 我转到 Android 应用程序的属性 选择 android 并添加 facebook sdk 然后单击 确定 然后 当我再次进入属性时 它会针对该 fa
  • 如何在Python中读取XML头

    如何在 Python 3 中读取 XML 文档的标头 理想情况下 我会使用 defusedxml 模块作为文件指出它更安全 https docs python org 3 library xml html 但在这一点上 经过几个小时的尝试弄
  • MongoDB:将数组添加到现有数组中

    我正在尝试将 Instructors 数组添加到现有的 Camps 数组中 层次结构看起来像这样 owner email email protected cdn cgi l email protection password mypassw
  • 适用于 Mac 的 Python Winsound 等效项

    我想知道是否有与在 Mac 上播放 wav 文件相当的 winsound 最好是本地进口 谢谢 正如其他模块所说的功能一样 本机导入会在这里造成麻烦 我假设您知道如何安装模块 所以我不会详细介绍 这是一个选项 Pygame sudo pip
  • Apollo GraphQL 突变(对象参数)

    所以 我正在尝试设计我的 Apollo 服务器 我想创建一个以对象作为参数的突变 这是我的架构的一个片段 它以某种方式导致了问题 我认为它在语法上是正确的 但我遇到了这个错误 errors message 预期的输入类型 您必须将 INPU
  • 小数点四舍五入到最接近的第 10 位

    需要将我的答案四舍五入到最接近的第十位 double finalPrice everyMile 2 8 DecimalFormat fmt new DecimalFormat 0 00 this answerField setText fm
  • 在 postgresql 中的列上设置“NOT NULL”是否会提高性能?

    我知道这在 MySQL 中是个好主意 如果我没记错的话 在 MySQL 中它允许索引更有效地工作 Setting NOT NULL本身对性能没有影响 检查几个周期 无关紧要 但是您可以通过实际使用 NULL 而不是虚拟值来提高性能 根据数据
  • C:从 stdin 读取,直到按 Enter 两次

    考虑一个简单的程序 它必须从 stdin 获取 5 个数字的序列并打印它们的和 没有说明将采用多少行输入 但如果换行符被采用两次 或按两次 Enter 键 则程序必须终止 例如 Input 1 1 1 1 1 2 2 2 2 2 3 3 3
  • 上传更新设置问题

    我有一个运行良好的上传表单 但我想以编程方式更改设置 但出现错误 Uploadify 在 document ready 上启动 我尝试将 updateSettings 绑定到按钮单击 也在 document ready 中完成 我还尝试在
  • MySQL:从一列中选择包含值的多行

    我想找到具有 FORD 和 SILVER 且用户在值列中输入值 200 的汽车的 car id table cars id car id name value 1 1 MAKE FORD 2 1 CARLINE FIESTA 3 1 COL
  • 如何将动态大小的纹理数组与 glTexImage2D 一起使用?

    目前 我可以加载我创建的静态大小的纹理 在本例中为 512 x 512 此代码来自标题 define TEXTURE WIDTH 512 define TEXTURE HEIGHT 512 GLubyte textureArray TEXT
  • 用于特定服务的CPU利用率%的Windows命令[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 有没有办法从 Windows 上的脚本获取特定服务的 CPU 利用率 我知道wmic cpu get LoadPercentage将给出整个系统的 C
  • 如何在更新链末尾触发触发器?

    我有几个使用触发器相互交互的表 并且我当前处理触发器执行的方式使用pg trigger depth lt 2这很丑 我真的希望最终的触发器只运行一次 并且在所有每行的事情发生之后最后运行 很遗憾 CONSTRAINT TRIGGERs ar