【问题标题】:Best way to validate Tree modeled as adjacency list, TRIGGER vs. CHECK constraint验证树建模为邻接列表、TRIGGER 与 CHECK 约束的最佳方法
【发布时间】:2017-06-14 07:58:05
【问题描述】:

我正在使用 PostgreSQL 使用邻接列表方法对树数据结构进行建模。我希望能够在每个叶子上存储额外的数据,指示这棵树属于哪个组:

CREATE TABLE groups (
  group_id integer PRIMARY KEY
);

CREATE TABLE leafs (
  leaf_id   integer PRIMARY KEY,
  parent_id integer REFERENCES leafs ON DELETE CASCADE ON UPDATE CASCADE,

  group_id integer REFERENCES groups ON DELETE CASCADE ON UPDATE CASCADE NOT NULL
);

我还想确保每个叶子只能连接到同一个组。看起来这可以通过创建 TRIGGERCHECK 约束来完成。我有两个问题:

  1. 处理这种特殊情况的最有效/正确的方法是什么,TRIGGERCHECK 约束? (以及在这两者之间进行选择的经验法则是什么)

  2. 是否有更好的方法来强制此模型的一致性(或者可能有替代方法来对此类树组进行建模)。

谢谢,

TRIGGER 版本代码:

CREATE OR REPLACE FUNCTION after_leaf_update()
  RETURNS trigger AS
$$
BEGIN
IF (NEW.parent_id IS NOT NULL) AND (SELECT group_id FROM leafs WHERE leaf_id=NEW.parent_id) <> NEW.group_id THEN
    RAISE EXCEPTION 'group_id of node/leaf does not match!!!';
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';


CREATE TRIGGER leafs_consistency_check
  BEFORE INSERT OR UPDATE
  ON leafs
  FOR EACH ROW
  EXECUTE PROCEDURE after_leaf_update();

CHECK 约束版本的代码:

CREATE OR REPLACE FUNCTION leafs_consistency_check_constraint(prn_id integer, grp_id integer) RETURNS BOOL AS
$$
BEGIN
    IF (prn_id IS NOT NULL) AND (SELECT group_id FROM leafs WHERE leaf_id=prn_id) <> grp_id THEN
    RAISE EXCEPTION 'CHECK: group_id of node/leaf does not match!!!';
END IF;
RETURN TRUE;

END;
$$ LANGUAGE plpgsql;

ALTER TABLE leafs ADD constraint group_id_constraint check ( leafs_consistency_check_constraint(parent_id, group_id) );

【问题讨论】:

  • 你检查PostgreSQL ltree module了吗?是否适用?你能在F.21.4. Example 中做类似CREATE TABLE test (path ltree, YOUR_LABEL_HERE text NOT NULL) 的事情吗?使用CREATE EXTENSION ltree为数据库启用模块。
  • 你检查过这个SO question的答案了吗?有帮助吗?
  • @flutter - 谢谢你的链接!我不知道 ltree 模块 - 我会检查一下!
  • @flutter 重新使用继承:我已经检查过了 - 对我来说似乎没有帮助,因为 leafs 都是同一类型...

标签: sql postgresql database-design


【解决方案1】:

使用触发器。

一般来说,Postgres 开发人员不建议将运行查询的函数放在 CHECK 约束中。除其他外,在某些情况下,CHECK 约束可能会针对一个查询进行多次检查,这是一项非常昂贵的检查。更重要的是,围绕行可见性和触发器的类似考虑因素进行了很多仔细的工程,而约束根本不存在。

我还建议您考虑另一种方法:如果您使用 BEFORE 触发器,忽略用户输入并自动为新节点提供与其父节点相同的 group_id,而不是引发错误,该怎么办?

或者更好的是,由于 group_ids 与父母相关联,而不是与孩子相关联,那么将 group_ids 放在仅链接到父节点的单独表中怎么样?

【讨论】:

  • 感谢您的回答并解释其背后的理性!重新“替代方法”:是的,我确实考虑过这一点,但如果这个应用程序尝试插入错误的节点group_id,则清楚地表明出现了可怕的错误(或对系统的攻击),所以我认为错误更合适方式。
  • 在单独的表中重新 group_ids:是的,我确实考虑过这一点。我同意这种方式没有数据重复,因此没有出错的余地。然而,在这种模式中,以下操作变得相当昂贵:[a] 识别树中的所有叶子,[b] 删除树。后来特别麻烦,因为有可能形成circular tree,删除它似乎并不简单。我知道 [a] 可以通过使用递归查询来解决,所以我唯一关心的是处理 [b]。对此有何建议/想法?谢谢,
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-08
  • 2010-09-22
  • 2014-03-11
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多