插入触发器最终在分区表中插入重复的行

时间:2014-02-25 21:15:35

标签: sql postgresql postgresql-9.1

我有一个分区表(我认为)适当的INSERT触发器和一些约束。不知何故,INSERT语句为每个INSERT插入2行:一行用于父级,一个用于适当的分区。

简要设置如下:

CREATE TABLE foo (
id SERIAL NOT NULL,
d_id INTEGER NOT NULL,
label VARCHAR(4) NOT NULL);

CREATE TABLE foo_0 (CHECK (d_id % 2 = 0)) INHERITS (foo);
CREATE TABLE foo_1 (CHECK (d_id % 2 = 1)) INHERITS (foo);

ALTER TABLE ONLY foo ADD CONSTRAINT foo_pkey PRIMARY KEY (id);
ALTER TABLE ONLY foo_0 ADD CONSTRAINT foo_0_pkey PRIMARY KEY (id);
ALTER TABLE ONLY foo_1 ADD CONSTRAINT foo_1_pkey PRIMARY KEY (id);

ALTER TABLE ONLY foo ADD CONSTRAINT foo_d_id_key UNIQUE (d_id, label);
ALTER TABLE ONLY foo_0 ADD CONSTRAINT foo_0_d_id_key UNIQUE (d_id, label);
ALTER TABLE ONLY foo_1 ADD CONSTRAINT foo_1_d_id_key UNIQUE (d_id, label);

CREATE OR REPLACE FUNCTION foo_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.id IS NULL THEN
       NEW.id := nextval('foo_id_seq');
    END IF;

    EXECUTE 'INSERT INTO foo_' || (NEW.d_id % 2) || ' SELECT $1.*' USING NEW;
    RETURN NEW;
END
$$
LANGUAGE plpgsql;

CREATE TRIGGER insert_foo_trigger
    BEFORE INSERT ON foo
    FOR EACH ROW EXECUTE PROCEDURE foo_insert_trigger();

在进一步调试后,我隔离了导致它的原因:INSERT触发器返回NEW而不是NULL。但是我确实希望我的insert语句返回自动增量id,如果我只返回NULL,那将不是这种情况。

解决方案是什么?为什么返回NEW导致这种看似“奇怪”​​的行为?

更新#1

好吧,我知道为什么行插入两次,因为从触发器的文档中可以清楚地看到:

  

每个语句触发器调用的触发器函数应始终使用   返回NULL。每行触发器调用的触发器函数可以返回   调用执行程序的表行(类型为HeapTuple的值)if   他们选择。在操作具有之前触发的行级触发器   以下选择:

     

它可以返回NULL以跳过当前行的操作。这个   指示执行程序不执行该行级操作   调用触发器(插入,修改或删除)   特别的表格行。)

     

仅适用于行级INSERT和UPDATE触发器,返回的行   成为将要插入的行或将替换正在行的行   更新。这允许触发器功能修改行   插入或更新。

但我的问题仍然是如何才能返回NEW并仍然可以获得自动递增的idROW_COUNT

更新#2

我找到了一个解决方案,但我确信希望有一个更好的解决方案。基本上,您可以添加AFTER TRIGGER来删除插入父表的行。这看起来非常低效,所以如果有人有更好的解决方案,请发帖!

供参考,解决方案是:

CREATE TRIGGER insert_foo_trigger
    BEFORE INSERT ON foo
    FOR EACH ROW EXECUTE PROCEDURE foo_insert_trigger();


CREATE OR REPLACE FUNCTION foo_delete_master() 
RETURNS TRIGGER AS $$
BEGIN
    DELETE FROM ONLY foo WHERE id = NEW.id;
    RETURN NEW;
END
$$
LANGUAGE plpgsql;

CREATE TRIGGER after_insert_foo_trigger
    AFTER INSERT ON foo
    FOR EACH ROW EXECUTE PROCEDURE foo_delete_master();

2 个答案:

答案 0 :(得分:3)

更简单的方法是创建存储过程而不是触发器,例如add_foo([parameters]),它将决定哪个分区适合插入行并返回id(或新记录值,包括id) 。例如:

CREATE OR REPLACE FUNCTION add_foo(
    _d_id   INTEGER
,   _label  VARCHAR(4)
) RETURNS BIGINT AS $$
DECLARE
    _rec    foo%ROWTYPE;
BEGIN
    _rec.id := nextval('foo_id_seq');
    _rec.d_id := _d_id;
    _rec.label := _label;
    EXECUTE 'INSERT INTO foo_' || ( _d_id % 2 ) || ' SELECT $1.*' USING _rec;
    RETURN _rec.id;
END $$ LANGUAGE plpgsql;

答案 1 :(得分:2)

此问题提供了另一个解决此问题的方法: Postgres trigger-based insert redirection without breaking RETURNING

总之,为您的表创建一个视图,然后您可以使用INSTEAD OF来处理更新,同时仍然可以返回NEW

未经测试的代码,但您明白了这一点:

CREATE TABLE foo_base (
  id SERIAL NOT NULL,
  d_id INTEGER NOT NULL,
  label VARCHAR(4) NOT NULL
);

CREATE OR REPLACE VIEW foo AS SELECT * FROM foo_base;

CREATE TABLE foo_0 (CHECK (d_id % 2 = 0)) INHERITS (foo_base);
CREATE TABLE foo_1 (CHECK (d_id % 2 = 1)) INHERITS (foo_base);

ALTER TABLE ONLY foo_base ADD CONSTRAINT foo_base_pkey PRIMARY KEY (id);
ALTER TABLE ONLY foo_0 ADD CONSTRAINT foo_0_pkey PRIMARY KEY (id);
ALTER TABLE ONLY foo_1 ADD CONSTRAINT foo_1_pkey PRIMARY KEY (id);

ALTER TABLE ONLY foo_base ADD CONSTRAINT foo_base_d_id_key UNIQUE (d_id, label);
ALTER TABLE ONLY foo_0 ADD CONSTRAINT foo_0_d_id_key UNIQUE (d_id, label);
ALTER TABLE ONLY foo_1 ADD CONSTRAINT foo_1_d_id_key UNIQUE (d_id, label);

CREATE OR REPLACE FUNCTION foo_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.id IS NULL THEN
       NEW.id := nextval('foo_base_id_seq');
    END IF;

    EXECUTE 'INSERT INTO foo_' || (NEW.d_id % 2) || ' SELECT $1.*' USING NEW;
    RETURN NEW;
END
$$
LANGUAGE plpgsql;

CREATE TRIGGER insert_foo_trigger
    INSTEAD OF INSERT ON foo
    FOR EACH ROW EXECUTE PROCEDURE foo_insert_trigger();