如何使用 SqlAlchemy 进行更新插入?
我有一条记录,如果它不存在,我希望存在于数据库中,如果它已经存在(主键存在),我希望将字段更新到当前状态。这通常称为 upsert。
以下不完整的代码片段演示了可行的方法,但它似乎过于笨拙(特别是如果有更多列)。更好/最好的方法是什么?
Base = declarative_base()
class Template(Base):
__tablename__ = 'templates'
id = Column(Integer, primary_key = True)
name = Column(String(80), unique = True, index = True)
template = Column(String(80), unique = True)
description = Column(String(200))
def __init__(self, Name, Template, Desc):
self.name = Name
self.template = Template
self.description = Desc
def UpsertDefaultTemplate():
sess = Session()
desired_default = Template("default", "AABBCC", "This is the default template")
try:
q = sess.query(Template).filter_by(name = desiredDefault.name)
existing_default = q.one()
except sqlalchemy.orm.exc.NoResultFound:
#default does not exist yet, so add it...
sess.add(desired_default)
else:
#default already exists. Make sure the values are what we want...
assert isinstance(existing_default, Template)
existing_default.name = desired_default.name
existing_default.template = desired_default.template
existing_default.description = desired_default.description
sess.flush()
有没有更好或更简洁的方法来做到这一点?像这样的东西会很棒:
sess.upsert_this(desired_default, unique_key = "name")
虽然 unique_key
kwarg 显然是不必要的(ORM 应该能够轻松地解决这个问题),但我添加它只是因为 SQLAlchemy 倾向于只使用主键。例如:我一直在查看是否 Session.merge 是适用的,但这仅适用于主键,在本例中主键是一个自动增量 id,对于此目的来说并不是很有用。
一个示例用例就是启动可能已升级其默认预期数据的服务器应用程序。即:此更新插入没有并发问题。
I have a record that I want to exist in the database if it is not there, and if it is there already (primary key exists) I want the fields to be updated to the current state. This is often called an upsert.
The following incomplete code snippet demonstrates what will work, but it seems excessively clunky (especially if there were a lot more columns). What is the better/best way?
Base = declarative_base()
class Template(Base):
__tablename__ = 'templates'
id = Column(Integer, primary_key = True)
name = Column(String(80), unique = True, index = True)
template = Column(String(80), unique = True)
description = Column(String(200))
def __init__(self, Name, Template, Desc):
self.name = Name
self.template = Template
self.description = Desc
def UpsertDefaultTemplate():
sess = Session()
desired_default = Template("default", "AABBCC", "This is the default template")
try:
q = sess.query(Template).filter_by(name = desiredDefault.name)
existing_default = q.one()
except sqlalchemy.orm.exc.NoResultFound:
#default does not exist yet, so add it...
sess.add(desired_default)
else:
#default already exists. Make sure the values are what we want...
assert isinstance(existing_default, Template)
existing_default.name = desired_default.name
existing_default.template = desired_default.template
existing_default.description = desired_default.description
sess.flush()
Is there a better or less verbose way of doing this? Something like this would be great:
sess.upsert_this(desired_default, unique_key = "name")
although the unique_key
kwarg is obviously unnecessary (the ORM should be able to easily figure this out) I added it just because SQLAlchemy tends to only work with the primary key. eg: I've been looking at whether Session.merge would be applicable, but this works only on primary key, which in this case is an autoincrementing id which is not terribly useful for this purpose.
A sample use case for this is simply when starting up a server application that may have upgraded its default expected data. ie: no concurrency concerns for this upsert.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
SQLAlchemy 通过
on_conflict_do_update()
和on_conflict_do_nothing()
两种方法支持ON CONFLICT
。从文档复制:
SQLAlchemy supports
ON CONFLICT
with two methodson_conflict_do_update()
andon_conflict_do_nothing()
.Copying from the documentation:
SQLAlchemy 确实有“保存或更新”行为,在最近的版本中已内置到
session.add
中,但以前是单独的session.saveorupdate
调用。这不是“更新插入”,但它可能足以满足您的需求。您询问具有多个唯一键的类是件好事;我相信这正是没有单一正确方法可以做到这一点的原因。主键也是唯一键。如果没有唯一约束,只有主键,这将是一个足够简单的问题:如果不存在给定 ID 的任何内容,或者如果 ID 为 None,则创建一条新记录;否则使用该主键更新现有记录中的所有其他字段。
然而,当存在额外的独特约束时,这种简单的方法就会出现逻辑问题。如果您想“更新插入”一个对象,并且对象的主键与现有记录匹配,但另一个唯一列与不同记录匹配,那么您该怎么办?同样,如果主键不匹配任何现有记录,但另一个唯一列确实匹配现有记录,那么怎么办?对于您的特定情况可能有一个正确的答案,但总的来说,我认为没有单一的正确答案。
这就是没有内置“更新插入”操作的原因。应用程序必须定义这在每种特定情况下的含义。
SQLAlchemy does have a "save-or-update" behavior, which in recent versions has been built into
session.add
, but previously was the separatesession.saveorupdate
call. This is not an "upsert" but it may be good enough for your needs.It is good that you are asking about a class with multiple unique keys; I believe this is precisely the reason there is no single correct way to do this. The primary key is also a unique key. If there were no unique constraints, only the primary key, it would be a simple enough problem: if nothing with the given ID exists, or if ID is None, create a new record; else update all other fields in the existing record with that primary key.
However, when there are additional unique constraints, there are logical issues with that simple approach. If you want to "upsert" an object, and the primary key of your object matches an existing record, but another unique column matches a different record, then what do you do? Similarly, if the primary key matches no existing record, but another unique column does match an existing record, then what? There may be a correct answer for your particular situation, but in general I would argue there is no single correct answer.
That would be the reason there is no built in "upsert" operation. The application must define what this means in each particular case.
如今,SQLAlchemy 提供了两个有用的函数
on_conflict_do_nothing
和on_conflict_do_update
< /a>.这些函数很有用,但需要您从 ORM 接口切换到较低级别的接口 - SQLAlchemy 核心。尽管这两个函数使得使用 SQLAlchemy 语法进行更新插入并不那么困难,但这些函数远未提供完整的开箱即用的更新插入解决方案。
我的常见用例是在单个 SQL 查询/会话执行中更新插入大量行。我通常会在更新插入时遇到两个问题:
例如,我们已经习惯的更高级别的 ORM 功能缺失。您不能使用 ORM 对象,而必须在插入时提供
ForeignKey
。我正在使用这个以下我编写的函数来处理这两个问题:
Nowadays, SQLAlchemy provides two helpful functions
on_conflict_do_nothing
andon_conflict_do_update
. Those functions are useful but require you to swich from the ORM interface to the lower-level one - SQLAlchemy Core.Although those two functions make upserting using SQLAlchemy's syntax not that difficult, these functions are far from providing a complete out-of-the-box solution to upserting.
My common use case is to upsert a big chunk of rows in a single SQL query/session execution. I usually encounter two problems with upserting:
For example, higher level ORM functionalities we've gotten used to are missing. You cannot use ORM objects but instead have to provide
ForeignKey
s at the time of insertion.I'm using this following function I wrote to handle both of those issues:
我使用“三思而后行”的方法:
优点是这是与数据库无关的,而且我认为读起来很清楚。缺点是在如下场景中存在潜在的竞争条件:
switch_command
但没有找到switch_command
switch_command
switch_command
I use a "look before you leap" approach:
The advantage is that this is db-neutral and I think it's clear to read. The disadvantage is that there's a potential race condition in a scenario like the following:
switch_command
and don't find oneswitch_command
switch_command
with the same primary key as oursswitch_command
下面的内容对我来说适用于 redshift 数据库,并且也适用于组合主键约束。
来源:此
在创建 SQLAlchemy 引擎时只需进行一些修改功能
def start_engine()
The below works fine for me with redshift database and will also work for combined primary key constraint.
SOURCE : this
Just few modifications required for creating SQLAlchemy engine in the function
def start_engine()
有多个答案,这里还有另一个答案(YAA)。由于涉及元编程,其他答案不那么可读。 的示例
使用 SQLAlchemy ORM
显示如何在零行的情况下使用
on_conflict_do_nothing
创建行显示如何更新现有行(如果有)而不使用
on_conflict_do_update
创建新行使用表主键作为
约束
原始问题此代码与什么相关。
There are multiple answers and here comes yet another answer (YAA). Other answers are not that readable due to the metaprogramming involved. Here is an example that
Uses SQLAlchemy ORM
Shows how to create a row if there are zero rows using
on_conflict_do_nothing
Shows how to update the existing row (if any) without creating a new row using
on_conflict_do_update
Uses the table primary key as the
constraint
A longer example in the original question what this code is related to.
这允许根据字符串名称访问底层模型
This allows access to the underlying models based on string names
这对我来说适用于 sqlite3 和 postgres。尽管它可能因组合主键约束而失败,并且很可能因附加唯一约束而失败。
This works for me with sqlite3 and postgres. Albeit it might fail with combined primary key constraints and will most likely fail with additional unique constraints.
由于我们在生成的默认 ID 和引用方面遇到问题,从而导致foreignkeyviolation错误,例如
我们必须排除更新字典的 id,否则它将始终生成为新的默认值。
此外,该方法还返回创建/更新的实体。
示例:
注意:仅在 postgresql 上进行了测试,但也适用于支持重复键更新的其他数据库,例如 MySQL
As we had problems with generated default-ids and references which lead to ForeignKeyViolation-Errors like
we had to exclude the id for the update dict, as otherwise the it will be always generated as new default value.
In addition the method is returning the created/updated entity.
Example:
Note: Only tested on postgresql but could work also for other DBs which support ON DUPLICATE KEY UPDATE e.g. MySQL
对于 sqlite,在定义
UniqueConstraint
时可以使用sqlite_on_conflict='REPLACE'
选项,并使用sqlite_on_conflict_unique
定义单列上的唯一约束。然后session.add
将以类似于upsert
的方式工作。请参阅官方文档 。In case of sqlite, the
sqlite_on_conflict='REPLACE'
option can be used when defining aUniqueConstraint
, andsqlite_on_conflict_unique
for unique constraint on a single column. Thensession.add
will work in a way just likeupsert
. See the official documentation.我使用此代码进行更新插入
在使用此代码之前,您应该向数据库中的表添加主键。
I use this code for upsert
Before using this code, you should add primary keys to table in database.
我使用了以下模式(我的用例更简单),
I used the following pattern(my usecase more simple),