Django

时间:2017-03-24 19:23:46

标签: 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分组,并为每个发布商计算所有{{}的总和1}}该发布商发布的所有图书的值。

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

dbl_price

所需的输出是:

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

Django查询

查询看起来像:

publisher                    tot_dbl_prices
---------------------------  --------------
Algodata Infosystems                 125.88
Binnet & Hardley                      45.96
New Age Books                         71.86 

但是出错了:

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

表示它无法在查询集中找到字段KeyError: 'dbl_price'.

错误原因

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

  

您还应注意,average_rating已明确包含在内        在要返回的值列表中。这是必需的,因为values()和annotate()子句的排序。

     

如果values()子句在annotate()子句之前,则为任何注释        将自动添加到结果集中。但是,如果        values()子句在annotate()子句之后应用,需要显式包含聚合列。

因此,在汇总中找不到dbl_price,因为它是由之前的dbl_price创建的,但未包含在annotate中。

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

  

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

这是Django implements SQL GROUP BY的基础。这意味着我无法在annotate内包含dbl_price,因为这样的分组将基于values()publisher两个字段的唯一组合,而我需要仅按dbl_price分组。

因此,以下查询仅与模型的publisher字段而不是带注释的price字段进行汇总,但与上述不同,实际上有效:

dbl_price

因为Title.objects .annotate(dbl_price=2*F('price')) .values('publisher') .annotate(sum_of_prices=Count('price')) 字段位于模型中而不是带注释的字段,因此我们不需要将其包含在price中以将其保留在查询集中。

问题

所以,我们在这里得到它:我需要在values中包含带注释的属性以将其保留在查询集中,但我无法做到这一点,因为values也用于分组(一个额外的字段会出错)。问题主要是由于在Django中使用values的两种截然不同的方式,取决于上下文(values后面跟values) - 这是(1)值提取(SQL plain annotate list)和(2)对组进行分组+聚合(SQL SELECT) - 在这种情况下,这两种方式似乎发生冲突。

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

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

4 个答案:

答案 0 :(得分:13)

这可能有点太晚了,但我找到了解决办法(用Django 1.11.1测试过)。

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

我们无法将dbl_price包含在字段 param中,因为它会添加另一个GROUP BY语句。

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

GROUP BY

这种方法的唯一减号 - 如果你不需要任何其他聚合,除了带有注释字段的聚合 - 你必须包括一些。如果没有最后一次调用.annotate()(并且它应该包含至少一个表达式!),Django将不会将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')) 添加到SQL查询中。处理此问题的一种方法是创建您的字段的副本:

GROUP BY

另外,请注意,您应该小心QuerySet排序。您最好在没有参数的情况下调用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') 来清除排序,或者使用.order_by()字段。如果生成的查询将包含任何其他字段的排序,则分组将是错误的。 https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

此外,您可能希望从输出中删除该伪注释,因此再次调用.values()。 所以,最终代码如下:

GROUP BY

答案 1 :(得分:3)

这可以从Django的group_by works方式开始。所有带注释的字段都添加在GROUP BY子句中。但是,我无法评论为什么这样写。

您可以让您的查询像这样工作:

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

产生以下SQL:

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

恰好适用于你的情况。

我知道这可能不是您正在寻找的完整解决方案,但是使用CombinedExpressions(我希望!)可以在此解决方案中容纳一些复杂的注释。

答案 2 :(得分:2)

您的问题来自values()关注annotate()。订单很重要。 这在关于[注释和值子句的顺序]的文档中进行了解释( https://docs.djangoproject.com/en/1.10/topics/db/aggregation/#order-of-annotate-and-values-clauses

.values('pub_id')使用pub_id限制查询集字段。因此,您无法在income

上进行注释
  

values()方法采用可选的位置参数*字段,   它指定SELECT应限制的字段名称。

答案 3 :(得分:1)

@alexandr的这个解决方案正确地解决了它。

https://stackoverflow.com/a/44915227/6323666

您需要的是:

from django.db.models import Sum

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

理想情况下,我通过先将它们相加然后将其加倍来扭转这种情况。你试图将它加倍然后总结。希望这很好。