PostgreSQL自引用表 - 如何在脚本中存储父ID?

时间:2016-08-23 10:31:06

标签: postgresql parent-child self-reference sql-returning

我是下表:

DROP SEQUENCE IF EXISTS CATEGORY_SEQ CASCADE;
CREATE SEQUENCE CATEGORY_SEQ START 1;

DROP TABLE IF EXISTS CATEGORY CASCADE;

CREATE TABLE CATEGORY (
  ID        BIGINT                 NOT NULL DEFAULT nextval('CATEGORY_SEQ'),
  NAME      CHARACTER VARYING(255) NOT NULL,
  PARENT_ID BIGINT
);

ALTER TABLE CATEGORY
  ADD CONSTRAINT CATEGORY_PK PRIMARY KEY (ID);
ALTER TABLE CATEGORY
  ADD CONSTRAINT CATEGORY_SELF_FK FOREIGN KEY (PARENT_ID) REFERENCES CATEGORY (ID);

现在我需要插入数据。所以我从父母开始:

INSERT INTO CATEGORY (NAME) VALUES ('PARENT_1');

现在我需要刚刚插入的父项的ID来添加子项:

INSERT INTO CATEGORY (NAME, PARENT_ID) VALUES ('CHILDREN_1_1', <what_goes_here>);
INSERT INTO CATEGORY (NAME, PARENT_ID) VALUES ('CHILDREN_1_2', <what_goes_here>);

如何获取并存储父级的ID以便以后在后续插入中使用它?

3 个答案:

答案 0 :(得分:2)

您可以使用带有returning子句的数据修改CTE:

with parent_cat (parent_id) as (
   INSERT INTO CATEGORY (NAME) VALUES ('PARENT_1')
   returning id
)
INSERT INTO CATEGORY (NAME, PARENT_ID) 
VALUES 
  ('CHILDREN_1_1', (select parent_id from parent_cat) ), 
  ('CHILDREN_1_2', (select parent_id from parent_cat) );

答案 1 :(得分:0)

答案是使用RETURNINGWITH

WITH inserted AS (
  INSERT INTO CATEGORY (NAME) VALUES ('PARENT_1')
  RETURNING id
) INSERT INTO CATEGORY (NAME, PARENT_ID) VALUES
  ('CHILD_1_1', (SELECT inserted.id FROM inserted)),
  ('CHILD_2_1', (SELECT inserted.id FROM inserted));

答案 2 :(得分:-1)

tl;dr:转到选项3:使用RETURNING INSERT)

回想一下,在postgresql中没有&#34; id&#34;表格的概念,只是序列(通常但不一定用作代理主键的默认值,SERIAL伪类型)。

如果您有兴趣获取新插入行的ID,可以采用以下几种方法:

选项1:CURRVAL(<sequence name>);

例如:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval('persons_id_seq');

序列的名称必须是已知的,它确实是任意的;在此示例中,我们假设表persons具有使用id伪类型创建的SERIAL列。为了避免依赖于此并感觉更干净,您可以使用pg_get_serial_sequence

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval(pg_get_serial_sequence('persons','id'));

警告:currval()仅适用于INSERT(已执行nextval()),在同一会话中

选项2:LASTVAL();

这与前一个类似,只是您不需要指定序列号:它会查找最近修改的序列(总是在您的会话中,与上面相同的警告)。

CURRVALLASTVAL都是完全并发安全的。 PG中序列的行为被设计为不同的会话不会干扰,因此不存在竞争条件的风险(如果另一个会话在我的INSERT和SELECT之间插入另一行,我仍然得到正确的值)。

然而他们确实存在一个微妙的潜在问题。如果数据库有一些TRIGGER(或RULE),那么在插入persons表时,会在其他表中进行一些额外的插入......那么LASTVAL可能会给我们错误的值。问题甚至可能发生在CURRVAL,如果额外的插入是在同一个persons表中完成的(这通常不太常见,但风险仍然存在)。

选项3:INSERTRETURNING

INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;

这是获取ID的最干净,最有效和最安全的方式。它没有任何前一个风险。

缺点?几乎没有:您可能需要修改调用INSERT语句的方式(在最坏的情况下,可能您的API或DB层不期望INSERT返回值);它不是标准的SQL(谁在乎);它自Postgresql 8.2(2006年12月......)

以来可用

结论:如果可以,请选择选项3.在其他地方,请选择1。

注意:如果您打算获取最后一次全局插入的ID (不一定在您的会话中),则所有这些方法都无用。为此,您必须使用select max(id) from table(当然,这不会读取其他事务中未提交的插入内容)。