我为这个问题打开了ticket。
简而言之,这是我的模特:
class Plan(models.Model):
cap = models.IntegerField()
class Phone(models.Model):
plan = models.ForeignKey(Plan, related_name='phones')
class Call(models.Model):
phone = models.ForeignKey(Phone, related_name='calls')
cost = models.IntegerField()
我想运行像这样的查询:
Phone.objects.annotate(total_cost=Sum('calls__cost')).filter(total_cost__gte=0.5*F('plan__cap'))
不幸的是,Django会生成错误的SQL:
SELECT "app_phone"."id", "app_phone"."plan_id",
SUM("app_call"."cost") AS "total_cost"
FROM "app_phone"
INNER JOIN "app_plan" ON ("app_phone"."plan_id" = "app_plan"."id")
LEFT OUTER JOIN "app_call" ON ("app_phone"."id" = "app_call"."phone_id")
GROUP BY "app_phone"."id", "app_phone"."plan_id"
HAVING SUM("app_call"."cost") >= 0.5 * "app_plan"."cap"
和错误:
ProgrammingError: column "app_plan.cap" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...."plan_id" HAVING SUM("app_call"."cost") >= 0.5 * "app_plan"....
除了运行原始SQL之外,还有其他解决方法吗?
答案 0 :(得分:1)
聚合时,SQL要求字段中的任何值在组内是唯一的,或者字段包含在聚合函数中,以确保每个组只有一个值。这里的问题是“app_plan.cap”对于“app_phone.id”和“app_phone.plan_id”的每个组合可能有许多不同的值,因此您需要告诉DB如何处理这些值。
因此,结果的有效SQL是两种不同的可能性之一,具体取决于您想要的结果。首先,您可以在GROUP BY函数中包含app_plan.cap
,以便(app_phone.id,app_phone.plan_id,app_plan.cap)的任何不同组合都是不同的组:
SELECT "app_phone"."id", "app_phone"."plan_id", "app_plan"."cap",
SUM("app_call"."cost") AS "total_cost"
FROM "app_phone"
INNER JOIN "app_plan" ON ("app_phone"."plan_id" = "app_plan"."id")
LEFT OUTER JOIN "app_call" ON ("app_phone"."id" = "app_call"."phone_id")
GROUP BY "app_phone"."id", "app_phone"."plan_id", "app_plan"."cap"
HAVING SUM("app_call"."cost") >= 0.5 * "app_plan"."cap"
诀窍是将额外的值放入“GROUP BY”调用中。我们可以通过滥用“额外”来狡猾地处理这个问题,尽管这会对“app_plan”的表名进行硬编码,这是非常不合适的 - 如果您愿意,可以使用Plan类以编程方式进行编写:
Phone.objects.extra({
"plan_cap": "app_plan.cap"
}).annotate(
total_cost=Sum('calls__cost')
).filter(total_cost__gte=0.5*F('plan__cap'))
或者,您可以将app_plan.cap
包装在聚合函数中,将其转换为唯一值。聚合函数因数据库提供程序而异,但可能包括AVG,MAX,MIN等内容。
SELECT "app_phone"."id", "app_phone"."plan_id",
SUM("app_call"."cost") AS "total_cost",
AVG("app_plan"."cap") AS "avg_cap",
FROM "app_phone"
INNER JOIN "app_plan" ON ("app_phone"."plan_id" = "app_plan"."id")
LEFT OUTER JOIN "app_call" ON ("app_phone"."id" = "app_call"."phone_id")
GROUP BY "app_phone"."id", "app_phone"."plan_id"
HAVING SUM("app_call"."cost") >= 0.5 * AVG("app_plan"."cap")
您可以使用以下方法在Django中获得此结果:
Phone.objects.annotate(
total_cost=Sum('calls__cost'),
avg_cap=Avg('plan__cap')
).filter(total_cost__gte=0.5 * F("avg_cap"))
您可能需要考虑更新您所留下的错误报告,更清楚地说明您期望的结果 - 例如,您所追求的有效SQL。