SQLAlchemy在一个事务中更新多行

时间:2019-01-25 13:02:57

标签: python sqlalchemy sql-update

如何使用字典将一列的现有值映射到另一列的所需新值,来更新数据库中的多个现有行?

我有一张桌子:

class MyTable(BaseModel):
    col1 = sa.Column(sa.String(256))
    col2 = sa.Column(sa.String(256))

鉴于col1已具有值且col2为空,如果我将数据集作为字典,该如何更新col2

payload = {'x': 'y', 'a': 'b', 'c': 'd'}

因此,此有效负载将col1的值映射到col2 new 值;更新后,您将从数据库中获得[{'col1': 'x', 'col2': 'y'}, ...]

我尝试了几种方法,这些方法实际上可以工作,但是我认为它们并不像以前那样理想。

my_table = MyTable.__table__
for key, value in payload.items():
    stm = my_table.update()
    stm = stm.where(getattr(sales_order_item.c, 'col1') == key)
    stm = stm.values({'col2': value})
    session.execute(stm)

或者这样

for key, value in payload.items():
    query = session.query(MyTable).filter(MyTable.col1==key)
    query.update({MyTable.col2: value})

现在这两种解决方案都能按预期工作,唯一困扰我的是时间,例如,100个元素的有效负载需要花费6秒钟,而且我几乎可以肯定应该有一个更好的方法,不是吗?

我在考虑是否有一种方法可以使其与in_函数一起工作:

query(MyTable).filter(
        MyTable.col1.in_(payload.keys())
    )

但是我不知道如何构造更新查询。

2 个答案:

答案 0 :(得分:2)

是的,用单个批量UPDATE语句更新大量行比在每个对象上使用单独的UPDATE快得多。 IN过滤器只会帮助您限制要更新的行,但是您仍然需要告诉数据库要用于col2更新的值。

为此,您可以使用case() functionCASE ... WHEN ... THEN构造:

from sqlalchemy.sql import case

query(MyTable).filter(
    MyTable.col1.in_(payload)
).update({
    MyTable.col2: case(
        payload,
        value=MyTable.col1,
    )
}, synchronize_session=False)

上面的a)选择col1值是payload字典中的键的行,然后b)使用col2语句更新CASE列值,从相同的字典中选择值,以根据与键的匹配col1来更新该列。

payload设置为{'x': 'y', 'a': 'b', 'c': 'd'}的情况下,以上代码执行以下查询(在WHEN测试中赋予IN子句和值的确切顺序或值): / p>

UPDATE mytable
SET
    col2=CASE mytable.col1
        WHEN 'x' THEN 'y'
        WHEN 'a' THEN 'b'
        WHEN 'c' THEN 'd'
    END
WHERE
    mytable.col1 IN ('x', 'a', 'c')

我在此处将synchronize_session设置为False,因为在更新大量行时,一次更新所有可能的缓存MyTable实例可能不是最好的主意。您的其他选项是'evaluate''fetch'

  • 我们无法使用默认的'evaluate'(它将在会话中找到与where子句匹配的现有对象以进行就地更新),因为SQLAlchemy当前不提供知道如何处理IN过滤器(您会得到UnevaluatableError异常)。

  • 如果您确实使用'fetch',则会话中缓存的所有受影响的MyTable实例都将使用col2的新值进行更新(由其主键映射)

请注意,无论如何提交都会使会话 过期,因此,如果您需要对更新的行进行更多处理,然后再提交,则只想使用'fetch'。当前交易。

有关您拥有的synchronize_session选项的更多信息,请参见Query.update() documentation

答案 1 :(得分:0)

我在选择的答案之前找到的另一个可行的解决方案也是:

# payload = {'x': 'y', 'a': 'b', 'c': 'd'}
all_rows = query(MyTable).filter(
    MyTable.col1.in_(payload)
)
for row in all_rows:
    row.col2=payload[row.col1]

这虽然可以完成SELECT,但会增加几秒钟的时间,但是对于发现整行并更灵活的人来说很有帮助。