简体   繁体   English

SQLAlchemy中的条件添加语句

[英]conditional add statement in SQLAlchemy

Suppose I want to upload several SQL records, to a table that may not be populated yet. 假设我想将几个SQL记录上载到可能尚未填充的表中。 If there is a record with a primary key("ID") that already exists, either in the table or in the records to be committed to a table, I want to replace the existing record with the new record. 如果在表或要提交到表的记录中已经存在具有主键(“ ID”)的记录,我想用新记录替换现有记录。 I'm using mssql, SQL server 2008. 我正在使用mssql,SQL Server 2008。

My first guess would be 我的第一个猜测是

try:
    session.add(record)
    session.commit
except:
    session.query().\
        filter(Class.ID == record.ID).\
        update(some expression)
    session.commit()       

what should the expression be? 表达式应该是什么? and is there a cleaner(and safer!) way of doing this? 有没有更清洁(更安全!)的方法呢?

In general unless using statements that guarantee atomicity, you'll always have to account for race conditions that might arise from multiple actors trying to either insert or update (don't forget delete). 通常,除非使用保证原子性的语句,否则您将始终必须考虑可能由于多个参与者尝试插入或更新(不要忘记删除)而引起的竞争条件。 Even the MERGE statement , though a single statement, can have race conditions if not used correctly. 即使使用MERGE语句 (即使是单个语句),如果使用不正确也可能具有竞争条件

Traditionally this kind of "upsert" is performed using stored procedures or other SQL or implementation specific features available, like the MERGE statement. 传统上,这种“更新”是使用存储过程或其他SQL或特定于实现的特定功能(例如MERGE语句)执行的。

An SQLAlchemy solution has to either attempt the insert and perform an update if an integrity error is raised, or perform the udpate and attempt an insert if no rows were affected. 如果出现完整性错误,SQLAlchemy解决方案必须尝试插入并执行更新,如果不影响任何行,则执行udpate并尝试插入。 It should be prepared to retry in case both operations fail (a row might get deleted or inserted in between): 如果两个操作都失败(在该行之间可能会删除或插入一行),则应准备重试:

from sqlalchemy.exc import IntegrityError

while True:  # Infinite loop, use a retry counter  if necessary
    try:
        # begin a save point, prevents the whole transaction failing
        # in case of an integrity error
        with session.begin_nested():
            session.add(record)
            # Flush instead of commit, we need the transaction intact
            session.flush()
            # If the flush is successful, break out of the loop as the insert
            # was performed
            break

    except IntegrityError:
        # Attempt the update. If the session has to reflect the changes
        # performed by the update, change the `synchronize_session` argument.
        if session.query(Class).\
                filter_by(ID=record.ID).\
                update({...},
                       syncronize_session=False):
            # 1 or more rows were affected (hopefully 1)
            break

        # Nothing was updated, perhaps a DELETE in between

    # Both operations have failed, retry

session.commit()

Regarding 关于

If there is a record with a primary key("ID") that already exists, either in the table or in the records to be committed to a table, I want to replace the existing record with the new record. 如果在表或要提交到表的记录中已经存在具有主键(“ ID”)的记录,我想用新记录替换现有记录。

If you can be sure that no concurrent updates to the table in question will happen, you can use Session.merge for this kind of task: 如果您可以确定不会对该表进行任何并发更新,则可以将Session.merge用于此类任务:

# Records have primary key set, on which merge can either load existing
# state and merge, or create a new record in session if none was found.
for record in records:
    merged_record = session.merge(record)
    # Note that merged_record is not record

session.commit()

The SQLAlchemy merge will first check if an instance with given primary key exists in the identity map. SQLAlchemy合并将首先检查身份映射中是否存在具有给定主键的实例。 If it doesn't and load is passed as True it'll check the database for the primary key. 如果没有,并且load作为True传递,它将检查数据库中的主键。 If a given instance has no primary key or an instance cannot be found, a new instance will be created. 如果给定实例没有主键或找不到实例,则将创建一个新实例。

The merge will then copy the state of the given instance onto the located/created instance. 然后,合并会将给定实例的状态复制到已定位/创建的实例上。 The new instance is returned. 返回新实例。

No. There is a much better pattern for doing this. 否。这样做有更好的模式。 Do a query first to see if the record already exists, and then proceed accordingly. 首先执行查询以查看记录是否已经存在,然后进行相应的处理。

Using your syntax, it would be something like the following: 使用您的语法,将类似于以下内容:

result = session.query().filter(Class.ID == record.ID).first()

#  If record does not exist in Db, then add record
if result is None:
    try:  
       session.add(record)
       session.commit()
    except:
       db.rollback()
       log.error('Rolling back transaction in query-none block')   

#  If record does exist, then update value of record in Db
else:
   try:
        session.query().\
            filter(Class.ID == record.ID).\
            update(some expression)
        session.commit() 
   except:
       db.rollback()
       log.error('Rolling back transaction')  

It's usually a good idea to wrap your database operations in a try/except block , so you're on the right track with the try-portion of what you wrote. 将数据库操作包装在try / except块中通常是一个好主意,因此您可以对尝试的部分进行正确的编写。 Depending on what you're doing, the except block should typically show you an error message or do a db rollback. 根据您的操作,except块通常应显示一条错误消息或执行数据库回滚。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM