我使用的是Postgres 9.2和SqlAlchemy。目前,这是我的代码,用于更新我的数据库中Thing
的排名:
lock_things = session.query(Thing).\
filter(Thing.group_id == 4).\
with_for_update().all()
tups = RankThings(lock_things) # return sorted tuple (<numeric>, <primary key Thing id>)
rank = 1
for prediction, id in tups:
thing = session.query(Thing).\
filter(Thing.group_id == 4).\
filter(Thing.id == id).one()
thing.rank = rank
rank += 1
session.commit()
然而,这似乎很慢。它也是我想成为原子的东西,我为什么使用with_for_update()
语法。
我觉得必须有一种方法来拉链&#34;查询,以这种方式更新。
如何在一次查询中更快地完成此操作?
编辑:我想我需要创建一个临时表来加入并进行快速更新,请参阅:
https://stackoverflow.com/a/20224370/712997
http://tapoueh.org/blog/2013/03/15-batch-update
如何在SqlAlchemy中执行此操作?
答案 0 :(得分:2)
一般来说,通过这种操作,您可以做两件事:
不要在循环中执行查询
通过在SQL端执行计算来减少所需的查询数
此外,如果可能,您可能希望合并一些查询。
让我们从2)开始,因为这是非常具体的,通常不容易实现。通常,这里最快的操作是编写返回排名的单个查询。这有两种选择:
查询可以快速运行,因此您只需在需要排名时执行它。这将是一个非常简单的例子:
SELECT
thing.*,
(POINTS_QUERY) as score
FROM thing
ORDER BY score DESC
在这种情况下,这将通过一些人工评分给你一个有序的事物清单(例如,如果你建立某种竞争)。 POINTS_QUERY
将使用子查询中的特定thing
来确定其分数,例如汇总已解决的所有任务的点数。
在SQLAlchemy中,这看起来像这样:
score = session.query(func.sum(task.points)).filter(task.thing_id == Thing.id).correlate(Thing).label("score")
thing_ranking = session.query(thing, score).order_by(desc("score"))
这是SQLAlchemy的一些更高级的用法:我们构造一个子查询,返回我们标注score
的标量值。使用correlate
,我们告诉它thing
将来自外部查询(这很重要)。
因此,您可以运行单个查询,为您提供排名(根据列表中的索引确定排名,并取决于您的ranking strategy)。如果你能做到这一点,那就是最好的情况
您想要缓存的值,查询本身很昂贵。这意味着您可以使用上面的解决方案并将值缓存在数据库之外(例如在dict中或使用缓存库)。或者您像上面一样计算它们但更新数据库字段(如Thing.rank
)。同样,上面的查询给我们排名。另外,我假设最简单的排名:索引表示排名:
for rank, (thing, score) in enumerate(thing_ranking):
thing.rank = rank
请注意我如何使用enumerate
根据索引建立排名。另外,我利用了这个事实,因为我刚刚查询thing
,我已经在会话中拥有它,所以不需要额外的查询。所以这可能是你的解决方案,但请继续阅读其他信息。
使用上面的最后一个想法,我们现在可以解决1):在循环外获取查询。一般来说,我注意到你将一个事物列表传递给一个只能返回ID的排序函数。为什么?如果您可以更改它,请将其作为一个整体返回。
但是,您可能无法更改此功能,因此如果我们无法更改此功能,请考虑我们的工作。我们已经列出了所有相关内容。我们得到了他们的ID的排序列表。那么为什么不建立dict
作为ID的查找 - &gt;事?
things_dict = dict(thing.id, thing for thing in lock_things)
我们可以使用这个dict而不是在循环内查询:
for prediction, id in tups:
thing = things_dict[id]
然而,可能(由于某些原因,我在您的示例中错过了)并非所有ID都先前已返回。在这种情况下(或者一般情况下),您可以利用SQLAlchemy保留的类似映射:您可以要求它提供主键,如果数据库已经拥有它,它将不查询数据库:
for prediction, id in tups:
thing = session.query(Thing).get(id)
这样我们就可以减少问题,只对我们不具备的对象执行查询。
最后一件事:如果我们没有大部分内容怎么办?然后我没有解决你的问题,我只是替换了查询。在这种情况下,您将不得不创建一个新查询来获取所需的所有元素。一般来说,这取决于ID的来源以及如何确定它们,但是你总是可以采用效率最低的方式(这仍然比内部循环查询更快):使用SQL IN
:
all_things = session.query(Thing).filter(Thing.group_id == 4).filter(Thing.id.in_([id for _, id in tups]).all()
这将构建一个使用IN
关键字进行过滤的查询。但是,由于存在大量事物,因此效率非常低,因此如果您处于这种情况下,最好在SQL中构建一种更有效的方法来确定这是否是您想要的ID。
所以这是一篇很长的文字。总结一下:
如果您可以在那里有效地编写SQL中的查询
使用SQLAlchemy的优势,例如:创建子查询
尝试永远不要在循环中执行查询
为自己创建一些映射(或者使用SQLAlchemy的映射)
以pythonic的方式做:保持简单,明确。
最后一个想法:如果您的查询变得非常复杂并且您担心您无法控制ORM执行的查询,请将其删除并使用Core。它几乎和ORM一样棒,并且在您自己构建查询时可以对查询进行大量控制。有了这个,您几乎可以构建任何您能想到的SQL查询,并且我确信您提到的批量更新也可以在这里(如果您看到上面的查询导致了许多UPDATE
语句,您可能想要使用核心)。