PostgreSQL中的“引用计数”触发器

时间:2014-08-07 14:09:27

标签: sql postgresql triggers reference-counting referential-integrity

给出带有以下表格的postgreSQL查询:

+--------------------+
|Foo                 |
+--------------------+
| id | value1 | refe |
+--------------------+

+--------------------+
|Bar                 |
+--------------------+
| id | value2 | refe |
+--------------------+

+-------------+
|Refe         |
+-------------+
| id | value3 |
+-------------+

refe列是id Refe的外键。现在Refe存储了与FooBar(以及可能的其他表格)相关的其他数据。

我创建了一个存储"活跃"的视图。换句话说,referefeFoo的{​​{1}}字段的并集:

Bar

现在当一个人从CREATE OR REPLACE VIEW liverefe AS (SELECT refe FROM Foo) UNION (SELECT refe FROM Bar) Foo删除一行时,Bar可能会死亡(没有其他refeFoo行指它)。在这种情况下,它应该删除。

可以解释一下如何实现这样的"引用计数"触发?

2 个答案:

答案 0 :(得分:2)

使用NOT EXISTS反半连接。

删除refe中的所有死行:

DELETE FROM refe r
WHERE NOT EXISTS (SELECT 1 FROM foo WHERE refe = r.id)
AND   NOT EXISTS (SELECT 1 FROM bar WHERE refe = r.id);

(可能)在其中一个引用表中删除DELETE / UPDATE之后的refe中的特定行:

DELETE FROM refe r
WHERE r.id = 12345   -- your id here
AND   NOT EXISTS (SELECT 1 FROM foo WHERE refe = r.id)
AND   NOT EXISTS (SELECT 1 FROM bar WHERE refe = r.id);

此类触发器的完整代码示例的相关答案

如果您应该更新refe列,请添加如下触发器:

CREATE TRIGGER foo_updelaft_kill_orphaned_refe
AFTER DELETE OR UPDATE OF refe
ON foo
FOR EACH ROW EXECUTE PROCEDURE f_trg_kill_orphaned_refe();

表格bar等相同 这样,仅当UPDATE实际定位refe列时才会调用触发器。

Details in the manual.

答案 1 :(得分:2)

如果你想立即删除,那么你可以声明一个触发器函数......

CREATE FUNCTION check_ref_count()
RETURNS trigger
LANGUAGE plpgsql
AS $$
  BEGIN
    DELETE FROM Refe WHERE id NOT IN (SELECT refe from liverefe);
    RETURN NULL;
  END;
$$;

然后将其附加到相关表格中。

CREATE TRIGGER slay_orphans
AFTER DELETE ON Foo
FOR EACH STATEMENT
EXECUTE PROCEDURE check_ref_count();

CREATE TRIGGER slay_orphans
AFTER DELETE ON Bar
FOR EACH STATEMENT
EXECUTE PROCEDURE check_ref_count();

(根据您使用这些项目的方式,您可能不得不说AFTER DELETE OR UPDATE而不仅仅是AFTER DELETE。特别是如果您经常更改refe。如果您通常设置它曾经并且不管它,AFTER DELETE应该没问题。)

注意:执行此操作后,Refe中的记录将无法在下次删除时继续存在,除非liverefe中的某些记录具有其ID。尽管这就是你想要的,但这意味着如果删除很常见,即使是全新的记录也可能会被删除。

只需检查已删除行指向的Refe,就可以使事情变得更严格。只需将id = old.refe添加到删除条件,然后设置触发器FOR EACH ROW而不是FOR EACH STATEMENT。这样做只会使以前指出的记录成为删除的候选者。 (但这也意味着清理不会追溯 - 当前的孤儿不会被清除,除非有东西指向他们然后指向别处或被删除。如果你走这条路线,AFTER DELETE OR UPDATE是必不可少的 - 让删除触发器处理所有内容的邋iness,如果你没有清理整个表,会导致行基本泄漏。)

或者,向Refe添加时间戳,并更改查询以忽略时间戳小于X秒/分钟/之前的记录。

但是,您解决了这个问题,您可能还想考虑使用交易 帮助确保记录在您尝试指向时不会被删除。并确保将您的外键指定为ON DELETE RESTRICT(这样除非记录确实是一个孤儿,否则将不允许删除Refe中的记录。)


如果您可以将所有行经常删除而不是立即删除,那么它会变得更容易,因为您不必创建函数等。

只需创建一个发出此查询的脚本:

DELETE FROM Refe WHERE id NOT IN (SELECT refe FROM liverefe)

并将其设置为cron作业(或Windows中的计划任务)。