如何在 RETURNING from INSERT ... ON CONFLICT 中包含排除的行
Posted
技术标签:
【中文标题】如何在 RETURNING from INSERT ... ON CONFLICT 中包含排除的行【英文标题】:How to include excluded rows in RETURNING from INSERT ... ON CONFLICT 【发布时间】:2016-06-27 06:26:21 【问题描述】:我有这张表(由 Django 生成):
CREATE TABLE feeds_person (
id serial PRIMARY KEY,
created timestamp with time zone NOT NULL,
modified timestamp with time zone NOT NULL,
name character varying(4000) NOT NULL,
url character varying(1000) NOT NULL,
email character varying(254) NOT NULL,
CONSTRAINT feeds_person_name_ad8c7469_uniq UNIQUE (name, url, email)
);
我正在尝试使用带有ON CONFLICT
子句的INSERT
批量插入大量数据。
问题是我需要为 所有 行取回 id
,无论它们是否已经存在。
在其他情况下,我会这样做:
INSERT INTO feeds_person (created, modified, name, url, email)
VALUES blah blah blah
ON CONFLICT (name, url, email) DO UPDATE SET url = feeds_person.url
RETURNING id
执行UPDATE
会导致语句返回该行的id
。除了,它不适用于此表。我认为它不起作用,因为我有多个唯一的字段,而在其他情况下,我使用了这种方法,我只有一个唯一的字段。
尝试通过 Django 的游标运行 SQL 时出现此错误:
django.db.utils.ProgrammingError: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
如何使用此表进行批量插入并取回已插入和现有的 id?
【问题讨论】:
I need to get the id back for all of the rows
。显然,您的VALUES
列表中的(name, url, email)
有重复项。你需要保存那些骗子还是可以折叠?如果是,从一组骗子中挑选哪个同伴?您是否必须处理并发写访问?
@ErwinBrandstetter 我每分钟从我定期请求数据的外部数据源转储数万条记录。每次我从外部源请求数据时,它都会返回一些我已经拥有的数据以及新数据。我不需要更新我已经拥有的数据。我不知道为什么它没有点击该错误表明我的 VALUES 中有重复项。 VALUES 中的重复项只能合并为一条记录。
【参考方案1】:
你得到的错误:
ON CONFLICT DO UPDATE 命令不能再次影响行
... 表示您尝试在单个命令中多次插入同一行。换句话说:您在VALUES
列表中对(name, url, email)
进行了欺骗。折叠重复项(如果可以的话),错误就会消失。这会从每组欺骗中选择任意一行:
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
('blah', 'blah', 'blah', 'blah', 'blah')
-- ... more rows
) AS v(created, modified, name, url, email) -- match column list
ON CONFLICT (name, url, email) DO UPDATE
SET url = feeds_person.url
RETURNING id;
由于我们现在使用独立的VALUES
表达式,因此您必须为非默认类型添加显式类型转换。喜欢:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
您的timestamptz
列需要显式类型转换,而字符串类型可以使用默认的text
进行操作。 (您仍然可以立即投射到 varchar(n)
。)
如果你想在哪一行行中有发言权从每组骗子中挑选,有办法做到这一点:
Select first row in each GROUP BY group?你是对的,(目前)没有办法在RETURNING
子句中使用 excluded
列。我引用Postgres Wiki:
请注意,
RETURNING
不会显示“EXCLUDED.*
”别名 来自UPDATE
(只有通用的“TARGET.*
”别名可见 那里)。这样做被认为会为 简单,常见的情况[30] 几乎没有好处。在某些 未来,我们可能会寻求一种暴露的方式,如果RETURNING
-projected 元组被插入和更新,但是这个 可能不需要进入第一次提交的迭代 功能[31]。
但是,您不应该更新不应该更新的行。空更新几乎与常规更新一样昂贵 - 并且可能会产生意想不到的副作用。您并不严格需要 UPSERT 开始,您的案例看起来更像“SELECT 或 INSERT”。相关:
Is SELECT or INSERT in a function prone to race conditions?一个更简洁的插入一组行的方法是使用数据修改 CTE:
WITH val AS (
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
(timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
, ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
-- more (type cast only needed in 1st row)
) v(created, modified, name, url, email)
)
, ins AS (
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT created, modified, name, url, email FROM val
ON CONFLICT (name, url, email) DO NOTHING
RETURNING id, name, url, email
)
SELECT 'inserted' AS how, id FROM ins -- inserted
UNION ALL
SELECT 'selected' AS how, f.id -- not inserted
FROM val v
JOIN feeds_person f USING (name, url, email);
增加的复杂性应该支付给大表,其中INSERT
是规则,SELECT
是例外。
最初,我在最后一个 SELECT
上添加了一个 NOT EXISTS
谓词,以防止结果重复。但那是多余的。 单个查询的所有 CTE 看到相同的表快照。ON CONFLICT (name, url, email) DO NOTHING
返回的集合与同一列上INNER JOIN
之后返回的集合互斥.
不幸的是,这也会打开一个竞态条件的小窗口。如果...
并发事务插入冲突行 尚未提交 但最终会提交...有些行可能会丢失。
您可能只需要INSERT .. ON CONFLICT DO NOTHING
,然后对所有行进行单独的SELECT
查询——在同一个事务中解决这个问题。如果并发事务可以在INSERT
和SELECT
(默认为READ COMMITTED
isolation level)之间提交对表的写入,这反过来又会打开另一个竞争条件的小窗口。可以使用REPEATABLE READ
transaction isolation(或更严格)来避免。或者在整个表上使用(可能昂贵甚至不可接受的)写锁。你可以得到任何你需要的行为,但可能要付出代价。
相关:
How to use RETURNING with ON CONFLICT in PostgreSQL? Return rows from INSERT with ON CONFLICT without needing to update【讨论】:
我回复了你对这个问题的评论,但详细说明我真的不想更新任何东西,我做更新的唯一原因是因为这是我能弄清楚如何获取已经存在的行的 id。换句话说,当我插入大量新数据时,我需要插入的行的 id 以及已经存在的行的 id ......并且我需要它们按照我在 VALUES 中提供它们的顺序。 AFAICT,这就是 PostgrSQL 9.5 所展示的行为……如果这是保证的行为,文档对我来说并不完全清楚。 @DustinWyatt:考虑更新并注意添加的位以防御竞争条件。以上是关于如何在 RETURNING from INSERT ... ON CONFLICT 中包含排除的行的主要内容,如果未能解决你的问题,请参考以下文章
在 PHP 中,当将 PDO 与 pgSQL 一起使用时,如何在原始 INSERT sql 查询中获取“RETURNING”子句的值
“INSERT INTO ... FETCH ALL FROM ...”无法编译
INSERT..RETURNING 在 JOOQ 中不起作用
PostgreSQL v12.6 中的 PLpgSQL INSERT-RETURNING-INTO 错误?