django에서 집계 함수를 사용하는 메소드
쿼리셋에서만 사용 가능하다
집계함수란?
테이블의 여러 행 또는 전체 행으로부터 하나의 값을 집계하여 리턴하는 함수
ex) COUNT, SUM, AVG, MAX, MIN
위 ERD 기준으로 product들의 평균 kcal를 구하려면 다음과 같은 orm query를 사용할 수 있다
Product.objects.aggregate(Avg('nutrient__kcal'))
# 아래 raw query가 발생한다
SELECT CAST(AVG("nutrients"."kcal") AS NUMERIC) AS "nutrient__kcal__avg"
FROM "products" LEFT OUTER JOIN "nutrients"
ON ("products"."id" = "nutrients"."product_id");
#결과
{'nutrient__kcal__avg': Decimal('308.735461538462')}
특정 menu에 속하는 메뉴들의 평균 kcal도 구할 수 있다
Product.objects.filter(menu__id=2).aggregate(Avg('nutrient__kcal'))
SELECT CAST(AVG("nutrients"."kcal") AS NUMERIC) AS "nutrient__kcal__avg"
FROM "products" LEFT OUTER JOIN "nutrients"
ON ("products"."id" = "nutrients"."product_id")
WHERE "products"."menu_id" = 2;
{'nutrient__kcal__avg': Decimal('215.958333333333')}
결과값은 위에서 볼수 있듯이 키:밸류 형태로 리턴되는데
대부분의 상황에서 자동생성된 키는 접근하기 불편하다
이를 해결하기 위해서는 aggregate의 집계함수를 키워드 인자로 건네주면 된다
Product.objects.filter(menu__id=2).aggregate(avg_kcal = Avg('nutrient__kcal'))
SELECT CAST(AVG("nutrients"."kcal") AS NUMERIC) AS "avg_kcal"
FROM "products" LEFT OUTER JOIN "nutrients"
ON ("products"."id" = "nutrients"."product_id")
WHERE "products"."menu_id" = 2;
{'avg_kcal': Decimal('215.958333333333')}
결과값의 데이터 타입을 결정하거나 서브쿼리 사이의 계산 또한 가능하다
Product.objects.filter(menu__id=2).aggregate(
kcal_diff=Max('nutrient__kcal') - Min('nutrient__kcal')
)
SELECT CAST((CAST(MAX("nutrients"."kcal") AS NUMERIC) -
CAST(MIN("nutrients"."kcal") AS NUMERIC)) AS NUMERIC)
AS "kcal_diff" FROM "products" LEFT OUTER JOIN "nutrients"
ON ("products"."id" = "nutrients"."product_id")
WHERE "products"."menu_id" = 2;
{'kcal_diff': Decimal('555')}
집계함수를 쿼리셋에 적용하여 집계함수의 결과를
쿼리셋에 존재하는 객체들의 클래스변수에 리턴하는 메소드
products = Menu.objects.annotate(product_count=Count('product'))
SELECT "menus"."id", "menus"."name", COUNT("products"."id")
AS "product_count" FROM "menus" LEFT OUTER JOIN "products"
ON ("menus"."id" = "products"."menu_id")
GROUP BY "menus"."id", "menus"."name" LIMIT 21;
products.first().product_count
25
aggregate와 달리 쿼리셋의 각 객체들에 적용된다
Product.objects.values('menu__name').annotate(Count('id'))
SELECT "menus"."name", COUNT("products"."id") AS "id__count"
FROM "products" LEFT OUTER JOIN "menus"
ON ("products"."menu_id" = "menus"."id")
GROUP BY "menus"."name" LIMIT 21;
[
{'menu__name': 'COFFEE', 'product_count': 25},
{'menu__name': 'BEVERAGE', 'product_count': 41},
{'menu__name': 'ICE-CREAM', 'product_count': 6},
{'menu__name': 'FOOD', 'product_count': 38},
{'menu__name': 'PRODUCT', 'product_count': 47}
]
위와 같이 query하면 menu foreignkey의 name이 같은 객체들에 대하여 aggregation을 리턴한다