postgresql 如何区分SQLAlchemy中的冲突. ON CONFLICT(Upsert)

eni9jsuy  于 2023-11-18  发布在  PostgreSQL
关注(0)|答案(2)|浏览(200)

我在http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#insert-on-conflict-upsert上阅读了SqlAlchemy关于postgres方言的upsert操作的文档
有没有办法知道upsert是插入还是更新?
文档似乎忽略了这个细节。

uhry853o

uhry853o1#

只需添加最后一个RETURNING子句:

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

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

  • 检测行是否被更新或插入
  • 使用系统列区分UPSERT中插入的行和更新的行

添加OID(就像另一个答案中建议的那样)会增加成本,膨胀表并烧毁OID(如果你的表不是很小的话)。这就是为什么default_with_oids的一般设置很久以前就被更改为off(Postgres 8.1)。Postgres 11的手册:
在用户表中使用OID被认为是不推荐的,因此大多数安装应该禁用此变量。需要特定表的OID的应用程序应该在创建表时指定WITH OIDS
后来的版本甚至完全删除了设置default_with_oids

af7jpaap

af7jpaap2#

从插入的postgresql文档:
在成功完成时,一个命令行命令返回一个命令标记,格式如下:

INSERT oid count

字符串
count是插入或更新的行数。如果count正好为1,并且目标表具有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': '[email protected]', '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对应于查询消息INSERT oid count中的oid
因此,如果第一次执行stmt

r = engine.execute(stmt)


由于创建了新行,r.lastrowid将输出an integer > 0
每隔一次,r.lastrowid将输出0
如果你需要一次跟踪多行的upsert,你可以设置额外的列作为标志,这些标志是从insert语句的on conflict do update部分更新的。
有很多方法可以做到这一点,这取决于具体的要求。这里是一个替代方案。
添加额外列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()}
)

相关问题