使用SQLAlchemy Postgres进行批量Upsert

12

我正在按照SQLAlchemy文档这里的指引撰写一个批量更新和插入(Postgres中称为upsert) 的语句。 为了演示,我有一个简单的表MyTable:

class MyTable(base):
    __tablename__ = 'mytable'
    id = Column(types.Integer, primary_key=True)
    test_value = Column(types.Text)

创建一个通用的插入语句很简单:
from sqlalchemy.dialects import postgresql

values = [{'id': 0, 'test_value': 'a'}, {'id': 1, 'test_value': 'b'}]
insert_stmt = postgresql.insert(MyTable.__table__).values(values)

我遇到的问题是在尝试添加upsert的"on conflict"部分时。
update_stmt = insert_stmt.on_conflict_do_update(
    index_elements=[MyTable.id],
    set_=dict(data=values)
)

尝试执行此语句会导致ProgrammingError
from sqlalchemy import create_engine
engine = create_engine('postgres://localhost/db_name')

engine.execute(update_stmt)

>>> ProgrammingError: (psycopg2.ProgrammingError) can't adapt type 'dict'

我认为我的误解在于使用on_conflict_do_update方法构建语句。有没有人知道如何构建这个语句?我已经查看了StackOverflow上的其他问题(例如这里),但似乎找不到解决以上错误的方法。

1个回答

32
update_stmt = insert_stmt.on_conflict_do_update(
    index_elements=[MyTable.id],
    set_=dict(data=values)
)

index_elements应该是一个字符串列表或列对象的列表。因此可以是[MyTable.id]['id'](这是正确的)。

set_应该是一个以列名为键,有效的SQL更新对象为值的字典。您可以使用excluded属性引用插入块中的值。所以在这里得到你所希望的结果的话,你需要 set_ = {'test_value': insert_stmt.excluded.test_value} (你的错误在于在例子中data=不是一个特殊参数...它是他们的示例表格上的列名)。

因此,整个内容如下:

update_stmt = insert_stmt.on_conflict_do_update(
    index_elements=[MyTable.id],
    set_={'test_value': insert_stmt.excluded.test_value}
)

当然,在实际的世界例子中,我通常想改变不止一列。在这种情况下,我会这样做...

update_columns = {col.name: col for col in insert_stmt.excluded if col.name not in ('id', 'datetime_created')}
update_statement = insert_stmt.on_conflict_do_update(index_elements=['id'], set_=update_columns)

(此示例将覆盖除id和datetime_created列以外的所有列)


2
非常好的答案 - 超级清晰易懂。谢谢! - user144153
4
这个优化了我的可怕的alpha代码,从90秒降到了9秒。有人曾告诉我“永远不要在循环中执行SQL语句”,这话一直留在我脑海里。我知道我必须找到更好的方法,所以- 干杯! - deed02392
当我尝试组合update_columns时,我一直收到AttributeError:'Insert'对象没有属性'excluded'的错误。我的插入语句如下:insert_statement = insert(table).values(items)其中table是DeclarativeMeta类型,items是字典列表。任何建议将不胜感激。 - Mike
5
我已经明白了......对于遇到这个问题的人,请特别注意你从哪里导入了插入语句。应该是from sqlalchemy.dialects.postgresql import insert而不是from sqlalchemy import insert。天啊。 - Mike
1
是的,对于像这样的数据库特定结构,您必须直接从方言中导入。 - Paul Becotte
clear and easy. tks - Yang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接