Предварительная выборка сложного запроса, выполняющего объединение
Я пытаюсь предварительно получить связанный объект, чтобы оптимизировать производительность. Код, который я пытаюсь префетчить, выглядит следующим образом;
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
Что это делает следующим образом;
- Получите все
attribute_values
самого себя,attribute_values
- это связанная модельProductAttributeValue
- Если это дочерняя модель, также получите родительскую
attribute_values
, но исключитеattribute_values
сattribute__codes
, которые уже присутствуют в дочерней моделиattribute_values
Результат - Выполните объединение, которое объединит их вместе.
В настоящее время у меня есть такой префетч;
Prefetch(
"attribute_values",
queryset=ProductAttributeValueModel.objects.select_related(
"attribute", "value_option"
)
),
Это работает для недетского сценария, но, к сожалению, дочерних продуктов много, и поэтому производительность не так велика.
Так что в идеале я могу префетчить объединенные атрибуты в '_prefetched_attribute_values', хотя я был бы не против сделать и два префетча;
- Для самого продукта
- Для родителя, но при этом необходимо исключить атрибуты, которые были у самого ребенка
Я пробовал сделать это с помощью 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",
),
)