이 포스팅은 장고 공식 문서를 기반으로 했습니다.
F() expressions
F() 객체는 모델 필드의 값, 모델 필드의 변환된 값 또는 annotated 된 열을 나타낸다. 실제로 DB에서 Python 메모리로 꺼낼 필요 없이 모델 필드 값을 참조(성능의 효율성)하고 이를 사용하여 DB 작업을 수행할 수 있다.
Django는 F() 객체를 사용하여 DB 수준에서 필요한 작업을 설명하는 SQL식을 생성한다. 일반적으로 다음과 같은 일을 할 수 있다.
# Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()
여기서 우리는 DB에서 reporter.stories_field의 값을 메모리로 가져와 파이썬 연산자를 이용하여 조작한 다음 DB에 다시 저장했다(race condition이 발생할 수 있음). 하지만 다음과 같은 일도 할 수 있다.
from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
reporter.stories_filed = F('stroies_filed') + 1은 인스턴스 속성에 대한 Python 메모리 할당처럼 보이지만, 실제로는 DB에 대한 작업을 설명하는 SQL 구조이다.
Django가 F()의 인스턴스를 만나면 표준 Python 연산자를 재정의하여 캡슐화된 SQL 식을 만든다. 이 경우에는 reporter.stories_filed로 표시되는 필드를 증분하도록 DB에 지시한다.
값의 처리는 DB에 의해 처리된다. 파이썬은 장고의 F() 클래스를 통해 필드를 참조하고 작업을 설명하는 SQL 구문을 만드는 것이 전부이다. 이렇게 저장된 새 값에 액세스 하려면 개체를 다시 로드한다.
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
위와 같이 단일 인스턴스의 작업에 사용되는 것 외에도 F()는 업데이트()와 함께 객체 인스턴스의 QuerySets에서 사용할 수 있다. 위에서 사용한 두개의 쿼리(get() 및 save())가 하나로 줄어든다.
reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)
update()를 사용하면 여러 개체의 필드 값을 증가시킬 수 있다. 이는 DB에서 모든 개체를 Python으로 끌어다 놓고 이 개체위에 루프 하고, 각 개체의 필드 값을 증가시키고, 각 개체를 DB에 다시 저장하는 것보다 훨씬 빠르다.
Reporter.objects.update(stories_filed=F('stories_filed') + 1)
따라서 F()는 1. Python 대신 DB 작업 수행, 2. DB 작업 수행에 필요한 쿼리 수 감소 등의 이점이 있다.
Avoding Race Condition
F()의 또 다른 유용한 점은 필드의 값을 Python이 아닌 DB로 업데이트하면 경쟁 조건을 피할 수 있다는 것이다. 위의 첫 번째 예제에서 두개의 파이썬 스레드가 코드를 실행하면 한 스레드는 다른 스레드가 필드 값을 DB에서 검색한 후에 검색, 증분 및 저장할 것이다. 두 번째 스레드가 저장하는 값은 원래 값을 기준으로 하며, 첫 번째 스레드의 작업은 손실된다.
DB가 필드 업데이트를 담당하는 경우 프로세스가 더 강력하다. 즉 인스턴스를 검색할 때 값을 기반으로 하는 대신 save() 또는 update()가 실행될 때만 DB 필드 값을 기반으로 필드를 업데이트 한다.
F() assignments persist after Model.save()
모델 필드에 할당된 F() 개체는 모델 인스턴스를 저장한 후에도 유지되며. save()할 때마다 적용된다. 예를 들어
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
reporter.name = 'Tintin Jr.'
reporter.save()
이 경우 stories_filed가 두 번 업데이트된다. 처음 값이 1이라면 최종 값은 3이 된다. refresh_from_db()를 사용하여 모델 개체를 저장한 후 다시 로드하면 이러한 지속성을 피할 수 있다.
Using F() in filters
F() 표현식은 QuerySet filter에서 매우 유용하다. 이 필터는 파이썬 값이 아닌 필드 값을 기반으로 기준에 따라 개체 집합을 필터링할 수 있게 한다. 자세한 사항 using F() expressions in queries 문서 참조
Using F() with annotations
F()는 다른 필드를 산술과 결합하여 모델에 동적 필드를 만드는 데 사용할 수 있다.
company = Company.objects.annotate(
chairs_needed=F('num_employees') - F('num_chairs'))
결합하는 필드의 타입이 다른 경우 장고에게 어떤 필드가 반환되는지 알려주어야 한다. F()는 output_filed를 지원하지 않으므로 ExpreesionWrapper로 감싸야한다.
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F('active_at') + F('duration'), output_field=DateTimeField()))
ForeignKey와 같은 관계형 필드를 참조할 때 F()는 모델 인스턴스가 아닌 기본 키 값을 반환한다.
>> car = Company.objects.annotate(built_by=F('manufacturer'))[0]
>> car.manufacturer
<Manufacturer: Toyota>
>> car.built_by
3
Using F() to sort null values
F()와 nulls_first 또는 nulls_last 키워드 인수를 Expression.asc() 또는 desc()에 사용하여 필드의 null 값 순서를 제어한다. 기본적으로 데이터베이스에 따라 순서가 달라진다. 예를 들어, 연락을 받은 회사 다음에 연락을 받지 않은 회사를 정렬하려면(last_contacted is null):
from django.db.models import F
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))
참고
1. https://docs.djangoproject.com/en/4.1/ref/models/expressions/#built-in-expressions
2. https://blog.myungseokang.dev/posts/django-f-class/
'Django' 카테고리의 다른 글
[DRF] SearchFilter (0) | 2023.02.16 |
---|---|
[QuerySet] Q() Objects 알아보기 (0) | 2023.01.18 |
[Queryset] select_related와 prefetch_related 살펴보기 (0) | 2023.01.13 |
[DRF] HyperlinkedModelSerializer 살펴보기 (0) | 2023.01.12 |
[DRF] ModelSerializer 살펴보기 (0) | 2023.01.11 |