我有一个分区表,该表具有(我认为)适当的INSERT触发器和一些约束。不知何故,INSERT语句为每行插入2行INSERT:一行用于父行,另一行用于适当的分区。
INSERT
简要的设置如下:
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而不是just NULL。但是,我确实希望我的insert语句返回自动增量,id并且如果我只是返回NULL,则不是这种情况。
NEW
NULL
id
有什么解决方案?为什么返回NEW会导致这种看似“奇怪”的行为?
更新#1
好吧,我知道为什么行插入两次,这从触发器的文档中可以很明显地看出来:
每个语句触发器调用的触发器函数应始终返回NULL。如果选择,逐行触发器调用的触发器函数可以将表行(HeapTuple类型的值)返回给执行者。在操作之前触发的行级触发器具有以下选择: 它可以返回NULL以跳过当前行的操作。这指示执行程序不要执行调用触发器的行级操作(特定表行的插入,修改或删除)。 仅对于行级INSERT和UPDATE触发器,返回的行将成为将要插入的行或将替换要更新的行。这允许触发器功能修改要插入或更新的行。
每个语句触发器调用的触发器函数应始终返回NULL。如果选择,逐行触发器调用的触发器函数可以将表行(HeapTuple类型的值)返回给执行者。在操作之前触发的行级触发器具有以下选择:
它可以返回NULL以跳过当前行的操作。这指示执行程序不要执行调用触发器的行级操作(特定表行的插入,修改或删除)。
仅对于行级INSERT和UPDATE触发器,返回的行将成为将要插入的行或将替换要更新的行。这允许触发器功能修改要插入或更新的行。
但是我的问题仍然是如何不返回NEW并且仍然能够自动递增id,ROW_COUNT例如?
ROW_COUNT
更新#2
我找到了一种解决方案,但我当然希望有更好的解决方案。基本上,您可以添加AFTER TRIGGER来删除插入到父表中的行。这似乎效率极低,因此,如果有人有更好的解决方案,请发布!
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();
一种简单的方法是创建存储过程而不是触发器,例如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;