PostgreSQL 自引用 table - 如何在脚本中存储 parent ID?
PostgreSQL self referential table - how to store parent ID in script?
我有以下 table:
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);
现在我需要插入数据。所以我从 parent:
开始
INSERT INTO CATEGORY (NAME) VALUES ('PARENT_1');
现在我需要刚刚插入的parent的ID来添加children:
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>);
如何获取和存储 parent 的 ID,以便稍后在后续插入中使用它?
( tl;dr
: 转到选项 3: INSERT with RETURNING )
回想一下,在 postgresql 中没有 table 的 "id" 概念,只有 序列 (通常但不一定用作默认值代理主键,使用 SERIAL 伪类型)。
如果您有兴趣获取新插入行的 id,有以下几种方法:
选项 1:CURRVAL(<sequence name>);
。
例如:
INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
SELECT currval('persons_id_seq');
序列的名字一定要知道,真的很随意;在此示例中,我们假设 table persons
具有使用 SERIAL
伪类型创建的 id
列。为了避免依赖于此并感觉更干净,您可以改用 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();
这与之前的类似,只是您不需要指定序列号:它会查找最近修改的序列(总是在您的会话中,与上面的警告相同)。
CURRVAL
和 LASTVAL
都是完全并发安全的。 PG 中 sequence 的行为是为了让不同的会话不会干扰,所以不存在竞争条件的风险(如果另一个会话在我的 INSERT 和我的 SELECT 之间插入另一行,我仍然得到我的正确值)。
但是他们确实有一个微妙的潜在问题。如果数据库有一些 TRIGGER(或规则),在插入 persons
table 时,会在其他 table 中进行一些额外的插入...那么 LASTVAL
可能会给我们错误的价值。如果在 persons
table 中进行了额外的插入,CURRVAL
甚至可能会出现此问题(这种情况不太常见,但风险仍然存在)。
选项 3:INSERT
与 RETURNING
INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;
这是最干净、高效和安全的获取 ID 的方法。它没有以前的任何风险。
缺点?几乎 none:您可能需要修改调用 INSERT 语句的方式(在最坏的情况下,您的 API 或 DB 层可能不期望 INSERT 到 return 一个值);这不是标准 SQL(谁在乎);自 Postgresql 8.2(2006 年 12 月...)起可用
结论:如果可以,选择选项 3。在其他地方,首选 1。
注意:如果您打算获取最后全局插入的 id(不一定在您的会话中),所有这些方法都是无用的。为此,您必须求助于 select max(id) from table
(当然,这不会从其他事务中读取未提交的插入)。
您可以使用带有 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) );
答案是使用 RETURNING
和 WITH
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));
我有以下 table:
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);
现在我需要插入数据。所以我从 parent:
开始INSERT INTO CATEGORY (NAME) VALUES ('PARENT_1');
现在我需要刚刚插入的parent的ID来添加children:
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>);
如何获取和存储 parent 的 ID,以便稍后在后续插入中使用它?
( tl;dr
: 转到选项 3: INSERT with RETURNING )
回想一下,在 postgresql 中没有 table 的 "id" 概念,只有 序列 (通常但不一定用作默认值代理主键,使用 SERIAL 伪类型)。
如果您有兴趣获取新插入行的 id,有以下几种方法:
选项 1:CURRVAL(<sequence name>);
。
例如:
INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
SELECT currval('persons_id_seq');
序列的名字一定要知道,真的很随意;在此示例中,我们假设 table persons
具有使用 SERIAL
伪类型创建的 id
列。为了避免依赖于此并感觉更干净,您可以改用 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();
这与之前的类似,只是您不需要指定序列号:它会查找最近修改的序列(总是在您的会话中,与上面的警告相同)。
CURRVAL
和 LASTVAL
都是完全并发安全的。 PG 中 sequence 的行为是为了让不同的会话不会干扰,所以不存在竞争条件的风险(如果另一个会话在我的 INSERT 和我的 SELECT 之间插入另一行,我仍然得到我的正确值)。
但是他们确实有一个微妙的潜在问题。如果数据库有一些 TRIGGER(或规则),在插入 persons
table 时,会在其他 table 中进行一些额外的插入...那么 LASTVAL
可能会给我们错误的价值。如果在 persons
table 中进行了额外的插入,CURRVAL
甚至可能会出现此问题(这种情况不太常见,但风险仍然存在)。
选项 3:INSERT
与 RETURNING
INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;
这是最干净、高效和安全的获取 ID 的方法。它没有以前的任何风险。
缺点?几乎 none:您可能需要修改调用 INSERT 语句的方式(在最坏的情况下,您的 API 或 DB 层可能不期望 INSERT 到 return 一个值);这不是标准 SQL(谁在乎);自 Postgresql 8.2(2006 年 12 月...)起可用
结论:如果可以,选择选项 3。在其他地方,首选 1。
注意:如果您打算获取最后全局插入的 id(不一定在您的会话中),所有这些方法都是无用的。为此,您必须求助于 select max(id) from table
(当然,这不会从其他事务中读取未提交的插入)。
您可以使用带有 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) );
答案是使用 RETURNING
和 WITH
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));