在Django中使用ValuesQuerySet的extra()方法

9

我正在尝试使用两个聚合值计算百分比。以下是说明我所需内容的SQL查询:

SELECT (SUM(field_a) / SUM(field_b) * 100) AS percent
FROM myapp_mymodel 
GROUP BY id
ORDER BY id

我尝试使用以下内容构建QuerySet,但不幸的是它不包含额外的字段:

MyModel.objects.values('id').annotate(
   sum_field_a=Sum('field_a'),
   sum_field_b=Sum('field_b')).extra(
      select={'percent': 'sum_field_a / sum_field_b * 100'})

我感到不爽的是,根据Django文档,这似乎是正确的方法:当使用values()子句来限制在结果集中返回的列时,而不是为原始QuerySet中每个结果返回注释结果,原始结果将根据在values()子句中指定的字段的唯一组合进行分组。然后为每个唯一组提供注释;注释是计算整个组成员的结果。如果在extra()子句之后使用values()子句,则extra()中定义的任何字段都必须明确包含在values()子句中。但是,如果在values()之后使用extra()子句,则由select参数添加的字段将自动包含在其中。
3个回答

4
聚合表达式 可以使聚合函数更加容易地使用 Django 1.8,而不需要使用问题多多的 'extra()' 方法。
qs = (
    MyModel.objects.values('id')
    .annotate(percent=Sum('field__a') / Sum('field__b') * 100)
    .order_by('id')
)

>>> print(str(qs.query))
SELECT id, ((SUM(field_a) / SUM(field_b)) * 100) AS percent
FROM app_mymodel GROUP BY id ORDER BY id ASC

提到的问题#15546很快被一条文档说明关闭了,该说明指出在values()之后使用extra()将不起作用-提交a4a250a

我还没有测试这个,因为我之后没有再研究这个问题。但是由于聚合表达式似乎是解决这个问题的方法,我将这个答案标记为我的问题的解决方案。 - jnns
我尝试使用ForeignKey组或从反向关系侧开始的查询集来检查SQL。一个简单的例子是为了回答最好的目的。 - hynekcer

1
如果在extra()子句之后使用values()子句,则extra()中由select参数定义的任何字段都必须在values()子句中显式包含。

来源:http://docs.djangoproject.com/en/dev/ref/models/querysets/#values

可以将select中添加的“percent”字段明确地添加到values子句中,并且应将其添加到查询集中。
MyModel.objects.annotate(
              sum_field_a=Sum('field_a'),
              sum_field_b=Sum('field_b')).extra(
              select={'percent': 'sum_field_a / sum_field_b * 100'}
         ).values('id', 'percent')

结果需要在注释之前进行分组(因此在annotate()之前使用values())。此外,当尝试选择通过注释创建的字段时,我会收到未知列field_a的错误提示。 - jnns
已经编辑了代码片段,将'field_c'替换为'ID',现在可以尝试一下吗? - Thomas

0

正如您指出的(#15546),Django 可能存在一个错误。

但是,作为一种解决方法,您可以像这样将实际计算的负担放在 Python 上,而不是 SQL 数据库:

[{'field_c': model['field_c'],
  'percent': m['sum_field_a'] * 100.0 / m['sum_field_b']}
 for model in MyModel.objects.values('field_c').annotate(
    sum_field_a=Sum('field_a'),
    sum_field_b=Sum('field_b')).order_by('field_c')]

由于这个解决方案强制您循环遍历所有数据,根据您想要做什么,它可能是可接受的,也可能不可接受。


谢谢。我本来希望能避免使用Python,但在修复这个错误之前,这可能是可行的方法。 - jnns

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接