Django 使用表达式聚合查询
Posted
技术标签:
【中文标题】Django 使用表达式聚合查询【英文标题】:Django aggregate queries with expressions 【发布时间】:2012-04-30 13:47:40 【问题描述】:我有一个模型 XYZ,我需要获取给定查询集的字段 a、b 和表达式 x/y 的最大值。
它非常适合田野。比如:
>>> XYZ.all().aggregate(Max('a'))
... 'a__max': 10
但是,我找不到表达式的方法。尝试类似:
>>> XYZ.all().aggregate(Max('x/y'))
给出错误:
*** FieldError: Cannot resolve keyword 'x/y' into field. Choices are: a, b, x, y, id
尝试类似:
>>> XYZ.all().aggregate(Max(F('x')/F('y')))
给出错误:
*** AttributeError: 'ExpressionNode' object has no attribute 'split'
甚至是这样的:
XYZ.all().extra(select='z':'x/y').aggregate(Max('z'))
也不起作用并给出与上述相同的错误:
FieldError: Cannot resolve keyword 'z' into field. Choices are: a, b, x, y, id
我发现这样做的一个技巧是:
XYZ.all().extra(select='z':'MAX(x/y)')[0].z
这确实有效,因为它生成了正确的 SQL,但令人困惑的是,我确实在 z 属性处获得了正确的值,但没有获得正确的实例,即具有该最大值的实例。
当然,我也可以使用带有 extra() 和 order_by() 的原始查询或技巧,但 Django 一直以一种很好的方式支持聚合查询对我来说真的没有意义,但可以'即使有自己的 F 表达式也不支持表达式。
有什么办法吗?
【问题讨论】:
您可能有兴趣知道在聚合中使用F()
对象的能力是part of the upcoming Django 1.8 release。
【参考方案1】:
对于低于 1.8 的版本,您可以通过这种(未记录的)方式实现相同的目标。
Book.objects.all().aggregate(price_per_page=Sum('price_per_page',
field='book_price/book_pages'))
这适用于 Postgres,我不了解 mysql。
来源:Django Aggregation: Summation of Multiplication of two fields
【讨论】:
【参考方案2】:自 Django 1.8 起,您使用 F()
对象的示例应该可以正常工作:
XYZ.all().aggregate(Max(F('x')/F('y')))
有一个 sn-p 演示了 Sum()
和 F()
对象在 Django aggregation cheat sheet 中的聚合:
Book.objects.all().aggregate(price_per_page=Sum(F('price')/F('pages'))
【讨论】:
很高兴知道。谢谢!【参考方案3】:在SQL中,你想要的其实是
SELECT x/y, * FROM XYZ ORDER BY x/y DESC LIMIT 1;
# Or more verbose version of the #1
SELECT x/y, id, a, b, x, y FROM XYZ GROUP BY x/y, id, a, b, x, y ORDER BY x/y DESC LIMIT 1;
# Or
SELECT * FROM XYZ WHERE x/y = (SELECT MAX(x/y) FROM XYZ) LIMIT 1;
因此在 Django ORM 中:
XYZ.objects.extra(select='z':'x/y').order_by('-z')[0]
# Or
XYZ.objects.extra(select='z':'x/y').annotate().order_by('-z')[0]
# Or x/y=z => x=y*z
XYZ.objects.filter(x=models.F('y') * XYZ.objects.extra(select='z':'MAX(x/y)')[0].z)[0]
版本
XYZ.all().extra(select='z':'MAX(x/y)')[0].z
没有正确的 x,y 和实例,因为在所有行中评估 MAX
函数,当没有 GROUP BY
时,因此返回的 QuerySet 中的所有实例将具有与 @987654327 相同的 z
值@。
【讨论】:
对,但目的是获取最大值本身,就像 XYZ.all().aggregate(Max('a')) 的返回值一样,而不是包含它的实例。带有额外选择的版本最接近它。不返回正确的实例是一个令人困惑的副作用,但它会返回正确的值。正如我在开场白中所说,我知道使用 extra 和 order_by 的解决方案,但这些是不可接受的,因为它们需要完整的表格,而不是单次通过。对于 Django 来说,支持单字段而不是表达式的 Max 聚合没有多大意义。 @pjwerneck 您所说的令人困惑的副作用的原因在我回答的最后一段中进行了描述。如果你只想要最大值,XYZ.objects.extra(select='z':'MAX(x/y)')[0].z
就足够了,没有 order_by。甚至直接cursor.execute('SELECT MAX(x/y) from XYZ')
。我同意你的观点,Django 不提供带表达式的聚合,因为它可能比支持单字段 IMO 困难得多。【参考方案4】:
我认为您应该单独获取最大值
result = XYZ.aggregate(Max('x'), Max('y'))
然后划分两个字段
result['x__max'] \ result['y__max']
【讨论】:
这没有任何意义。即使它返回具有最大 x 和 y 对的行,也不一定是最大 x/y。例如,Max(x)/Max(y) 行是 69/16=4,而 max(x/y) 是 8/1=8以上是关于Django 使用表达式聚合查询的主要内容,如果未能解决你的问题,请参考以下文章
无法对包含 MySql 中的聚合或子查询的表达式执行聚合函数