2010-05-02 4 views
0

이 문제에 대해 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')) 

불행히도 장고가 생성하는 나쁜 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을 실행하는 것과는 다른 대안이 있습니까?

답변

1

집계 할 때 SQL은 필드의 모든 값이 그룹 내에서 고유하거나 필드가 각 그룹에 대해 하나의 값만 나오게하는 집계 함수로 래핑되도록 요구합니다. 여기서 문제는 "app_plan.cap"이 "app_phone.id"및 "app_phone.plan_id"의 ​​각 조합에 대해 여러 가지 값을 가질 수 있으므로 DB를 처리하는 방법을 알려야한다는 것입니다.

결과에 대한 유효한 SQL은 원하는 결과에 따라 두 가지 가능성 중 하나입니다.

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" 

트릭은을 얻는 것입니다 : (app_phone.id, app_phone.plan_id, app_plan.cap)의 독특한 조합이 다른 그룹이 될 수 있도록 먼저 기능 BY 그룹에 app_plan.cap을 포함 할 수 "GROUP BY"호출에 추가 값. 우리는 unideal이다 "app_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을 집계 함수로 랩핑하여 고유 한 값으로 바꿀 수 있습니다. 집계 함수는 DB 제공 업체에 따라 다를 수 있지만 다음 사용 장고에서이 결과를 얻을 수 등

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

AVG, MAX, MIN, 등을 포함 할 수 있습니다 :

Phone.objects.annotate(
    total_cost=Sum('calls__cost'), 
    avg_cap=Avg('plan__cap') 
).filter(total_cost__gte=0.5 * F("avg_cap")) 

당신은 고려할 수 있습니다 당신이 남긴 버그 보고서를 당신이 기대하는 결과에 대한 명확한 명세로 업데이트합니다 - 예를 들어, 유효한 SQL을 말입니다.

+0

감사합니다. c. 나는 그것을 줄 것이다. – parxier

+0

''app_plan "."GROUP BY' 절의 "cap"다음에있었습니다. – parxier

+0

'Avg ('plan__cap')'이 (가) 트릭을했습니다. 고마워, C! – parxier

관련 문제