TIL 38 | Query Expression - F()

임종성·2021년 8월 5일
1

Django

목록 보기
16/17
post-thumbnail

1차 프로젝트를 진행하면서 Django를 이용한 상품 구매나 결제, 장바구니 기능 구현 글들을 많이 찾아봤다. 그 중 데이터의 sort나 aggregation같은 처리를 할 경우 유독 F()로 데이터 Field를 감싸는 것을 많이 보게 되었다. F()는 무슨 의미를 가지고 있고, 왜 사용하는 걸까?

Query Expressions

Django 공식 문서를 살펴보니 Q(), F()와 같은 표현은 Query Expression이라고 하며, create, update, filter, order by, annotation, aggreagate의 일부로 사용할 수 있는 Value 혹은 Computation이다. 짧은 설명만 봐도 기능 구현에 큰 도움이 될 것 같아 그 중 가장 핵심이라고 하는F()에 대해 알아봤다.

F() Expressions

Django Documentation의 설명을 보면,

F() object는 Model Field나 Annotate된 Column의 Value를 나타낸다. 실제로 데이터베이스에서 Python 메모리로 가져오지 않고, 모델 필드 값을 참조하고 이를 데이터베이스에서 사용하여 작업할 수 있다.

사실 이 글만 봐서는 이해가 잘 안간다. 주어진 Model Field의 값을 Python으로 가져오지 않고 직접 Database에서 처리한다는 것 같은데, 큰 의미가 있을까? Django Documentation의 예제를 살펴보자.

# Normal Condition
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()

# Use F()
from django.db.models import F

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()

똑같이 Databse의 stories_field 값은 +1 처리 되었을 것이다. 그러나 F()를 사용하면 실제로 코드에 보이는 것처럼 Python Operator를 사용한 것이 아니고, Python 연산자를 override 해서 캡슐화된 SQL문을 만든다.

이 작업은 Database 내에서만 이루어지므로, Python에서는 reporter.stories_Field의 값을 알 수 없다. 아래 코드로 쉽게 이해해보자.

# 예시로 보자

product = Product.objects.get(name="Wecode")
product.price			# product.price = 100
product.price += 1		# product.price = 101

product = Product.objects.get(name="Wecode")
product.price			# product.price = 100
product.price = F('price') + 1  # product.price = <CombinedExpression: F(price) + Value(1)>
product.save()
product.price 			# product.price = <CombinedExpression: F(price) + Value(1)>
product.price.refresh_from_db() # 저장한 값을 사용하기 위해 사용해줘야 한다.
product.price			# product.price = 101

이와 같이 실제로 python에서 연산하는 것이 아니라 db에서 연산하기 때문에 다시 product = Product.objects.get(name="Wecode")로 선언해서 db로부터 재선언해주거나 refresh_from_db()를 사용해 불러온 값을 refresh한다.

Why Use F()?

왜 굳이 F()를 사용하는걸까? F() Expression을 사용했을때의 장점을 몇가지 살펴보자.

1. Python으로 데이터를 가져오지 않고 Database에서 해당 연산을 처리한다.
2. 연산이 필요한 경우 Query 수를 줄일 수 있다.

products = Product.objects.all()
for product in products:
    product.price *= 1.2 
product.save()
# F() 사용
Product.objects.all().update(F('price') * 1.2)

3. Race Condition(경쟁조건)을 피할 수 있다.

python의 여러 thread에서 product.price를 1씩 증가시킨다고 가정하자. 만약 python 연산자로 +=1 해줄때 1번 thread가 먼저 작업을 하고 2번 thread가 연산한다면 1번 thread의 작업은 손실되고 Database에는 그냥 +1로 계산할 것이다.
하지만 F()를 사용하면 각 연산이 처리될 때 Database에서 연산을 하기 때문에 경쟁조건을 피할 수 있고, Database에는 의도한대로 +2로 연산될 것이다.

4. Sort, Annotate와 같은 연산에 유용하게 쓰인다.

company = Company.objects.annotate(
    chairs_needed=F('num_employees') - F('num_chairs'))

위와 같이 anootate로 Model Instance의 다른 Field 값을 참조하여 연산을 통해 동적으로 Field를 추가해줄 수 있다. 이는 상품의 가격과 할인과 같은 연산을 할 때 유용할 것이다.


이번엔 Query Expression중 F()만 블로깅했으나, 다음엔 Q()와 같은 다양한 Expression들을 더 알아보겠다. 이외에도 Transaction, QueryString, RESTful API 등 개념을 이해해야 좋은 것들에 대해 블로깅해야겠다.

참고자료
Django Documentation
Django F() 객체 알아보기
Django Query

profile
어디를 가든 마음을 다해 가자

1개의 댓글

comment-user-thumbnail
2021년 8월 8일

Why Use F()? 부분이 정말 좋네요! 항상 유용한 블로깅 감사드립니다. 👍🏼👍🏼👍🏼👍🏼👍🏼

답글 달기