0

我有一个关于在 postgresql(9.1) 中实现继承的问题。

目的是建立一个地理层级模型,可以将国家、州和大陆混合起来创建“区域”。然后这些区域也可以与国家等混合在一起,以创建一个真正令人敬畏的区域层次结构

所以在我的逻辑模型中,一切都是一种“地方”。可以通过使用两个“位置”沿边指定来构造区域树。设计如下,在Java层易于管理。

    create table place_t (
            place_id serial primary key,
            place_type varchar(10)
    );
    create table country_t (
            short_name varchar(30) unique, 
            name varchar(255) null
    ) inherits(place_t);
    create table region_t( 
            short_name varchar(30),
            hierarchy_id integer, -- references hierarchy_t(hierarchy_id)
            unique(short_name) -- (short_name,hierarchy_id)
    ) inherits(place_t);
    create table region_hier_t( 
            parent integer references place_t(place_id), -- would prefer FK region_t(place_id)
            child integer references place_t(place_id),
            primary key(parent,child)
    );

    insert into region_t values(DEFAULT, 'region', 'NA', 'north american ops');
    insert into region_t values(DEFAULT, 'region', 'EMEA', 'europe and middle east');
    insert into country_t values(DEFAULT, 'country', 'US', 'USD', 'united states');
    insert into country_t values(DEFAULT, 'country', 'CN', 'CND', 'canada');

到目前为止,一切都很好。但以下失败:

    insert  into region_hier_t
        select p.place_id, c.place_id
        from region_t as p, country_t as c
        where p.short_name = 'NA' and c.short_name = 'US';

原因是前 4 次插入没有在“place_t”中创建任何行。实时调频!Postgres 文档实际上提到了这一点。

问题是 - 有解决方法吗?通过在 region_t 和 country_t 上插入触发器来实现我自己的“继承”是我唯一能想到的。

第二个问题是 - 这种混合节点树结构是否有更好的设计?

由于某些原因,我不想过分依赖 postgres-contrib 功能。也许这很愚蠢,请随意插话,但要温和(并且仅在回答另一个问题之后)!

谢谢

4

3 回答 3

1

对 region_hier_t 表中父列和子列的引用是错误的,因为如果您的引用调用另一个表,则无法从 country_t 插入键(child integer references place_t(place_id));您可以删除它们或添加新的。因此,让我们采用第二个选项,并为引用的表 region_t 和 country_t 添加一个与给定键匹配的唯一约束:

ALTER TABLE region_t
  ADD CONSTRAINT pk_region_t PRIMARY KEY(place_id );

ALTER TABLE country_t
  ADD CONSTRAINT pk_country_t PRIMARY KEY(place_id );

region_hier_t 的正确 CREATE 语句是:

        create table region_hier_t( 
            parent integer references region_t(place_id),
            child integer references country_t(place_id),
            primary key(parent,child)
    );

最后你可以运行你的INSERT.

因此,正如您所见,您需要做许多改进。也许你应该重新考虑你的设计。看看这个答案:如何以规范的方式存储邮政地址和政治部门?它比您的解决方案简单得多,并且更易于维护。

但是,如果您想坚持您的解决方案,请不要忘记在子表上设置主键(如上所示)。只有检查约束和非空约束由其子级继承,而您还没有这样做。

我看到您的其他插件无法正常工作:

insert into region_t values(DEFAULT, 'region', 'NA', 'north american ops');


ERROR:  invalid input syntax for integer: "north american ops"
LINE 1: ...ert into region_t values(DEFAULT, 'region', 'NA', 'north ame...

所以列分配也有问题。

于 2013-01-27T14:31:12.803 回答
0

解决方法是摆脱您对 place_t 的外键并改为:

CREATE FUNCTION place_t_exists(id int)
RETURNS bool LANGUAGE SQL AS 
$$
   SELECT count(*) = 1 FROM place_t;
$$;

CREATE FUNCTION fkey_place_t() RETURNS TRIGGER
LANGUAGE PLPGSQL AS $$
BEGIN;
  IF place_t_exists(TG_ARGV[1]) THEN RETURN NEW
  ELSE RAISE EXCEPTION 'place_t does not exist';
  END IF;
END;
$$;

当层次结构节点存在时,您还需要在子表上进行限制:

CREATE FUNCTION hierarchy_exists(id int) RETURNS BOOL LANGUAGE SQL AS
$$
SELECT COUNT(*) > 0 FROM region_heir_t WHERE parent = $1 or child = $1;
$$;

CREATE OR REPLACE FUNCTION fkey_hierarchy_trigger() RETURNS trigger LANGUAGE PLPGSQL AS
$$
BEGIN
   IF hierarchy_exists(old.place_id) THEN RAISE EXCEPTION 'Hierarchy node still exists';
   ELSE RETURN OLD;
END;
$$;

然后你可以创建你的触发器:

CREATE CONSTRAINT TRIGGER fkey_place_parent AFTER INSERT OR UPDATE TO region_hier_t
FOR EACH ROW EXECUTE PROCEDURE fkey_place_t(new.parent);
CREATE CONSTRAINT TRIGGER fkey_place_child AFTER INSERT OR UPDATE TO region_hier_t
FOR EACH ROW EXECUTE PROCEDURE fkey_place_t(new.child);

然后对于每个 place_t 子表:

CREATE CONSTRAINT TRIGGER fkey_hier_t TO [child_table]
FOR EACH ROW EXECUTE PROCEDURE fkey_hierarchy_trigger();

这个解决方案可能不值得,但如果你需要,知道如何去做是值得的。

于 2013-03-02T14:52:54.257 回答
0

所以事实证明,PostgreSQL 中的继承与典型 OOP 语言中使用的继承有些不同。特别是,“超类”表不会自动填充。如果我必须使用我自己的触发器来做到这一点,我就没有继承结构的用例了。

所以我放弃了 Postgresql 继承并创建了自己的“place_t”表。以及“country_t”、“state_t”、“county_t”和“region_t”子表,通过“place_id”链接到父“place_t”。

在这些子表上,我创建了一个插入/更新前的行级触发器,以确保“place_id”引用“place_t”中的有效行,并且以后不会更改引用。IOW,子表中的“place_id”应该表现得像一次写入多次读取。

现在,我可以插入世界地理。另外,定义一个新的“区域”。我创建了一个“region_composition_t”来记录区域层次结构的边缘,父级是对“region_t”的引用,子级是对“place_t”的引用。

到目前为止,一切都很好。现在的挑战是如何抑制任何更新/删除级联效应。

于 2013-02-07T02:44:57.963 回答