# Django Queries

앞으로 예시들에 사용할 모델. 글 하나에 블로그 여러 개 중 하나가 연결되어있고, author는 m2m.

class Blog(models.Model): # 블로그
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name

class Author(models.Model): # 작가
    name = models.CharField(max_length=50)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model): # 글
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):
        return self.headline

# 오브젝트 만들기

b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') # 모델 인스턴스화
b.save() # 디비에 저장. SQL의 INSERT를 호출한다. 이거 하기 전까지는 디비를 건들지 않는다.

create()는 create하고 save까지 한다.

# 오브젝트 바뀐거 저장

>>> b5.name = 'New name'
>>> b5.save()

save()하면 UPDATE SQL문을 날린다.

# foreignkey 필드 저장

entry = Entry.objects.get(pk=1)
yoolmoo_blog = Blog.objects.get(name='유르무르 블로그')
entry.blog = yoolmoo_blog
entry.save 

# ManyToManyField 저장

jay = Author.objects.create(name='Jay')
entry.authors.add(jay)

josh = Author.objects.create(name='Josh')
yoolmoo = Author.objects.create(name='Yoolmoo')
entry.authors.add(josh, yoolmoo)

add로 추가한다.

# 오브젝트 가져오기 (Retrieving objects)

오브젝트를 retrieve하면, Manager를 통해 Queryset을 만든다. Filters는 쿼리를 좁힌다. 모든 모델들은 하나 이상의 Manager를 가진다. Manager는 기본으로 objects라는 이름. Queryset은 디비에서 나온 오브젝트들의 컬렉션이다.

  • QuerySet == SELECT
  • filter == WHERE, LIMIT
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Blog instances."

Managers는 모델 클래스에서만 접근 가능한다.

# 모든 오브젝트 가져오기

all_entries = Entry.objects.all()

Manager의 all()메서드를 쓴다. 디비 모든 오브젝트의 쿼리셋을 반환한다.

# Filter로 오브젝트 걸러서 가져오기

Entry.objects.filter(pub_date__year=2006) # all() 안써도 된다.
Entry.objects.all().filter(pub_date__year=2006)

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 30)
... )

Queryset이 evaluated되기 전까진 장고는 쿼리를 돌리지 않는다.

# get()으로 싱글 오브젝트 가져오기

filter()는 싱글 오브젝트를 반환하더라도 쿼리셋으로 반환한다. 하나 오브젝트를 가져오려면 get()을써라.

one_entry = Entry.objects.get(pk=1)

get 안에 filter처럼 쓰면 된다. 쿼리 반환값이 없을 때 get()은 DoesNotExist exception을 뱉는다. 반환값이 1 이상일때는 MultipleObjectsReturned.

# 주석(annotate)

Queryset의 각 오브젝트마다 annotate하기.

>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))
# 첫 번째 블로그 이름
>>> q[0].name
'율무네 블로그'
# 첫 번째 블로그의 엔트리 숫자
>>> q[0].entry__count
10

>>> q = Blog.objects.annotate(number_of_entries=Count('entry'))
# 첫 번째 블로그의 엔트리 숫자. 지정한 이름으로 가져오기
>>> q[0].number_of_entries
42

# 쿼리셋 제한하기

파이썬 Array-slicing 문법을 사용해서 쿼리셋의 서브셋 가져오기. SQL의 LIMIT, OFFSET과 비슷하다.

Entry.objects.all()[:5] # 앞에서부터 5개 오브젝트
Entry.objects.all()[5:10] # 6번부터 10번까지 
Entry.objects.all()[-1] # 에러난다
Entry.objects.all()[:10:2] # 10번째까지 모든 오브젝트들의 2번째 오브젝트
Entry.objects.order_by('headline')[0]

# Field lookups

field이름__lookuptype=value이렇게 쓰면 된다.

Entry.objects.filter(pub_date__lte='2006-01-01')
Entry.objects.filter(blog_id=4) # foreignkey일땐 _id 서픽스를 붙여서 primary key를 찾는다.

Entry.objects.get(headline__exact="개발자 생활백서") # headline이 '개발자 생활백서'인 것을 찾는다.

Blog.objects.get(id__exact=14)  # Explicit form
Blog.objects.get(id=14)         # __exact is implied. 위랑 같다.

Blog.objects.get(name__iexact="beatles blog") # 대소문자 구분 X

Entry.objects.get(headline__contains='Lennon') # 포함

# Span relationships의 Lookups

JOIN을 자동으로 해준다.

Entry.objects.filter(blog__name='율무 블로그') # 블로그모델의 name필드가 `율무 블로그`

Reverse relationship도 참조 가능. 모델 이름을 소문자로 쓴다.

Blog.objects.filter(entry__headline__contains='율무') # 거꾸로 참조

# 두 조건 모두 만족
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

# Spanning multi-valued relationships

M2M필드나 reverse foreignkey를 필터링할때, 두 가지 종류의 필터가 있다.

  • Blog / Entry (one-to-many)
    • 엔트리 제목이 Lennon이면서 2008년에 발행된 블로그는 1개
    • 엔트리 제목이 Lennon이면서 2008년에 발행된 블로그를 찾고싶다.
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008) # 엔트리 제목이 Lennon포함하면서 엔트리 발행일이 2008

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008) # 엔트리 제목이 Lennon포함하는 블로그를 찾고, 거기서 엔트리 발행일이 2008인 블로그를 찾는다. 위의 쿼리셋과 다를 수 있다.

# 모델의 필드를 참조할 수 있는 필터

특정 모델 필드랑 다른 필드 값 비교할 때도 쓸 수 있다.

>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks')) # 커멘트가 핑백보다 수가 많은 Entry 가져오기
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks')) # 레이팅이 커멘트+핑백보다 적은 엔트리 가져오기
>>> Entry.objects.filter(authors__name=F('blog__name')) # 관계를 span하기 위해선 __를 쓴다. 
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3)) # 발행일보다 3일 후 날짜보다 큰 수정일을 가진 엔트리를 가져와라

# pk lookup shortcut

primary key를 좀 더 편하게 참조할 수 있게 pk라고 shortcut을 만듦

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact

# pk가 1이나 4나 7인것
>>> Blog.objects.filter(pk__in=[1,4,7])

# pk가 14보다 큰 것
>>> Blog.objects.filter(pk__gt=14)

>>> Entry.objects.filter(blog__pk=3) # entry의 blog의 pk

# 쿼리셋 캐싱하기

>>> queryset = Entry.objects.all() # 재활용할거 할당해두기
>>> print([p.headline for p in queryset])  
>>> print([p.pub_date for p in queryset])

>>> [entry for entry in queryset] # 데이터베이스 쿼리하기
>>> print queryset[5] # 캐시 쓰기
>>> print queryset[5] # 캐시 쓰기

# Q 오브젝트로 복잡한 lookups 만들기

filter, exclude, get등에 쓸 수 있다.

from django.db.models import Q
Q(question__startswith='What')
Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) # OR연산
)

# 오브젝트 삭제

e.delete()

# 다중 오브젝트 한번에 업데이트 하기

# pub_date가 2007인 모든 엔트리 헤드라인 업데이트하기
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

# 모든 엔트리 업데이트
>>> Entry.objects.all().update(blog=b)

update 메서드는 SQL문으로 바뀌어 바로 적용된다.

# 다대일(One-to-many)

Forward Foreignkey로 연결했으면 그냥 .으로 호출 가능

>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save() #이거 해야 저장됨

>>> e = Entry.objects.get(id=2)
>>> print(e.blog)  # e에서 블로그를 가져올 때 DB를 건드린다.
>>> print(e.blog)  # 캐시 써서 디비를 건드리지 않는다.

>>> e = Entry.objects.select_related().get(id=2) #select_related()
>>> print(e.blog)  # 캐시 써서 디비를 건드리지 않는다.
>>> print(e.blog)  # 캐시 써서 디비를 건드리지 않는다.

Backward 모델이 ForeignKey를 갖고 있으면,

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # 거꾸로 연결되어있던 Entry를 소문자로 바꾸고, 뒤에 _set을 붙여주면 블로그랑 연결된 모든 entry의 쿼리셋 가져온다.

>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()

related_name파라미터를 ForeignKey만들때 쓰면 FOO_set을 오버라이드 할 수 있다.

# related_name 지정해두기
 blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')

# related_name으로 불러오기
>>> b = Blog.objects.get(id=1)
>>> b.entries.all()

# 다대다(Many-to-many)

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

# Refer

https://docs.djangoproject.com/en/1.9/topics/db/queries/