django - select_related, prefetch_related

김영훈·2021년 7월 22일
0

Django

목록 보기
9/11

django ORM에서 JOIN 기능 사용하기

  • RDBMS(관계형 데이터베이스)에선 관계를 맺고 있는 복수의 테이블을 결합하여, 한 번의 쿼리문으로 여러 테이블의 데이터를 검색할 수 있다. 이러한 기능의 장점은 서로 다른 테이블의 데이터들을 마치 하나의 테이블에 저장된 데이터처럼 활용할 수 있다는 점이다. 테이블 결합은 SQL의 JOIN을 통해 이뤄진다.

  • SQL문으로 실행 가능한 기능은 ORM으로 구현할 수 있기에, django에도 JOIN을 실행하는 메서드가 존재한다. select_relatedprefetch_related가 이에 해당한다. 두 가지 메서드의 장점은 참조(혹은 역참조)된 테이블 데이터(related objects)에 접근하기 위해 쿼리문을 반복 사용할 필요가 없도록 만드는 데 있다. 당연히 데이터 처리에 걸리는 시간도 감소한다. 프로그램의 성능 개선을 위해서라도, django ORM의 두 기능을 제대로 활용할 줄 알아야 한다.

  • 오직 1:1, 1:다 관계에서만 사용할 수 있다.

    정참조 : foreign key, one to one
    역참조 : one to one

  • 1:다, 다:다 관계에서 사용한다.

    역참조 : Many to one, Many to Many

사용 예시

  • 상품 상세보기 기능을 구현한 함수다. 해당 로직에선 select_related와 prefetch_related를 활용하여 products 테이블과 연결된 테이블을 결합했다.
class ProductDetailView(View):
    def get(self,request,product_id):
        try:
            # 하단 코드에서 selected_related와 prefetch_related가 사용됐다. 
            product = Product.objects.select_related('category').prefetch_related(
                'review_set','size','color','productimage_set').get(id=product_id)
            # print(Product.objects.select_related('category').prefetch_related(
            #     'review_set','size','color','productimage_set').filter(id=product_id).values())
            product_info = {
                'category'          : product.category.name,
                'manufacturer'      : product.manufacturer,
                'name'              : product.name,
                'star_rating'       : product.review_set.all().aggregate(Avg('star_rating')).get('star_rating__avg', None),
                'review_number'     : product.review_set.count(),
                'discout_rate'      : product.discount_rate,
                'delivery_fee'      : product.delivery_fee,
                'delivery_method'   : product.delivery_method,
                'size'              : list(set([size.name for size in product.size.all()])),
                'color'             : [color.name for color in product.color.all()],
                'thumbnail_image'   : product.thumbnail_image,
                'description'       : product.description,
                'description_image' : product.description_image,
                'product_images'    : [image.image_url for image in product.productimage_set.all()]
                }
            return JsonResponse({'MESSAGE':'SUCCESS','product_info':product_info}, status=200)
        except Product.DoesNotExist:
            return JsonResponse({'MESSAGE':'PRODUCT_ID_DOES_NOT_EXISTS'}, status=401)
  • 테이블 결합을 구현한 코드를 자세히 살펴보면 다음과 같다.

    product = Product.objects.select_related('category').prefetch_related(
                    'review_set','size','color','productimage_set').get(id=product_id)
    • .select_related('category') : INNER JOIN으로 products 테이블과 products테이블이 정참조(1:N 관계)하는 categories 테이블을 결합하고 있다.

    • .prefetch_related('review_set','size','color','productimage_set'): 다중 JOIN이 사용됐다. INNER JOIN으로 products 테이블을 reviews 테이블(1:N 역참조 관계), sizes 테이블(N:N 관계), colors 테이블(N:N 관계), product_images 테이블(1:N 역참조 관계)과 결합하고 있다.

      • products 테이블과 N:N 관계인 sizes 테이블 혹은 colors테이블의 값들은 중간 테이블인 product_options 테이블과 결합을 통해 가져올 수도 있다.
  • select_related와 prefetch_related를 SQL문으로 표현해보기

    • 자세한 이해를 위해, 위에서 사용된 코드를 SQL문으로 표현해 봤다. 다중 JOINScala Subquery가 사용됐다.

# f-string을 사용하여 변수 product_id에 대한 값을 처리했다.
# SELECT 구에서 scala subquery를 사용하여 N:N 관계를 맺고 있는 테이블의 필드 값을 가져왔다.

SELECT product.*, category.*, review.*, product_image.*
(SELECT name FROM sizes AS size WHERE size.id=product_option.size_id) AS size, 
(SELECT name FROM colors AS color WHERE color.id=product_option.color_id) AS color 
FROM products AS product 
JOIN categories AS category ON category.id=product.category_id  
JOIN reviews AS review ON product.id=review.product_id 
JOIN product_options AS product_option ON product.id=product_option.product_id 
JOIN product_images AS product_image ON product_image.product_id=product.id 
WHERE product.id=변수 product_id의 값;
  • select_related, prefetch_related를 사용하여 가져온 데이터에 접근하는 방법

    • 가져온 데이터에 접근하는 방법은 테이블을 결합하지 않았을 때와 동일하다.

    • (정참조/역참조)된 테이블의 필드값에 접근하기

      • object 객체.foreignkey 필드 이름(models.py에서 사용된 필드 이름).참조 테이블에서 접근할 필드 이름(정참조)
      • object 객체.역참조하는 테이블 이름_set(models.py에서 사용된 class name).역참조 테이블에서 접근할 필드 이름(역참조)
profile
Difference & Repetition

0개의 댓글