PostgreSQL的规则和nextval()/serial问题(非常针对PostgreSQL)

12

当我使用一个重写规则将插入到一个表的数据拆分成两个表的插入时,其中一个插入的值具有相同的序列(some_sequence)作为默认值,并且对于两个表都是相同的序列,那么这些插入的默认值在两个表中是不同的。这可能是由于重写规则的简单文本替换导致的。我原本希望默认值首先被解析,然后相同的值被写入到两个表中。

以下是一个示例(你可能已经猜到了,我正在尝试使用规则来实现专业化/泛化):

-- first and third commands can be skipped if id is defined as serial
create sequence parents_id_seq;
create table Parents(
  id integer default(nextval('parents_id_seq')) primary key,
  type varchar(50) not null check(type in ('Child1', 'Child2')),
  unique (id, type),
  attribute1 varchar(50) not null unique check(length(attribute1) > 0)
);
alter sequence parents_id_seq owned by parents.id;

第一类儿童的特定数据保存在

create table Partial_Children1(
  id integer default(nextval('parents_id_seq')) primary key,
  type varchar(50) not null check(type = 'Child1'),
  foreign key (id, type) references Parents(id, type),
  attribute2 varchar(50) not null check(length(attribute2) > 0)
);

接下来,我定义了一个视图Children1,它连接了上面两个表(根据文档,我明确说明了PostgreSQL用于定义视图的操作并对视图进行了重写)。

create table Children1(
  id int default(nextval('parents_id_seq')),
  type varchar(50) not null check(type in ('Child1')),
  attribute1 varchar(50) not null check(length(attribute1) > 0),
  attribute2 varchar(50) not null check(length(attribute2) > 0)
);
create rule "_RETURN" as on select to Children1 do instead
  select p.*, c.attribute2
  from Parents p
    join Partial_Children1 c
      on p.id = c.id;

最后我遇到问题的重写规则:

create rule ct_i_children1 as
  on insert to Children1
  do instead (
    insert into Parents(attribute1, type)
      values(new.attribute1, 'Child1');
    insert into Partial_Children1(attribute2, type)
      values(new.attribute2, 'Child1');
  );

尝试使用

INSERT

语句插入数据。

insert into Children1 (attribute1, attribute2)
  values ('a1', 'a2'),
         ('b1', 'b2');

返回错误信息

ERROR:  insert or update on table "partial_children1" violates foreign key constraint "partial_children1_id_fkey"
DETAIL:  Key (id,type)=(3,Child1) is not present in table "parents".
用以下方式解决这个问题是将重写规则的第二个插入替换为:
insert into Partial_Children1(id, attribute2, type)
  select p.id, new.attribute2, p.type
    from Parents p
    where p.attribute1 = new.attribute1

但是这依赖于属性1的唯一性,而我不想强加此限制。另一种解决方案是首先将值插入到临时表中,然后从中选择两次进行插入到两个表中。但出于性能原因,我不喜欢这种方法。

有没有其他想法可以在两个表中获取相同的默认值(仅使用规则而非触发器)?

2个回答

6

来自文档http://www.postgresql.org/docs/8.4/static/rules.html

规则系统会修改查询语句以考虑规则,然后将修改后的查询语句传递给查询计划器进行规划和执行。

因此,它首先重写查询语句而不执行任何操作。

当您一次不插入多条记录时,可以使其工作:

create or replace rule ct_i_children1 as
  on insert to Children1
  do instead (
    insert into Parents(id, attribute1, type)
      values(nextval('parents_id_seq'), new.attribute1, 'Child1');
    insert into Partial_Children1(id, attribute2, type)
      values(currval('parents_id_seq'), new.attribute2, 'Child1');
  );

然后你可以这样做:

insert into Children1 (attribute1, attribute2) values ('a1', 'a2');
insert into Children1 (attribute1, attribute2) values ('b1', 'b2');

但不包括
insert into Children1 (attribute1, attribute2)
  values ('a1', 'a2'),
         ('b1', 'b2');

我建议您不要在使用复杂的currval()调用时使用规则系统。

此外,请查看这些页面上的注释:

另一个提示:postgresql邮件列表的支持与数据库引擎本身一样出色!顺便说一下,您知道postgresql支持开箱即用的继承吗?

总结:您应该使用触发器或避免多行插入!


谢谢提供这些链接。虽然它们没有解决问题,但至少我不再感到孤单了 ;-)内置的继承并没有完全满足我的需求。 - j.p.

0

规则会为您完成这项工作 - 它们在执行查询之前重写查询。

只要您有一个实际的基础表(Children1),我认为您可以使用TRIGGER而不是RULE来完成相同的事情。


嗯,我以为我做了。但我至少错过了“非触发器”部分,对此感到抱歉。 - Magnus Hagander

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接