Django ORM에서 Join
을 수행하려면 select_related()
또는 prefetch_related()
메서드를 사용하여 Join
을 할 수 있습니다.
Model 정의
1
2
3
4
5
6
7
8
9
| from django.db import models
class BlogPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
class Comment(models.Model):
post = models.ForeignKey(BlogPost, on_delete=models.CASCADE)
text = models.TextField()
|
select_related()
는 1:1, 1:N의 관계에서 N이 사용할 수 있습니다.
정방향 참조에서의 Join
에 유리하게 사용됩니다.
Join
을 통해 데이터를 즉시 가져오는 방법 (SQL 단계에서 Join
)
Inner Join
은 연결된 모든 데이터를 가져옵니다.
게시물에 댓글이 하나도 없으면 해당 게시물은 결과에 나타나지 않습니다.
serializer에서 작성한 Inner Join
1
2
3
4
5
6
7
8
9
| # serializers.py
from rest_framework import serializers
from .models import BlogPost, Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'post', 'text']
|
view에서 작성한 Inner Join
1
2
3
4
5
6
7
8
9
| # views.py
from rest_framework.generics import ListCreateAPIView
from .models import BlogPost, Comment
from .serializers import BlogPostSerializer, CommentSerializer
class CommentListAPIView(ListCreateAPIView):
queryset = Comment.objects.select_related('post').all()
serializer_class = CommentSerializer
|
Result
1
2
3
4
5
6
7
8
9
| SELECT "practice_comment"."id",
"practice_comment"."post_id",
"practice_comment"."text",
"practice_blogpost"."id",
"practice_blogpost"."title",
"practice_blogpost"."content"
FROM "practice_comment"
INNER JOIN "practice_blogpost"
ON ("practice_comment"."post_id" = "practice_blogpost"."id")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"id": 1,
"post": 1,
"text": "첫번째 게시글의 댓글 1번"
},
{
"id": 2,
"post": 1,
"text": "첫번째 게시글의 댓글 2번"
}
]
|
prefetch_related()
는 1:N의 관계에서 1이 사용, M:N의 관계에서 사용할 수 있습니다.
역방향 참조에 유리하게 사용됩니다.
추가 쿼리를 통해 데이터를 즉시 가져오는 방법입니다. (추가 쿼리 발생, Join
은 파이썬 level에서 이루어집니다.)
Left Outer Join
은 왼쪽(기본)테이블의 모든 데이터를 가져오며, 오른쪽(연결된)테이블과 관련된 데이터가 없는 경우에도 왼쪽 테이블의 데이터 결과에 포함시킵니다.
따라서 게시물에 댓글이 없어도 게시물은 결과에 나타납니다.
serializer에서 작성한 Left Outer Join
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # serializers.py
from rest_framework import serializers
from .models import BlogPost, Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'post', 'text']
class BlogPostSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, read_only=True, source='comment_set')
class Meta:
model = BlogPost
fields = ['id', 'title', 'content', 'comments']
|
Inner Join
에서 예시로 보여준 코드에서 source='comment_set'
을 추가하여 Left Outer Join
과 유사한 형태로 결과를 나타냅니다. BlogPost
와 연결된 모든 댓글을 가져오며, 만약 게시물에 연관 된 댓글이 없다면 빈 리스트가 표시됩니다.
serializers.SerializerMethodField()을 이용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| from rest_framework import serializers
from .models import BlogPost, Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'post', 'text']
class BlogPostSerializer(serializers.ModelSerializer):
# SerializerMethodField를 사용하여 comments 필드를 추가
comments = serializers.SerializerMethodField()
class Meta:
model = BlogPost
fields = ['id', 'title', 'content', 'comments']
def get_comments(self, obj):
# BlogPost와 연관된 댓글들을 가져옵니다. (Left Outer Join)
comments = Comment.objects.filter(post=obj)
return CommentSerializer(comments, many=True).data
|
view에서 작성한 Left Outer Join
1
2
3
4
5
6
7
8
9
| # views.py
from rest_framework.generics import ListCreateAPIView
from .models import BlogPost, Comment
from .serializers import BlogPostSerializer, CommentSerializer
class BlogPostListAPIView(ListCreateAPIView):
queryset = BlogPost.objects.prefetch_related('comment_set').all()
serializer_class = BlogPostSerializer
|
✔ 이러한 동작은 Left Outer Join과 유사하지만 Left Outer Join과는 약간 다를 수 있습니다.
Result
1
2
3
4
5
6
7
8
9
10
11
| SELECT "practice_comment"."id",
"practice_comment"."post_id",
"practice_comment"."text"
FROM "practice_comment"
WHERE "practice_comment"."post_id" = '1'
SELECT "practice_comment"."id",
"practice_comment"."post_id",
"practice_comment"."text"
FROM "practice_comment"
WHERE "practice_comment"."post_id" = '2'
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"id": 1,
"title": "첫번째 게시글",
"content": "첫번째 게시글 내용 입니다.",
"comments": [
{
"id": 1,
"post": 1,
"text": "첫번째 게시글의 댓글 1번"
},
{
"id": 2,
"post": 1,
"text": "첫번째 게시글의 댓글 2번"
}
]
},
{
"id": 2,
"title": "두번째 게시글",
"content": "두번째 게시글 내용입니다.",
"comments": []
}
]
|
Self Join - __ (더블언더바를 활용)
참고
Django select_related()와 prefetch_related()