๐Ÿ”ฅTIL#7. QuerySet ์˜ ์ •/์—ญ์ฐธ์กฐ์™€ ManyToManyField

๋ฐฑ์Šน์ง„ยท2020๋…„ 11์›” 17์ผ
1

wecode Django ์‹ค์Šต

๋ชฉ๋ก ๋ณด๊ธฐ
9/16

1. QuerySet์˜ ์ •/์—ญ์ฐธ์กฐ

์ •์ฐธ์กฐ : ์ž์‹ ์ด ์ฐธ์กฐํ•˜๋Š” table์„ ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ.
์—ญ์ฐธ์กฐ : ์ž์‹ ์„ ์ฐธ์กฐํ•˜๋Š” table์„ ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ.

[์ •์ฐธ์กฐ]
์ด ์‹œ๋ฆฌ์ฆˆ์˜ ์ด์ „ ํฌ์ŠคํŠธ์—์„œ select_related()๋ฅผ ํ†ตํ•ด ์ •์ฐธ์กฐ์˜ ๊ธฐ๋Šฅ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.
์ฒ˜์Œ์— django์˜ QuerySet ๊ธฐ๋Šฅ์„ ์ ‘ํ•˜๋ฉด์„œ ํ…Œ์ด๋ธ”์„ ๊ฐ์ฒดํ™”ํ•ด์ฃผ๋Š” ์šฉ๋„๋กœ๋งŒ ์ธ์‹ํ–ˆ์—ˆ๋‹ค. ๋•Œ๋ฌธ์— "join" ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฌด์กฐ๊ฑด select_related()๋ฅผ ์ด์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ƒ๊ฐํ–ˆ์œผ๋‚˜ ์ฐฉ๊ฐ์ด์—ˆ๋‹ค. foreignKey๋กœ ์ง€์ •ํ•œ ์†์„ฑ์€ id ๊ด€๋ จ ๊ฐ’๋งŒ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ด ์†์„ฑ์ด relation ํ•˜๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์˜€๋‹ค.
hourplace site์˜ ๋ชจ๋ธ๋ง ์ค‘ place์™€ user ์˜ ๊ด€๊ณ„๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณด์ž.

class User(models.Model):
	name = models.CharField
    	email = models.CharField    	

class Place(models.Model):
	address = models.CharField
    	floor = models.IntegerField
    	user = models.ForeignKeyField(User, on_delete=models.CASCADE)
                      		
p = Place.objects.get(id=1)
print(p.user.name)
print(p.user.email)

์œ„๋Š” user_id ๋กœ related ์ค‘์ธ user์˜ ์ •๋ณด๋ฅผ ์ •์ฐธ์กฐ๋กœ ์ ‘๊ทผํ•˜๋Š” ์˜ˆ์ œ์ด๋‹ค. select_related() ์—†์ด ๋ฐ”๋กœ relation ์ค‘์ธ ๊ฐ์ฒด ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด select_related()๋Š” ์™œ ์ง€์›ํ•˜๋Š” ๊ฑธ๊นŒ? ์˜คํžˆ๋ ค ์œ„ ์˜ˆ์ œ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ํŽธ๋ฆฌํ•œ๋ฐ ๋ง์ด๋‹ค.
์ด๋Š” Hit(Database์— query๋ฅผ ์š”์ฒญํ•˜๋Š” ํšŸ์ˆ˜)๋ฅผ ์ตœ์†Œํ™”ํ•˜์—ฌ ์„ฑ๋Šฅ์ ์ธ ์ธก๋ฉด์—์„œ์˜ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ์œผ๋กœ SQL๋กœ ์น˜๋ฉด join ์„ ์ด์šฉํ•ด ํ•œ๋ฒˆ์˜ Hit๋กœ ๊ด€๊ณ„๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ๊ณผ ๊ฐ™๋‹ค.

[์—ญ์ฐธ์กฐ]
SQL๋ฌธ๋ฒ• ์ค‘ join ๊ธฐ๋Šฅ์„ ์•Œ๊ณ  ์žˆ์—ˆ๊ธฐ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ •์ฐธ์กฐ ๊ธฐ๋ฒ•์— ์ต์ˆ™ํ•˜๊ฒŒ ๋ฐ›์•„๋“ค์˜€๋‹ค. ํ•˜์ง€๋งŒ ์—ญ์ฐธ์กฐ๋ผ๋Š” ๊ฐœ๋…์€ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์ง€ ์•Š์•˜๋‹ค. ์ด์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•˜๋˜ ๋„์ค‘ ์—ญ์ฐธ์กฐ๊ฐ€ ํ•„์š”ํ•  ์ƒํ™ฉ์„ ์ ‘ํ•˜๋ฉด์„œ ์กฐ๊ธˆ ์ดํ•ด๊ฐ€ ๊ฐ”๋‹ค. ์•„๋ž˜๋Š” ๊ทธ ์ƒํ™ฉ์ด๋‹ค.

User๋Š” ์—ฌ๋Ÿฌ Place๋ฅผ ์†Œ์œ ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ Place๋Š” user table์„ ์ฐธ์กฐํ•œ๋‹ค. 
Place๋งˆ๋‹ค ์ •์ฐธ์กฐ ๋ฐฉ์‹์œผ๋กœ Place์˜ ์†Œ์œ ์ฃผ์ธ user์˜ ์ •๋ณด(name, email ๋“ฑ)๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ User data๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ด User๊ฐ€ ์†Œ์œ ํ•œ Place ์ •๋ณด๋ฅผ ์–ป์œผ๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?
Place table์„ user_id๋ฅผ ๊ธฐ์ค€์œผ๋กœ filteringํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๊ฒ ์ง€๋งŒ ์ด ๊ฒฐ๊ณผ๋Š” ์˜ค์ง Place 
์— ๋Œ€ํ•œ Queryset๋งŒ์ด ๋‚˜์˜จ๋‹ค. 

๋‚˜๋Š” User data์™€ Place data๋ฅผ ๋ชจ๋‘ ๋ณด์œ ํ•œ Queryset์„ ์–ป๊ณ  ์‹ถ๋‹ค.

User๊ฐ€ Place๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ์ •์ฐธ์กฐ ๋ฐฉ์‹ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‚˜ ์ง€๊ธˆ์€ ๋ฐ˜๋Œ€ ์ƒํ™ฉ์ด๋‹ค. ์ด ๊ฒจ์šฐ๋ฅผ ์œ„ํ•ด ์—ญ์ฐธ์กฐ ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด prefetch_related() ๋ฅผ ์•Œ์•„๋ณด์ž.

user = Users.objects.prefetch_related('place_set').get(id=1)

print(user.name)
print(user.place_set.address)

์œ„ ์ฝ”๋“œ์—์„œ prefetch_related()๋ฅผ callํ•˜๋ฉด์„œ ์ธ์ž๋กœ ์—ญ์ฐธ์กฐ ๋Œ€์ƒ์ธ class์ด๋ฆ„ + '_set'์„ ๋„ฃ๊ณ  ์žˆ๋‹ค. '_set'์€ ์—ญ์ฐธ์กฐ๋ฅผ ์˜๋ฏธํ•˜๋Š” ์˜ˆ์•ฝํ‚ค๋กœ ์ด๋ฅผ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์œผ๋ฉด ์—ญ์ฐธ์กฐ๊ฐ€ ๋ถˆ๊ฐ€ํ•˜๋‹ค.

Prefetch_related() ์—ญ์‹œ ์ ์€ Hit์ˆ˜๋กœ ์ฐธ์กฐ๊ด€๊ณ„์ธ Table๋“ค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ํ•จ์ˆ˜๋กœ ์ฃผ๋กœ ์—ญ์ฐธ์กฐ ์ƒํ™ฉ์—์„œ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.
(๊ทธ๋ ‡๋‹ค๊ณ  ๊ผญ ์—ญ์ฐธ์กฐ์‹œ์—๋งŒ ์“ฐ๋Š”๊ฑด ์•„๋‹ˆ๋‹ค. QuerySet์˜ 'query'๋ผ๋Š” ์†์„ฑ์„ ํ†ตํ•ด ์‹ค์ œ sql query๋ฌธ์„ ํ™•์ธ, ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์ข‹์€ ๋ฐฉ์‹์„ ์„ ํƒํ•  ์ค„ ์•Œ์•„์•ผ ํ•œ๋‹ค).

์œ„์—์„œ select_related() ์—†์ด๋„ ์ •์ฐธ์กฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํ–ˆ์—ˆ๋‹ค. ๋ฌผ๋ก  ์—ญ์ฐธ์กฐ์˜ ๊ฒฝ์šฐ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

user = Users.objects.get(id=1)

print(user.name)
print(user.place_set.address)

prefetch_related()๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ๋งŒ ๋นผ๋ฉด ๋˜‘๊ฐ™๋‹ค. ๋‹ค๋งŒ ์ด ๊ฒฝ์šฐ๋„ ์„ฑ๋Šฅ์„ ์œ„ํ•ด prefetch_related๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ๋งŒ์ด ์ฐจ์ด์ผ ๋ฟ์ด๋‹ค.

2. ManyToManyField

Database ์„ค๊ณ„์‹œ N:N ๊ด€๊ณ„์˜ Table๋“ค์€ ์ค‘๊ฐ„Table์„ ๋งŒ๋“ค์–ด N:N์„ ํšŒํ”ผํ•ด์•ผ ํ•œ๋‹ค๊ณ  ๋ฐฐ์› ๋‹ค. ๊ทธ๋ž˜์„œ django์—์„œ models ์ž‘์—…์‹œ ์ค‘๊ฐ„table์šฉ class ๋ฅผ ์ •์˜, ์–‘์ชฝ id๋ฅผ FK๋กœ ํ•˜๋Š” ์†์„ฑ์„ ๊ฐ–๋„๋ก ๋ชจ๋ธ๋ง ํ–ˆ์—ˆ๋‹ค. ์ด๋Ÿฐ ์ค‘๊ฐ„ table์„ ๋งค๋ฒˆ ๋งŒ๋“ค์–ด์•ผ ํ• ๊นŒ? ์ด๋ฒˆ์— ์•Œ๊ฒŒ๋œ ManyToManyField๋Š” ์ด๋ฅผ ์ž๋™ํ™” ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.

from django.db import models

class Publication(models.Model):
    title = models.CharField(max_length=30)

    class Meta:
        db_table='publications'

class Article(models.Model):
    headline = models.CharField(max_length=100)
    publications = models.ManyToManyField(Publication)

    class Meta:
        ordering ='articles'

Article๊ณผ Publication์˜ ์ค‘๊ฐ„table๋กœ articles_publications๋ฅผ ๋งŒ๋“œ๋Š” ์ฝ”๋“œ์ด๋‹ค.
๊ทธ๋ฆฌ๊ณ  Article.publications๋ฅผ ํ†ตํ•ด ์ค‘๊ฐ„ํ…Œ์ด๋ธ”์„ ์ด์šฉํ•œ๋‹ค. ๊ฐ’์„ ๋„ฃ์„ ๋•Œ๋Š” ๋‘ ํ…Œ์ด๋ธ”์— ๊ฐ’์ด ๋จผ์ € ์ƒ์„ฑ๋˜์–ด ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ ๋ณดํ†ต ์•„๋ž˜์™€ ๊ฐ™์€ ์ ˆ์ฐจ๋กœ ์ง„ํ–‰๋œ๋‹ค.

p1 = Publication.objects.create(title="hello")
p2 = Publication.objects.create(title="hello2")
a1 = Article.objects.create(headline="ํ—ค๋“œ๋ผ์ธ")
     ๋˜๋Š” Article(headline="ํ—ค๋“œ๋ผ์ธ").save()
    
a1.publications.add(p1)
a1.publications.add(p2)
๋˜๋Š”
a1.publications.add(p1, p2)

a1์€ Article๊ฐ์ฒด๋กœ Create๋‚˜ Save๋ฅผ ํ•˜๊ธฐ ์ „๊นŒ์ง„ Table์— ์ถ”๊ฐ€๋˜์ง€ ์•Š์œผ๋ฉฐ ์ด ์ƒํ™ฉ์—์„œ a1์— publications๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด error๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

์œ„ ๊ณผ์ •์„ ๊ฑฐ์ณ ์ค‘๊ฐ„ํ…Œ์ด๋ธ”์ด ์ฑ„์›Œ์กŒ๋‹ค. ์—ฌ๊ธฐ์„œ ์งˆ๋ฌธ.... title์ด 'hello'์ธ Publication์„ ๊ฐ–๋Š” Article์„ ์ฐพ์œผ๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?

Article.objects.filter('publication__title' = 'hello')

'__'๋Š” queryset์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์•ฝ์–ด์ด๋‹ค. ์†์„ฑ์„ ์ ‘๊ทผํ•˜๋Š” ์šฉ๋„๋Š” ๋ฌผ๋ก  ๊ฐ์ข…์—ฐ์‚ฐ(gt, lt, gte, lte...)์„ ์ ์šฉํ• ๋•Œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ตํ˜€๋‘์ž


๋งŒ์•ฝ ์ƒ์„ฑํ•ด์•ผ ํ•  ์ค‘๊ฐ„ table์ด FK ์™ธ์— ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค๋ฉด models์—์„œ ์ค‘๊ฐ„table์„ ์ •์˜ํ•˜๊ณ  ManyToManyField() argument ์ค‘ 'through' ์— ์ค‘๊ฐ„table class ์ด๋ฆ„์„ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค.
๋‹ค์Œ์€ ๋‘ table(User, Place)์„ relate ํ•˜๋Š” 'ratings' ๋ผ๋Š” table์„ ManyToManyField๋กœ ์ง€์ •ํ•˜๋Š” code ์ด๋‹ค.

class User(models.Model):
	name = models.CharField()
    	email = models.CharField()    	
        user_place = models.ManyToManyField(Place, through=Rating)
       
class Place(models.Model):
	address = models.CharField()
  
class Rating(models.Model):
	user = models.ForeignKeyField(User, on_delete=models.CASCADE)
    	place = models.ForeignKeyField('place.Place', on_delete=models.CASCADE)
        starpoint = models.FloatField()
        comments = models.Text()
        ...
profile
12๋…„ .NET ๊ฐœ๋ฐœ ๊ฒฝ๋ ฅ์„ ๊ฐ€์ง„ ์›น ์ดˆ์งœ ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค :)

0๊ฐœ์˜ ๋Œ“๊ธ€