RDBMS(관계형 데이터베이스)에선 관계를 맺고 있는 복수의 테이블을 결합하여, 한 번의 쿼리문으로 여러 테이블의 데이터를 검색할 수 있다. 이러한 기능의 장점은 서로 다른 테이블의 데이터들을 마치 하나의 테이블에 저장된 데이터처럼 활용할 수 있다는 점이다. 테이블 결합은 SQL의 JOIN을 통해 이뤄진다.
SQL문으로 실행 가능한 기능은 ORM으로 구현할 수 있기에, django에도 JOIN을 실행하는 메서드가 존재한다. select_related와 prefetch_related가 이에 해당한다. 두 가지 메서드의 장점은 참조(혹은 역참조)된 테이블 데이터(related objects)에 접근하기 위해 쿼리문을 반복 사용할 필요가 없도록 만드는 데 있다. 당연히 데이터 처리에 걸리는 시간도 감소한다. 프로그램의 성능 개선을 위해서라도, django ORM의 두 기능을 제대로 활용할 줄 알아야 한다.
정참조 : foreign key, one to one
역참조 : one to one
역참조 : Many to one, Many to Many
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 역참조 관계)과 결합하고 있다.
select_related와 prefetch_related를 SQL문으로 표현해보기
# 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).역참조 테이블에서 접근할 필드 이름
(역참조)