django ORM adv

Jinhyeon Son·2020년 4월 30일
0

정리

목록 보기
13/17

Aggragation

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')}

annotate

집계함수를 쿼리셋에 적용하여 집계함수의 결과를
쿼리셋에 존재하는 객체들의 클래스변수에 리턴하는 메소드

사용

  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와 달리 쿼리셋의 각 객체들에 적용된다

응용 - Group by query

  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을 리턴한다

0개의 댓글