다음과 같은 관계일 때,

select할 모델 객체가 역참조하는 single object(one to one, many to one)
정참조 외래키(foreign key)

Join된 테이블을 불러들이는 쿼리를 실행하여 한번의 쿼리만으로 모델에 참조된 모델들의 필드를 읽을 수 있다.

예시

장고문서에서 예시를 확인해보자

from django.db import models

class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

Book <- Person <- City의 참조관계가 있다.

# Hits the database with joins to the author and hometown tables.
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author         # Doesn't hit the database.
c = p.hometown       # Doesn't hit the database.

# Without select_related()...
b = Book.objects.get(id=4)  # Hits the database.
p = b.author         # Hits the database.
c = p.hometown       # Hits the database.
  • selected_related를 사용하지않을때는 총 세번의 쿼리 갯수가 있다.(세번 데이터베이스 접근)
  • select_related는 총 한번의 쿼리(JOIN)를 부름으로써 참조관계에 있는 필드들을 불러 읽을 수있다.
  • 쿼리는 복잡해지지만 불러온 필드데이터값들이 캐시에 남아있기때문에 DB의 접근을 줄일 수 있다.

응용

djangopractice에는 다음과 같은 모델 릴레이션이 있다. Many(review) to one(restaruant)관계이다.

class Restaurant(models.Model): # Restaurant 라는 상점을 나타내는 모델을 정의
    name = models.CharField(max_length=30)  # 이름
    address = models.CharField(max_length=200)  # 주소

    ...

class Review(models.Model):
    point = models.IntegerField(default=0)
    comment = models.CharField(max_length=500)

    # 식당 모델과의 릴레이션 정의,
    # on_delete CASCADE로 지정하면 식당이 삭제되면 같이 삭제된다.
    restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)

    ...

이러한 릴레이션 관계(Many to one)에서 한번의 호출만을 사용하여 Restaurant과 관련된 review데이터를 불러들일수 있다.

def review_list(request):
    #reviews = Review.objects.all().order_by('-created_at')
    reviews = Review.objects.select_related().all().order_by('-created_at')
    paginator = Paginator(reviews, 10)  # 한 페이지에 10개씩 표시

    page = request.GET.get('page')  # query params에서 page 데이터를 가져옴
    items = paginator.get_page(page)  # 해당 페이지의 아이템으로 필터링

    context = {
        'reviews': items
    }
    return render(request, 'third/review_list.html', context)

댓글 쓰기