UPDATE

多亏了贴出的答案,我找到了一个更简单的方法来解决这个问题.原始问题可以在修订历史中看到.

问题

我正在try 将SQL查询转换为Django,但收到一个我不理解的错误.

这是我的Django 模型:

class Title(models.Model):
  title_id = models.CharField(primary_key=True, max_length=12)
  title = models.CharField(max_length=80)
  publisher = models.CharField(max_length=100)
  price = models.DecimalField(decimal_places=2, blank=True, null=True)

我有以下数据:

publisher                    title_id      price  title
---------------------------  ----------  -------  -----------------------------------
New Age Books                PS2106         7     Life Without Fear
New Age Books                PS2091        10.95  Is Anger the Enemy?
New Age Books                BU2075         2.99  You Can Combat    Computer Stress!
New Age Books                TC7777        14.99  Sushi, Anyone?
Binnet & Hardley             MC3021         2.99  The Gourmet Microwave
Binnet & Hardley             MC2222        19.99  Silicon Valley   Gastronomic Treats
算法data Infosystems         PC1035        22.95  But Is It User Friendly?
算法data Infosystems         BU1032        19.99  The Busy Executive's   Database Guide
算法data Infosystems         PC8888        20     Secrets of Silicon Valley

下面是我想要做的:引入一个价格两倍的带注释的字段dbl_price,然后用publisher对结果查询集进行分组,并 for each 出版商计算该出版商出版的所有图书的全部dbl_price个值的总和.

执行此操作的SQL查询如下所示:

SELECT SUM(dbl_price) AS total_dbl_price, publisher
FROM (
  SELECT price * 2 AS dbl_price, publisher
  FROM title
) AS A 
GROUP BY publisher

所需的输出将为:

publisher                    tot_dbl_prices
---------------------------  --------------
算法data Infosystems                 125.88
Binnet & Hardley                      45.96
New Age Books                         71.86 

Django查询

查询如下所示:

Title.objects
 .annotate(dbl_price=2*F('price'))
 .values('publisher')
 .annotate(tot_dbl_prices=Sum('dbl_price'))

但会给出一个错误:

KeyError: 'dbl_price'. 

这表明它在查询集中找不到字段dbl_price.

错误的原因

以下是发生此错误的原因:the documentation says

您还应注意,Average_Rating已明确包括在内 在要返回的值列表中.这是必需的,因为VALUES()和ANNOTATE()子句的顺序.

如果VALUES()子句在ANNOTATE()子句之前,则所有批注 将自动添加到结果集中.但是,如果 如果在ANNOTATE()子句之后应用VALUES()子句,则需要显式包含聚合列.

因此,在聚合中找不到dbl_price,因为它是由先前的annotate创建的,但不包括在values()中.

但是,我也不能将其包含在values中,因为我想使用values(后面跟着另一个annotate)作为分组设备,因为

如果values()子句位于annotate()之前,则将使用values()子句描述的分组来计算注释.

这就是Django implements SQL GROUP BY号的基础.这意味着我不能将dbl_price包含在values()中,因为这样分组将基于字段publisherdbl_price的唯一组合,而我只需要按publisher分组.

因此,下面的查询实际上是有效的,它与上面的不同之处在于我对模型的price字段进行了聚合,而不是对dbl_price字段进行了注释:

Title.objects
 .annotate(dbl_price=2*F('price'))
 .values('publisher')
 .annotate(sum_of_prices=Count('price'))

因为price字段在模型中,而不是一个带注释的字段,所以我们不需要将其包含在values中以将其保留在queryset中.

问题是

因此,现在我们有了它:我需要将带注释的属性包括到values中以将其保留在查询集中,但我不能这样做,因为values也用于分组(如果使用额外的字段,这将是错误的).问题本质上是由于Django中使用values的两种截然不同的方式(values后面是否跟annotate)-即(1)值提取(SQL普通SELECT列表)和(2)分组+组上的聚合(SQL GROUP BY)-在这种情况下,这两种方式似乎是冲突的.

My question is:有没有办法解决这个问题(不用退回到原始SQL)?

Please note:有问题的具体例子可以通过将所有annotate条语句移到values之后来解决,其中有几个答案指出了这一点.然而,我更感兴趣的解决方案(或讨论)将保持annotate个声明之前values()个,原因有三:1.还有一些更复杂的例子,建议的解决方案不起作用.2.我可以想象这样的情况,带注释的queryset被传递给另一个函数,该函数实际上是分组的,因此我们只知道带注释字段的名称集及其类型.3.情况似乎很简单,如果之前没有人注意到和讨论values()两种不同用法的冲突,我会感到惊讶.

推荐答案

Update: Since Django 2.1, everything works out of the box. No workarounds needed and the produced query is correct.

这可能有点晚了,但是我已经找到了解决方案(使用Django 1.11.1进行了测试).

问题是,调用提供分组所需的.values('publisher')会删除.values() fields参数中未包括的所有注释.

我们不能包含dbl_pricefields个参数,因为它会添加另一个GROUP BY语句.

解决方案是进行所有聚合,这首先需要带注释的字段,然后调用.values()并将聚合包含到fields参数(这不会添加GROUP BY,因为它们是聚合). 然后,我们应该使用任何表达式调用.annotate()-这将使Django使用Query-publisher中唯一的非聚合字段将GROUP BY语句添加到SQL查询中.

Title.objects
    .annotate(dbl_price=2*F('price'))
    .annotate(sum_of_prices=Sum('dbl_price'))
    .values('publisher', 'sum_of_prices')
    .annotate(titles_count=Count('id'))

这种方法唯一的缺点是——如果除了带有注释字段的聚合之外,不需要任何其他聚合,那么无论如何都必须包含一些聚合.没有最后一次通话.annotate()(它应该至少包含一个表达式!),Django不会将GROUP BY添加到SQL查询中.解决这个问题的一种方法就是创建一个字段的副本:

Title.objects
    .annotate(dbl_price=2*F('price'))
    .annotate(_sum_of_prices=Sum('dbl_price')) # note the underscore!
    .values('publisher', '_sum_of_prices')
    .annotate(sum_of_prices=F('_sum_of_prices')

另外,请注意,您应该小心进行QuerySet排序.您最好调用.order_by(),或者不带参数来清除排序,或者使用您的GROUP BY字段.如果结果查询将包含按任何其他字段排序,则分组将是错误的. https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

此外,您可能希望从输出中删除该假注释,所以请调用.再说一遍.

Title.objects
    .annotate(dbl_price=2*F('price'))
    .annotate(_sum_of_prices=Sum('dbl_price'))
    .values('publisher', '_sum_of_prices')
    .annotate(sum_of_prices=F('_sum_of_prices'))
    .values('publisher', 'sum_of_prices')
    .order_by('publisher')

Django相关问答推荐

批量删除多对多条目?

如果密码在Django中未被散列,则对其进行散列

如何显示日期?

Urls.py中路径**kwargs的Django翻译?

Django 相当于子查询

如何在不编写每个视图中的逻辑的情况下呈现值,Django?

Django:如何在表单 clean() 方法的 django 验证错误中添加 超链接?

如何在 Django 测试框架中修改会话

在 virtualenv Ubuntu 12.10 中使用 pip 安装 lxml 错误:command 'gcc' failed with exit status 4

Django UrlResolver,在运行时添加 url 进行测试

django 管理员操作而不 Select 对象

更改 Django ModelChoiceField 以显示用户的全名而不是用户名

Table doesn't exist表不存在

Django:在还原(迁移)后try 访问数据库时权限被拒绝

UpdateView 中的success_url,基于传递的值

如何使 Django 的开发服务器公开?

RemovedInDjango18Warning:不推荐创建没有fields属性或 exclude属性的 ModelForm

django select_related - 何时使用它

Django将整数模型字段的范围设置为约束

Django:在模块中实现 status字段的最佳方式