如何区分sqlalchemy INSERT…ON CONFLICT中的冲突(Upsert)

时间:2018-07-09 13:49:55

标签: python postgresql sqlalchemy

我正在阅读SqlAlchemy的文档,有关在http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#insert-on-conflict-upsert上对postgres方言进行upsert操作

有没有办法知道upsert是插入还是更新?

文档似乎忽略了此细节。

2 个答案:

答案 0 :(得分:2)

摘自insert的Postgresql文档:

  

成功完成后,INSERT命令返回格式为

的命令标签
INSERT oid count
     

计数是插入或更新的行数。如果count恰好为1,并且目标表具有OID,则oid是分配给插入行的OID。单行必须已插入而不是已更新。否则,oid为零。

因此可以通过检查查询消息来检查更新还是插入

可以通过OIDs使用语法

创建表
CREATE TABLE mytable (...) WITH OIDS 

OIDs可以使用语法

在现有表上启用
ALTER TABLE mytable SET WITH OIDS

使用sqlalchemy,可以使用OIDs来创建表,如下所示:

import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = sa.create_engine('postgresql+psycopg2://user:pass@hostname:port/db')

class Person(Base):
    __tablename__ = 'people'
    __table_args__ = {'postgresql_with_oids': True}
    email = sa.Column(sa.Text, primary_key=True)
    name = sa.Column(sa.Text, nullable=False)

Base.metadata.create_all(engine)

冲突声明中的插入可以这样构造:

p = {'email': 'hal@hal.hal', 'name': 'Hally hal'}
stmt = pg_insert(Person).values(p)
stmt = stmt.on_conflict_do_update(
    index_elements = [Person.email],
    set_ = {'name': stmt.excluded.name}
)

最后,执行该语句后,将返回结果代理,该代理具有属性lastrowid,该属性与查询消息oid中的INSERT oid count

因此,如果您是第一次执行stmt

r = engine.execute(stmt)
由于创建了新行,

r.lastrowid将输出an integer > 0

每隔一次r.lastrowid就会输出0


如果您需要一次跟踪多个行的向上插入,则可以将额外的列设置为标记,这些标记是从insert语句的onflict do update部分更新的。

有许多方法可以执行此操作,具体取决于确切的要求。这是1种选择。

添加额外的列conflict_updated_at = sa.Column(sa.Datetime(True))

将upsert定义更改为

stmt = pg_insert(Person).values(p)
stmt = stmt.on_conflict_do_update(
    index_elements = [Person.email],
    set_ = {'name': stmt.excluded.name,
            'conflict_updated_at': sa.func.now()}
)

答案 1 :(得分:2)

只需添加带有以下内容的最终RETURNING子句:

...
RETURNING (tbl.xmax = 0) AS inserted

对于插入的行返回true,对于更新的行返回false。但是,这依赖于未记录的实现细节。有关详细说明,请参见:

添加OID(如在另一个答案中建议的那样)会增加成本,膨胀表并刻录OID(如果您的表不是很小的话)。这就是为什么default_with_oids的常规设置很久以前已更改为off的原因(Postgres 8.1)。 Quoting the manual:

  

不建议在用户表中使用OID,因此大多数   安装应禁用此变量。的应用   需要特定表的OID时应指定WITH OIDS   创建表。