【发布时间】:2017-08-17 20:52:22
【问题描述】:
更新
感谢发布的答案,我找到了一种更简单的方法来解决问题。原始问题可以在修订历史中看到。
问题
我正在尝试将 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
您还应该注意,average_rating 已明确包含在内 在要返回的值列表中。这是必需的,因为 values() 和 annotate() 子句的顺序。
如果 values() 子句在 annotate() 子句之前,任何注释 将自动添加到结果集中。但是,如果 values() 子句在 annotate() 子句之后应用,您需要显式包含聚合列。
因此,dbl_price 无法在聚合中找到,因为它是由先前的 annotate 创建的,但未包含在 values() 中。
但是,我也不能将它包含在values 中,因为我想使用values(后跟另一个annotate)作为分组设备,因为
如果 values() 子句在 annotate() 之前,注释将使用 values() 子句描述的分组计算。
这是 Django implements SQL GROUP BY 的基础。这意味着我不能在values() 中包含dbl_price,因为这样分组将基于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 中包含带注释的属性以将其保留在查询集中,但我不能这样做,因为 values 也用于分组(这将是错误的额外字段)。问题本质上是由于 values 在 Django 中使用的两种非常不同的方式,具体取决于上下文(values 是否后跟annotate) - 这是(1)值提取(SQL 普通 @ 987654355@ list) 和 (2) 分组 + 对组的聚合 (SQL GROUP BY) - 在这种情况下,这两种方式似乎有冲突。
我的问题是:有什么办法可以解决这个问题(无需回退到原始 sql)?
请注意:有问题的具体示例可以通过将所有annotate 语句移到values 之后来解决,几个答案都指出了这一点。但是,我对将annotate 语句保留在values() 之前的解决方案(或讨论)更感兴趣,原因有三个: 1. 还有更复杂的示例,其中建议的解决方法不起作用。 2. 我可以想象这样的情况,带注释的查询集已传递给另一个函数,该函数实际上执行 GROUP BY,因此我们唯一知道的是带注释字段的名称集及其类型。 3. 情况似乎很简单,如果values() 的两种不同用途的这种冲突以前没有被注意到和讨论过,我会感到惊讶。
【问题讨论】:
-
如果你还没有,你可以做一件事,就是在查询集被评估时打印构造的 SQL 字符串,这样你可以尝试切换事情的顺序,直到你得到原始您尝试模拟的 SQL 查询
-
@Mojimi 感谢您的建议。但是我对通过尝试使上面的特定示例起作用并不真正感兴趣。我有兴趣了解如何使这项工作总体上进行,最好只使用记录在案的用户级 Django 功能,或者这只是无法完成,对于一般查询类,您可以获得一些带注释的属性然后想要聚合在 GROUP BY 中覆盖它。
-
@LeonidShifrin 根据聊天,我可以得出结论,您发现原始查询是要走的路。如果是这种情况,请在此处发布答案,说明未找到其他替代方案并将其标记为已接受。
-
@ThulasiRam 我还不确定。在我看来,原始查询是最后的手段,除非我确定这是唯一的答案,否则我不会乐意将其发布为答案。我还没有时间尝试其他事情。一旦我得到满意的答案,我一定会在这里发布,除非有人先发布。
标签: sql django django-models django-orm