我需要使用 psycopg2 将多行一次性插入 (
INSERT ... ON CONFLICT DO UPDATE
) 到 postgreSQL 数据库中。本质上,我有一个代表“行”的元组列表,我需要将它们插入数据库,或者在存在冲突时更新数据库。我(可能)需要更新每一列(如果未插入)以及每一行。
我尝试了两种主要方法,使用 psycopg2 的
cursor.execute()
函数和 execute_many()
函数。首先,我做了以下事情:
upsert_statement = 'INSERT INTO table (col1, col2, col3) VALUES %s ON CONFLICT (col1) DO UPDATE SET (col1, col2, col3) = ROW (excluded.*) WHERE table IS DISTINCT FROM excluded'
psycopg2.extras.execute_values(cursor, upsert_statement, values)
我创建了一个使用
execute_many()
插入值的 SQL 语句(其中传递给它的 values
是元组列表),并且在发生冲突时,应将列值更新为排除。但是,我收到错误SyntaxError: number of columns does not match number of values
有时,即使我知道事实列数和值是相同的。
所以,我尝试只使用
execute()
:
upsert_statement = f'INSERT INTO table (col1, col2, col3) VALUES (value1, value2, value3), (value4, value5, value6)... ON CONFLICT (col1) DO UPDATE SET (col1, col2, col3) = (value1, value2, value3), (value4, value5, value6)...'
cursor.execute(upsert_statement)
在这里,我将批量更新插入作为 SQL 的一部分,因此不必使用
execute_values()
。但是,我在 SyntaxError
之后得到了 DO UPDATE SET
,因为我认为拥有 (col1, col2, col3) = (value1, value2, value3), (value4, value5, value6)...
是无效的。
我做错了什么?如何使用 psycopg2 批量更新插入多行?
(我要注意的是,实际上,
(col1, col2, col3)
和(value1, value2, value3)
是动态的,并且经常变化)
您需要在 EXCLUDED
语句中使用
table
ON CONFLICT
而不是值文字。这是一个特殊的表,保存建议插入的值。您也不需要重新设置冲突的值,只需重新设置其余的值。
INSERT INTO table (col1, col2, col3)
VALUES
(value1, value2, value3),
(value4, value5, value6)
ON CONFLICT (col1) DO UPDATE
SET (col2, col3) = (EXCLUDED.col2, EXCLUDED.col3);
为了提高可读性,如果对 f 字符串使用三引号,则可以格式化内联 SQL。我不确定是否以及哪些 IDE 可以检测到它是 Python 中的内联 SQL 并切换语法突出显示,但我发现缩进足够有用。
upsert_statement = f"""
INSERT INTO table (col1, col2, col3)
VALUES
({value1}, {value2}, {value3}),
({value4}, {value5}, {value6})
ON CONFLICT (col1) DO UPDATE
SET (col2, col3) = (EXCLUDED.col2, EXCLUDED.col3)"""
这是一个测试:
drop table if exists test_70066823 cascade;
create table test_70066823 (
id integer primary key,
text_column_1 text,
text_column_2 text);
insert into test_70066823 select 1,'first','first';
insert into test_70066823 select 2,'second','second';
select * from test_70066823;
id | text_column_1 | text_column_2 |
---|---|---|
1 | 首先 | 首先 |
2 | 第二 | 第二 |
insert into test_70066823
values (1, 'third','first'),
(3, 'fourth','third'),
(4, 'fifth','fourth'),
(2, 'sixth','second')
on conflict (id) do update
set text_column_1=EXCLUDED.text_column_1,
text_column_2=EXCLUDED.text_column_2;
select * from test_70066823;
id | text_column_1 | text_column_2 |
---|---|---|
1 | 第三 | 首先 |
3 | 第四 | 第三 |
4 | 第五 | 第四 |
2 | 第六 | 第二 |
您可以参考this以提高插入性能。使用简单的基于字符串的
execute
或 execute_many
进行插入是其中提到的最慢的 2 个方法。