sql - Django 中 GROUP BY 注释的聚合

标签 sql django django-models django-orm

更新

感谢发布的答案,我找到了一种更简单的方法来表述问题。原题可在修改历史中查看。

问题

我正在尝试将 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
Algodata Infosystems         PC1035        22.95  But Is It User Friendly?
Algodata Infosystems         BU1032        19.99  The Busy Executive's   Database Guide
Algodata Infosystems         PC8888        20     Secrets of Silicon Valley

这是我想做的:引入一个带注释的字段 dbl_price,它是价格的两倍,然后按 publisher 对生成的查询集进行分组,并为每个发布者计算该出版商出版的所有图书的所有 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
---------------------------  --------------
Algodata 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

You should also note that average_rating has been explicitly included in the list of values to be returned. This is required because of the ordering of the values() and annotate() clause.

If the values() clause precedes the annotate() clause, any annotations will be automatically added to the result set. However, if the values() clause is applied after the annotate() clause, you need to explicitly include the aggregate column.

因此,dbl_price 无法在聚合中找到,因为它是由先前的 annotate 创建的,但未包含在 values()

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

If the values() clause precedes the annotate(), the annotation will be computed using the grouping described by the values() clause.

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

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

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

因为 price 字段在模型中而不是注释字段,所以我们不需要将它包含在 values 中以将其保留在查询集中.

问题

所以,我们有了它:我需要将带注释的属性包含到 values 中以将其保留在查询集中,但我不能这样做,因为 values 也是用于分组(额外字段会出错)。问题本质上是由于 values 在 Django 中使用的两种非常不同的方式,具体取决于上下文(values 是否后跟 annotate) - 这是 (1) 值提取(SQL plain SELECT list)和 (2) 分组 + 对组的聚合(SQL GROUP BY) - 在这个如果这两种方式似乎冲突。

我的问题是:有什么方法可以解决这个问题(无需回退到原始 sql 之类的东西)吗?

请注意:有问题的具体示例可以通过将所有 annotate 语句移动到 values 之后来解决,这已被多个答案指出。但是,我对将 annotate 语句保留在 values() 之前的解决方案(或讨论)更感兴趣,原因有以下三个:1. 还有更多复杂的示例,其中建议的解决方法不起作用。 2. 我可以想象这样的情况,其中带注释的查询集已传递给另一个函数,该函数实际上执行 GROUP BY,因此我们唯一知道的是带注释的字段的名称集及其类型。 3. 情况似乎很简单,如果以前没有注意到和讨论过 values() 的两种不同用法的冲突,我会感到很惊讶。

最佳答案

更新:从 Django 2.1 开始,一切都开箱即用。无需解决方法,生成的查询是正确的。

这可能有点太晚了,但我找到了解决方案(使用 Django 1.11.1 测试)。

问题是,调用提供分组所需的 .values('publisher') 会删除所有未包含在 .values() 中的注释> 字段参数。

而且我们不能将 dbl_price 包含到 fields 参数中,因为它会添加另一个 GROUP BY 语句。

所有聚合的解决方案,首先需要带注释的字段,然后调用 .values() 并将该聚合包含到 fields 参数(这不会添加GROUP BY,因为它们是聚合)。 然后我们应该用任何表达式调用 .annotate() - 这将使 django 使用查询中唯一的非聚合字段将 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

此外,您可能想从输出中删除该假注解,因此请再次调用 .values()。 因此,最终代码如下所示:

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')

关于sql - Django 中 GROUP BY 注释的聚合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43007595/

相关文章:

sql - 判断月份是否包含条目

mysql - 比较顺序在连接查询中是否重要?

php - Sql高效快速搜索

django 使用链接下载 csv 文件

sql - Hive 插入覆盖表

python - 通过POST创建资源指定相关字段ID

python - 装饰器在 Django 中覆盖 POST、GET 和 REQUEST - 做得对吗?

python - django 和 mysql 'maximum number of connections' 是什么?

Django,查询集中的实际月份

django - 自定义表单作者 - 自动将作者保存到数据库