Предварительная выборка сложного запроса, выполняющего объединение

Я пытаюсь предварительно получить связанный объект, чтобы оптимизировать производительность. Код, который я пытаюсь префетчить, выглядит следующим образом;

class Product(models.Model):
    ...
    def get_attribute_values(self):
        # ToDo: Implement this prefetch.
        if hasattr(self, "_prefetched_attribute_values"):
            return self._prefetched_attribute_values

        if not self.pk:
            return self.attribute_values.model.objects.none()

        attribute_values = self.attribute_values.all()
        if self.is_child:
            parent_attribute_values = self.parent.attribute_values.exclude(
                attribute__code__in=attribute_values.values("attribute__code")
            )
            return attribute_values | parent_attribute_values

        return attribute_values

Что это делает следующим образом;

  1. Получите все attribute_values самого себя, attribute_values - это связанная модель ProductAttributeValue
  2. Если это дочерняя модель, также получите родительскую attribute_values, но исключите attribute_values с attribute__codes, которые уже присутствуют в дочерней модели attribute_values Результат
  3. Выполните объединение, которое объединит их вместе.

В настоящее время у меня есть такой префетч;

Prefetch(
    "attribute_values",
    queryset=ProductAttributeValueModel.objects.select_related(
        "attribute", "value_option"
    )
),

Это работает для недетского сценария, но, к сожалению, дочерних продуктов много, и поэтому производительность не так велика.

Так что в идеале я могу префетчить объединенные атрибуты в '_prefetched_attribute_values', хотя я был бы не против сделать и два префетча;

  1. Для самого продукта
  2. Для родителя, но при этом необходимо исключить атрибуты, которые были у самого ребенка

Я пробовал сделать это с помощью Subquery & OuterRef, но пока безуспешно.

В итоге я пришел к следующему решению, которое, похоже, работает.

        # The base queryset for both self and parent attribute values.
        prefetch_queryset = (
            ProductAttributeValue.objects.all()
            .select_related("attribute", "value_option", "value_option__group")
            .prefetch_related(
                Prefetch(
                    "value_multi_option",
                    queryset=AttributeOption.objects.select_related("group"),
                )
            )
            .annotate(code=F("attribute__code"))
        )

        # Subquery to get the child's attribute codes
        child_attribute_codes = ProductAttributeValue.objects.filter(
            product=OuterRef("product__children")
        ).values("attribute__code")

        parent_prefetch_queryset = prefetch_queryset.exclude(
            Exists(
                child_attribute_codes.filter(
                    attribute__code=OuterRef("attribute__code")
                )
            )
        )

        # pylint: disable=not-callable
        queryset = self.select_related("parent").prefetch_related(
            Prefetch(
                "attribute_values",
                queryset=prefetch_queryset,
                to_attr="_prefetched_attribute_values",
            ),
            Prefetch(
                "parent__attribute_values",
                queryset=parent_prefetch_queryset,
                to_attr="_prefetched_parent_attribute_values",
            ),
        )
Вернуться на верх