Postgres基于触发器的插入重定向而不会破坏RETURNING

时间:2014-04-08 06:52:36

标签: sql postgresql

我在postgres中使用表继承,但我用来将数据分区到子表中的触发器并不是很正常。例如,此查询返回nil,但我希望它返回新记录的id

INSERT INTO flags (flaggable_id, flaggable_type) 
VALUES (233, 'Thank') 
RETURNING id;

如果我将触发器函数的返回值从NULL更改为NEW,我会得到所需的RETURNING行为,但之后会在数据库中插入两个相同的行。这是有道理的,因为来自触发器函数的非null返回值导致原始INSERT语句执行,而返回NULL会导致语句停止执行。唯一索引可能会暂停第二次插入,但可能会引发错误。

如何使INSERT RETURNINGCREATE TABLE flags ( id integer NOT NULL, flaggable_type character varying(255) NOT NULL, flaggable_id integer NOT NULL, body text ); ALTER TABLE ONLY flags ADD CONSTRAINT flags_pkey PRIMARY KEY (id); CREATE TABLE "comment_flags" ( CHECK ("flaggable_type" = 'Comment'), PRIMARY KEY ("id"), FOREIGN KEY ("flaggable_id") REFERENCES "comments"("id") ) INHERITS ("flags"); CREATE TABLE "profile_flags" ( CHECK ("flaggable_type" = 'Profile'), PRIMARY KEY ("id"), FOREIGN KEY ("flaggable_id") REFERENCES "profiles"("id") ) INHERITS ("flags"); CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$ BEGIN IF (NEW."flaggable_type" = 'Comment') THEN INSERT INTO comment_flags VALUES (NEW.*); ELSIF (NEW."flaggable_type" = 'Profile') THEN INSERT INTO profile_flags VALUES (NEW.*); ELSE RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type"; END IF; RETURN NULL; END; $BODY$ LANGUAGE plpgsql; CREATE TRIGGER flag_insert_trigger BEFORE INSERT ON flags FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun(); 一起正常使用这样的触发器?

{{1}}

2 个答案:

答案 0 :(得分:3)

我找到的唯一解决方法是为基表创建一个视图&在该视图上使用INSTEAD OF触发器:

CREATE TABLE flags_base (
    id integer NOT NULL,
    flaggable_type character varying(255) NOT NULL,
    flaggable_id integer NOT NULL,
    body text
);

ALTER TABLE ONLY flags_base
    ADD CONSTRAINT flags_base_pkey PRIMARY KEY (id);

CREATE TABLE "comment_flags" (
 CHECK ("flaggable_type" = 'Comment'),
 PRIMARY KEY ("id")
) INHERITS ("flags_base");

CREATE TABLE "profile_flags" (
 CHECK ("flaggable_type" = 'Profile'),
 PRIMARY KEY ("id")
) INHERITS ("flags_base");

CREATE OR REPLACE VIEW flags AS SELECT * FROM flags_base;

CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$
BEGIN
  IF (NEW."flaggable_type" = 'Comment') THEN
    INSERT INTO comment_flags VALUES (NEW.*);
  ELSIF (NEW."flaggable_type" = 'Profile') THEN
    INSERT INTO profile_flags VALUES (NEW.*);
  ELSE
    RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type";
  END IF;
  RETURN NEW;
END; $BODY$ LANGUAGE plpgsql;

CREATE TRIGGER flag_insert_trigger
  INSTEAD OF INSERT ON flags
  FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun();

但是这样你必须在每次插入时提供id字段(即使flags_base的主键具有默认值/是序列号),所以你如果是NEW.id,则必须准备插入触发器以修复NULL

更新:看来,视图的列也可以有默认值,设置为

ALTER VIEW [ IF EXISTS ] name ALTER [ COLUMN ] column_name SET DEFAULT expression
仅在视图中使用的

具有插入/更新规则/触发器。

http://www.postgresql.org/docs/9.3/static/sql-alterview.html

答案 1 :(得分:1)

@pozs提供了正确答案,但没有提供完整工作实施的代码。我试图在他的问题的编辑中包含代码,但它不被接受。相反,他提出了另一种方法,它看起来更干净,但可能有一些缺点(在你重新使用其他地方的触发功能的情况下)。

在此处包含我的解决方案以供参考:

CREATE TABLE base_flags (
  id integer NOT NULL,
  flaggable_type character varying(255) NOT NULL,
  flaggable_id integer NOT NULL,
  body text
);

ALTER TABLE ONLY base_flags
  ADD CONSTRAINT base_flags_pkey PRIMARY KEY (id);

CREATE SEQUENCE base_flags_id_seq
  START WITH 1
  INCREMENT BY 1
  NO MINVALUE
  NO MAXVALUE
  CACHE 1;

ALTER SEQUENCE base_flags_id_seq OWNED BY base_flags.id;

CREATE OR REPLACE VIEW flags AS SELECT * FROM base_flags;

CREATE TABLE "comment_flags" (
  CHECK ("flaggable_type" = 'Comment'),
  PRIMARY KEY ("id"),
  FOREIGN KEY ("flaggable_id") REFERENCES "comments"("id")
) INHERITS ("flags");

CREATE TABLE "profile_flags" (
  CHECK ("flaggable_type" = 'Profile'),
  PRIMARY KEY ("id"),
  FOREIGN KEY ("flaggable_id") REFERENCES "profiles"("id")
) INHERITS ("flags");

CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$
  BEGIN
    IF NEW.id IS NULL THEN
      NEW.id := nextval('base_flags_id_seq');
    END IF; 
    IF (NEW."flaggable_type" = 'Comment') THEN
      INSERT INTO comment_flags VALUES (NEW.*);
    ELSIF (NEW."flaggable_type" = 'Profile') THEN
      INSERT INTO profile_flags VALUES (NEW.*);
    ELSE
      RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type";
    END IF;
    RETURN NEW;
  END;
$BODY$
LANGUAGE plpgsql;

CREATE TRIGGER flag_insert_trigger
  INSTEAD OF INSERT ON base_flags
  FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun();