我正在阅读SqlAlchemy的文档,有关在http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#insert-on-conflict-upsert上对postgres方言进行upsert操作
有没有办法知道upsert是插入还是更新?
文档似乎忽略了此细节。
答案 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
创建表。