Skip to content
On this page

Reverse Foreign Key 필터링 시 annotation subquery 필터와 역참조 필터 비교

Annotation Filter

개요

Django ORM에서 Reverse Foreign Key를 필터링 할 때 두가지 방법이 있다.

  1. 서브쿼리와 annotation, OuterRef를 사용
  2. 모델에 설정한 related_name을 이용한 역참조 필터링

1번 방법은 다양한 조건문을 걸거나 기존 QuerySet에 미리 만들어놓은 filter를 사용할 수 있는 장점이 있고, 2번 방법은 코드가 간단해지는 장점이 있다.

테이블 구조

Product

  • id: int

ProductPromotion

  • id: int
  • product_id: Product의 fk
  • product_promotion_type: string
erDiagram
products_product {
int id
}
promotions_product_promotion {
int id
int product_id
string product_promotion_type
}
products_product ||--o{ promotions_product_promotion : uses

Reverse Foreign Exclude 필터링

Django ORM

Product.objects.exclude(
product_product_promotions__product_promotion_type__in=('TYPE_1', 'TYPE_2')
)

SQL

SELECT
products_product.id
FROM
products_product
WHERE
NOT (
products_product.id IN (
SELECT
U1.product_id
FROM
promotions_productpromotion U1
WHERE (
U1.product_promotion_type IN ('TYPE_1','TYPE_2')
AND U1.product_id IS NOT NULL
)
)
)
;

Query Explain

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYproducts_product-index-CENSORED5-9812100Using where; Using index
2SUBQUERYU1-rangeCENSORED,CENSOREDCENSORED258-18150Using index condition; Using where

annotation과 subquery를 이용한 exclude 필터링

Python

Product.objects.annotate(
_has_special_deal=Exists(
ProductPromotion.objects.filter(product_id=OuterRef('id')).filter(
product_promotion_type__in=('TYPE_1','TYPE_2')
)
)
).filter(_has_special_deal=False)

SQL

SELECT
products_product.id,
EXISTS(
SELECT
U0.id
FROM
promotions_productpromotion U0
WHERE (
U0.product_promotion_type IN ('TYPE_1', 'TYPE_2')
AND U0.product_id = products_product.id
)
) AS _has_special_deal
FROM
products_product
WHERE
NOT EXISTS(
SELECT
U0.id
FROM
promotions_productpromotion U0
WHERE (
U0.product_promotion_type IN ('TYPE_1', 'TYPE_2')
AND U0.product_id = products_product.id
)
)
;

Query Explain

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1PRIMARYproducts_product-index-CENSORED5-9812100Using index
1PRIMARYsubquery3-eq_refauto_distinct_keyauto_distinct_key5CENSORED.products_product.id1100Using where; Not exists
3MATERIALIZEDU0-ALLCENSORED,CENSORED--31813100Using where
2DEPENDENT SUBQUERYU0-refCENSORED,CENSOREDCENSORED5CENSORED.products_product.id41.11Using where

결론

annotation을 이용하여 filter를 하면, reverse foreign 필터와 WHERE절은 같지만 annotation으로 인해 Subquery가 SELECT 절에도 생기게 된다.

Query Explain결과도 type을 보면 index -> range에서 index -> eq_ref -> ALL -> ref로 단계가 훨씬 늘어나고, 심지어 풀스캔을 도는것을 확인할 수 있다. (해당 부분은 인덱스 설정 방법에 따라 달라질 수 있다.)

따라서 별도로 annotation을 이용한 필드가 필요한것이 아닌 단순 filter 용이라면 되도록 annotation 사용을 지양해야한다.