검색기능 트러블슈팅 정리

우소라·2023년 7월 6일
def ordering_queryset(queryset, ordering):
    
    order_items_qs = OrderItem.objects.filter(
    product_id=OuterRef("id")
    ).values('product_id').annotate(sales_count=Sum("amount")).values("sales_count")

    """쿼리셋 정렬 함수"""
    orderings = {
        "popularity": queryset.annotate(num_wishlists=Count("wish_lists")).order_by(
            "-num_wishlists"
        ),
        "stars":  queryset.annotate(stars=Avg("product_reviews__star")).order_by("-stars"),
        "expensive": queryset.order_by("-price"),
        "cheap": queryset.order_by("price"),
        "sales": queryset.annotate(
            sales_count=Subquery(order_items_qs, output_field=models.IntegerField())
        ).order_by("-sales_count"),
    }
    return orderings[ordering]

  1. 판매량순
order_items_qs = OrderItem.objects.filter(
    product_id=OuterRef("id")
    ).values('product_id').annotate(sales_count=Sum("amount")).values("sales_count")
"sales": queryset.annotate(
            sales_count=Subquery(order_items_qs, output_field=models.IntegerField())
        ).order_by("-sales_count"),
    }

1) OuterRef :OuterRef 클래스는 Django의 F 클래스를 상속받고, F 클래스는 쿼리셋의 필터 조건에서 데이터베이스 필드를 참조할 때 사용

OuterRef 클래스

class OuterRef(F):
    contains_aggregate = False

    def resolve_expression(self, *args, **kwargs):
        if isinstance(self.name, self.__class__):
            return self.name
        return ResolvedOuterRef(self.name)

    def relabeled_clone(self, relabels):
        return self

isinstanceof가 아닐때 실행되는 ResolvedOuterRef 클래스

class ResolvedOuterRef(F):
    """
    An object that contains a reference to an outer query.

    In this case, the reference to the outer query has been resolved because
    the inner query has been used as a subquery.
    """

    contains_aggregate = False
    contains_over_clause = False

    def as_sql(self, *args, **kwargs):
        raise ValueError(
            "This queryset contains a reference to an outer query and may "
            "only be used in a subquery."
        )

    def resolve_expression(self, *args, **kwargs):
        col = super().resolve_expression(*args, **kwargs)
        if col.contains_over_clause:
            raise NotSupportedError(
                f"Referencing outer query window expression is not supported: "
                f"{self.name}."
            )
        # FIXME: Rename possibly_multivalued to multivalued and fix detection
        # for non-multivalued JOINs (e.g. foreign key fields). This should take
        # into account only many-to-many and one-to-many relationships.
        col.possibly_multivalued = LOOKUP_SEP in self.name
        return col

    def relabeled_clone(self, relabels):
        return self

    def get_group_by_cols(self):
        return []

OuterRef와 ResolvedOuterRef에서 상속하는 F클래스

@deconstructible(path="django.db.models.F")
class F(Combinable):
    """An object capable of resolving references to existing query objects."""

    def __init__(self, name):
        """
        Arguments:
         * name: the name of the field this expression references
        """
        self.name = name

    def __repr__(self):
        return "{}({})".format(self.__class__.__name__, self.name)

    def resolve_expression(
        self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
    ):
        return query.resolve_ref(self.name, allow_joins, reuse, summarize)

    def replace_expressions(self, replacements):
        return replacements.get(self, self)

    def asc(self, **kwargs):
        return OrderBy(self, **kwargs)

    def desc(self, **kwargs):
        return OrderBy(self, descending=True, **kwargs)

    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.name == other.name

    def __hash__(self):
        return hash(self.name)

    def copy(self):
        return copy.copy(self)

2) Subquery 클래스


class Subquery(BaseExpression, Combinable):
    """
    An explicit subquery. It may contain OuterRef() references to the outer
    query which will be resolved when it is applied to that query.
    """

    template = "(%(subquery)s)"
    contains_aggregate = False
    empty_result_set_value = None
    subquery = True

    def __init__(self, queryset, output_field=None, **extra):
        # Allow the usage of both QuerySet and sql.Query objects.
        self.query = getattr(queryset, "query", queryset).clone()
        self.query.subquery = True
        self.extra = extra
        super().__init__(output_field)

    def get_source_expressions(self):
        return [self.query]

    def set_source_expressions(self, exprs):
        self.query = exprs[0]

    def _resolve_output_field(self):
        return self.query.output_field

    def copy(self):
        clone = super().copy()
        clone.query = clone.query.clone()
        return clone

    @property
    def external_aliases(self):
        return self.query.external_aliases

    def get_external_cols(self):
        return self.query.get_external_cols()

    def as_sql(self, compiler, connection, template=None, **extra_context):
        connection.ops.check_expression_support(self)
        template_params = {**self.extra, **extra_context}
        subquery_sql, sql_params = self.query.as_sql(compiler, connection)
        template_params["subquery"] = subquery_sql[1:-1]

        template = template or template_params.get("template", self.template)
        sql = template % template_params
        return sql, sql_params

    def get_group_by_cols(self):
        return self.query.get_group_by_cols(wrapper=self)

따라서,

"sales": queryset.annotate(
            sales_count=Subquery(order_items_qs, output_field=models.IntegerField())
        ).order_by("-sales_count"),

1) queryset.annotate는 전 단계에서 그룹화한 주문 상품들의 수량(amount)을 합산하여 새로운 필드인 sales_count에 저장한다는 뜻

2) 그런데 order_items_qs와 output_filed의 관계는 BaseExpression, Combinable 기본표현과 융합 가능한 객체(?)의 관계임,
3) ("-sales_count") 에 의해서 정렬되는데, 여기서 order_by는 queryset의 내장 메소드이고 말 그대로 정렬된다는 뜻임

def order_by(self, *field_names):
        """Return a new QuerySet instance with the ordering changed."""
        if self.query.is_sliced:
            raise TypeError("Cannot reorder a query once a slice has been taken.")
        obj = self._chain()
        obj.query.clear_ordering(force=True, clear_default=False)
        obj.query.add_ordering(*field_names)
        return obj

4) 따라서 판매수에 의해서 정렬된 Subquery의 내용이 sales_count라는 새로운 변수에 저장이 됨 , 그 Subquery의 내용은 order_item_qs이며, 쿼리의 외부 필드에서 합쳐진 내용

5) 그렇다면 order_item_qs란?
users의 OrderItem의 객체에서 상품 id에 의해 필터링된 product_id라는 값의 합들을 sales_count에 저장함

여기서 OuterRef는 F클래스를 상속받은 데이터베이스 필드를 참조하는데, F클래스 자리에 id값이 들어갔으니까, id값을 가진 데이터베이스 필드를 외부참조해오는 것임 또한 그 값을 sales_count라는 변수에 OrderItem에서 product_id 수량의 총합(Sum)을 저장하기로 한것

0개의 댓글