Search

QuerySet

Django QuerySet

1. QuerySet์ด๋ž€?

DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ๋ ˆ์ฝ”๋“œ ์ง‘ํ•ฉ (๋ฆฌ์ŠคํŠธ์ฒ˜๋Ÿผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
Post.objects.all() # QuerySet ๊ฐ์ฒด ๋ฐ˜ํ™˜
Python
๋ณต์‚ฌ

QuerySet์˜ ํ•ต์‹ฌ ํŠน์ง•

1.
์ง€์—ฐ ํ‰๊ฐ€ (Lazy Evaluation)
โ€ข
QuerySet์„ ์ •์˜ํ•˜๋Š” ์‹œ์ ์—๋Š” DB์— ์ ‘๊ทผํ•˜์ง€ ์•Š์Œ
โ€ข
์‹ค์ œ๋กœ ๊ฒฐ๊ณผ๊ฐ€ ํ•„์š”ํ•œ ์‹œ์ (์ดํ„ฐ๋ ˆ์ด์…˜, ์Šฌ๋ผ์ด์‹ฑ ๋“ฑ)์— ์ฟผ๋ฆฌ ์‹คํ–‰
2.
์ฒด์ด๋‹ ๊ฐ€๋Šฅ (Chainable)
โ€ข
์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ์—ฐ๊ฒฐํ•ด์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
โ€ข
๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ์ƒˆ๋กœ์šด QuerySet ๋ฐ˜ํ™˜
3.
๋ฐ˜๋ณต ๊ฐ€๋Šฅ (Iterable)
โ€ข
for ๋ฌธ์œผ๋กœ ์ˆœํšŒ ๊ฐ€๋Šฅ
โ€ข
๋ฆฌ์ŠคํŠธ์ฒ˜๋Ÿผ ์ธ๋ฑ์‹ฑ/์Šฌ๋ผ์ด์‹ฑ ์ง€์›
4.
์บ์‹œ (Caching)
โ€ข
ํ•œ ๋ฒˆ ์‹คํ–‰๋œ QuerySet์€ ๋ฉ”๋ชจ๋ฆฌ์— ์บ์‹œ
โ€ข
๋™์ผํ•œ ์ฟผ๋ฆฌ ์žฌ์‹คํ–‰ ์‹œ DB ์ ‘๊ทผ ์—†์ด ์บ์‹œ ํ™œ์šฉ

2. QuerySet ์ฃผ์š” ๋ฉ”์„œ๋“œ ์š”์•ฝ

๋ฉ”์„œ๋“œ
์„ค๋ช…
.all()
์ „์ฒด ๋ ˆ์ฝ”๋“œ ์กฐํšŒ
.filter(์กฐ๊ฑด)
์กฐ๊ฑด์— ๋งž๋Š” ๋ ˆ์ฝ”๋“œ ์ถ”์ถœ
.get(์กฐ๊ฑด)
์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ํ•˜๋‚˜์˜ ๋ ˆ์ฝ”๋“œ๋งŒ (์—†๊ฑฐ๋‚˜ ์—ฌ๋Ÿฌ ๊ฐœ๋ฉด ์—๋Ÿฌ)
.exclude(์กฐ๊ฑด)
์กฐ๊ฑด์„ ์ œ์™ธํ•œ ๋ ˆ์ฝ”๋“œ
.order_by('field')
์ •๋ ฌ (-ํ•„๋“œ๋ช…์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ)
.count()
์ด ๊ฐœ์ˆ˜
.exists()
์กด์žฌ ์—ฌ๋ถ€ ๋ฐ˜ํ™˜ (Boolean)
.first(), .last()
์ฒซ ๋ฒˆ์งธ/๋งˆ์ง€๋ง‰ ๊ฐ์ฒด ๋ฐ˜ํ™˜
.values(), .values_list()
dict ๋˜๋Š” tuple๋กœ ์ถ”์ถœ

3. ํ•„ํ„ฐ๋ง ์˜ˆ์ œ (Field lookup)

Post.objects.filter(title__contains="์žฅ๊ณ ") # ๋ถ€๋ถ„ ํฌํ•จ Post.objects.filter(title__startswith="๊ณต์ง€") # ์‹œ์ž‘ ๋‹จ์–ด Post.objects.filter(title__iexact="๊ณต์ง€์‚ฌํ•ญ") # ๋Œ€์†Œ๋ฌธ์ž ๋ฌด์‹œ Post.objects.filter(id__in=[1, 2, 3]) # ๋ชฉ๋ก ์กฐ๊ฑด Post.objects.filter(created_at__date='2024-01-01') # ๋‚ ์งœ ์กฐ๊ฑด
Python
๋ณต์‚ฌ

4. ์ •๋ ฌ

Post.objects.order_by('created_at') # ์˜ค๋ฆ„์ฐจ์ˆœ Post.objects.order_by('-created_at') # ๋‚ด๋ฆผ์ฐจ์ˆœ
Python
๋ณต์‚ฌ

5. ์Šฌ๋ผ์ด์‹ฑ

Post.objects.all()[:5] # ์•ž 5๊ฐœ Post.objects.all()[5:10] # 6~10๋ฒˆ์งธ
Python
๋ณต์‚ฌ
์ฃผ์˜: ์Šฌ๋ผ์ด์‹ฑ์€ ์‹ค์ œ๋กœ LIMIT OFFSET SQL์ด ์‹คํ–‰๋จ

6. ์ง‘๊ณ„ (Aggregation)

from django.db.models import Count, Avg, Max Post.objects.aggregate(Count('id')) # ์ด ๊ฐœ์ˆ˜ Post.objects.aggregate(Max('created_at')) # ์ตœ์‹  ๋‚ ์งœ
Python
๋ณต์‚ฌ

7. ๊ทธ๋ฃน๋ณ„ ์ง‘๊ณ„ (annotate)

from django.db.models import Count # ๊ฒŒ์‹œ๊ธ€๋ณ„ ๋Œ“๊ธ€ ์ˆ˜ ์ถœ๋ ฅ Post.objects.annotate(num_comments=Count('comment')).values('title', 'num_comments')
Python
๋ณต์‚ฌ

8. Q ๊ฐ์ฒด (๋ณต์žกํ•œ ์กฐ๊ฑด)

from django.db.models import Q Post.objects.filter(Q(title__contains="์žฅ๊ณ ") | Q(content__contains="Django")) Post.objects.filter(Q(title__contains="๊ณต์ง€") & Q(is_public=True))
Python
๋ณต์‚ฌ

9. ์‹ค์Šต ์˜ˆ์ œ

์‹ค์Šต๋ช…
๋ชฉํ‘œ
๊ฒŒ์‹œ๊ธ€ ๊ฒ€์ƒ‰
filter(title__contains=) ํ™œ์šฉ
์ •๋ ฌ ๋ฐ ํŽ˜์ด์ง•
order_by() + ์Šฌ๋ผ์ด์‹ฑ ์‹ค์Šต
๋Œ“๊ธ€ ์ˆ˜ ์ง‘๊ณ„
annotate(Count())๋กœ ์ถœ๋ ฅ
๋‹ค์ค‘ ์กฐ๊ฑด ๊ฒ€์ƒ‰
Q() ๊ฐ์ฒด ์‚ฌ์šฉ ์‹ค์Šต

10. shell์—์„œ ์—ฐ์Šต

python manage.py shell
Shell
๋ณต์‚ฌ
from board.models import Post Post.objects.filter(title__contains="์žฅ๊ณ ")
Python
๋ณต์‚ฌ

QuerySet ์ฒด์ด๋‹

Post.objects.filter(is_public=True).order_by('-created_at')[:5]
Python
๋ณต์‚ฌ
QuerySet์€ ์ฒด์ด๋‹์œผ๋กœ ์กฐํ•ฉ ๊ฐ€๋Šฅ โ†’ SQL ์ฟผ๋ฆฌ ์ตœ์ ํ™” ์ž๋™ ์ ์šฉ๋จ

์š”์•ฝ

๊ธฐ๋Šฅ
์ฝ”๋“œ ์˜ˆ์‹œ
ํ•„ํ„ฐ๋ง
filter(field__lookup=value)
์ •๋ ฌ
order_by('field'), -field
๊ฐœ์ˆ˜ ์„ธ๊ธฐ
count(), aggregate(Count('id'))
๋‹ค์ค‘ ์กฐ๊ฑด
filter(Q(...))
๊ฐ’๋งŒ ์ถ”์ถœ
values(), values_list()

์กฐ์ธ

โ€ข
๋‹จ์ผ ๊ฐ์ฒด ์กฐ์ธ
โ—ฆ
1:1 = post : user (๊ฒŒ์‹œ๊ธ€ : ํšŒ์›)
โ€ข
๋‹ค์ˆ˜ ๊ฐ์ฒด ์กฐ์ธ
โ—ฆ
1:N = post : comment (๊ฒŒ์‹œ๊ธ€ : ๋Œ“๊ธ€)

1. select_related() โ€“ ๋‹จ์ผ ๊ฐ์ฒด ์กฐ์ธ (1:1, N:1)

โ€ข
ForeignKey, OneToOneField ๊ด€๊ณ„์—์„œ ์‚ฌ์šฉ
โ€ข
SQL์˜ INNER JOIN๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ์ž‘๋™

์˜ˆ์ œ

comments = Comment.objects.select_related('post', 'writer') for c in comments: print(c.content, c.post.title, c.writer.username)
Python
๋ณต์‚ฌ
โ€ข
Comment โ†’ Post, User ๋ฅผ JOIN
โ€ข
ํšจ์œจ์ ์œผ๋กœ 1๊ฐœ์˜ SQL ์ฟผ๋ฆฌ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ด

2. prefetch_related() โ€“ ๋‹ค์ˆ˜ ๊ฐ์ฒด ์กฐ์ธ (1:N, M:N)

โ€ข
์—ญ์ฐธ์กฐ๋‚˜ ManyToManyField์— ์‚ฌ์šฉ
โ€ข
์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ด€๋ จ ๊ฐ์ฒด๊ฐ€ ์žˆ์„ ๋•Œ ์ ํ•ฉ (2๊ฐœ์˜ ์ฟผ๋ฆฌ๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉ)

์˜ˆ์ œ

posts = Post.objects.prefetch_related('comment_set') for post in posts: print(f'{post.title}์˜ ๋Œ“๊ธ€๋“ค:') for comment in post.comment_set.all(): print(f' - {comment.content}')
Python
๋ณต์‚ฌ
โ€ข
Post โ†’ Comment JOIN
โ€ข
์—ญ์ฐธ์กฐ๋Š” related_name์ด ์—†๋‹ค๋ฉด <๋ชจ๋ธ๋ช…>_set์œผ๋กœ ์ ‘๊ทผ

3. ํ•„ํ„ฐ์—์„œ JOIN

โ€ข
๊ด€๊ณ„ ํ•„๋“œ๋ฅผ __(๋”๋ธ” ์–ธ๋”์Šค์ฝ”์–ด)๋กœ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ์กฐ๊ฑด ์„ค์ •

์˜ˆ์ œ

# 'tester' ์œ ์ €๊ฐ€ ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๊ธ€ Post.objects.filter(writer__username='tester') # ๊ฒŒ์‹œ๊ธ€ ์ œ๋ชฉ์— 'Django' ํฌํ•จ๋œ ๋Œ“๊ธ€ Comment.objects.filter(post__title__contains='Django')
Python
๋ณต์‚ฌ

4. SQL๋กœ JOIN ํ™•์ธํ•˜๊ธฐ

qs = Comment.objects.select_related('post', 'writer') print(qs.query)
Python
๋ณต์‚ฌ
โ€ข
์‹ค์ œ SQL JOIN ์ฟผ๋ฆฌ ํ™•์ธ ๊ฐ€๋Šฅ

์š”์•ฝ ํ‘œ

๋ชฉ์ 
๋ฉ”์„œ๋“œ
๊ด€๊ณ„
์ฟผ๋ฆฌ ์ˆ˜
์„ค๋ช…
์™ธ๋ž˜ํ‚ค/๋‹จ์ผ ๊ฐ์ฒด ์กฐ์ธ
select_related()
1:1, N:1
1
JOIN์œผ๋กœ ํ•จ๊ป˜ ์กฐํšŒ
๋‹ค๋Œ€๋‹ค/์—ญ์ฐธ์กฐ ์กฐ์ธ
prefetch_related()
1:N, M:N
2
๊ด€๋ จ ๊ฐ์ฒด๋ฅผ ๋ณ„๋„ ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์™€ ์กฐ์ธ
์กฐ๊ฑด์—์„œ JOIN
filter(FK__ํ•„๋“œ)
๋ชจ๋‘
1
ํ•„ํ„ฐ ์•ˆ์—์„œ ์กฐ์ธ ์ˆ˜ํ–‰
SQL ํ™•์ธ
.query
-
-
SQL ์ฟผ๋ฆฌ ์ง์ ‘ ํ™•์ธ

์ถ”๊ฐ€ ์˜ˆ์‹œ: Post - Comment ๋ชจ๋ธ

# Post โ†’ Comment (์—ญ์ฐธ์กฐ) posts = Post.objects.prefetch_related('comment_set') # Comment โ†’ Post (์ •์ฐธ์กฐ) comments = Comment.objects.select_related('post')
Python
๋ณต์‚ฌ
ํ•„์š”ํ•˜์‹œ๋ฉด ManyToMany, GenericForeignKey, annotate()๋ฅผ ํ™œ์šฉํ•œ ์ง‘๊ณ„ JOIN ์˜ˆ์ œ๋„ ์ œ๊ณตํ•ด๋“œ๋ฆด ์ˆ˜ ์žˆ์–ด์š”.