Shshzik
@Shshzik
Начинающий

Можно ли использовать Prefetch Related c той же моделью?

Есть модель:
class Product(models.Model):
    name = models.CharField()
    group_id = models.CharField()
    is_main = models.BooleanField()

    def child(self):
        return Product.objects.filter(group_id=self.group_id)


На странице беру:
products = Product.objects.filter(is_main=True)
и вывожу.
{% for p in products %}
<ul>
    {% for c in p.child %}
        <li>{{ c.name }}</li>
    {% endfor %}
</ul>
{% endfor %}

Соответственно на каждый такой цикл генерируется свой запрос в БД. Для малого количества это не атк критично, но если увеличивать количество продуктов, то количество запросов растёт в алгебраической прогрессии.
Если была бы зависимость FK или M2M то можно было бы использовать prefetch_related. Но тут такое не получится. Что-то читал про Subquery, но так ничего и не накопал.
Можно ли как-нибудь уменьшить количество запросов к БД?
  • Вопрос задан
  • 325 просмотров
Решения вопроса 1
Насколько знаю, такой финт ушами стандартными средствами джанги не предусмотрен :)
Но если уж очень хочется получить данный эффект, его можно "сымитировать":

products = Product.objects.filter(is_main=True)
groups = [item.group_id for item in products]

children = Product.objects.filter(group_id__in=groups).order_by('group_id')
children = {
    grouper: elements for grouper, elements in
    groupby(children, key=lambda x: x.group_id)
}

for product in products:
    product.children = children.get(product.group_id, [])


{% for p in products %}
    <ul>
        {% for c in p.children %}
            <li>{{ c.name }}</li>
        {% endfor %}
    </ul>
{% endfor %}


P.S. Вопрос необходимости данных манипуляций оставлю на ваше усмотрение :)
P.P.S. код не проверял.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@deliro
Использовать-то можно, только у тебя нет связи модели с самой собой. В prefetch_related ключевое — relation.

Вот то, что тебе нужно:
main_products = Product.objects.filter(is_main=True)
all_products = Product.objects.filter(
    Q(is_main=True) | Q(group_id__in=main_products.values_list("group_id", flat=True))
)


Вот бессмысленный пример, который показывает, что подзапросы действительно работают:

str(Review.objects.filter(Q(user_id__gt=4000) | Q(user_id__in=Review.objects.values_list('user_id', flat=True))).query)
Out[10]: 'SELECT "reviews_review"."id", "reviews_review"."user_id", "reviews_review"."email", "reviews_review"."rating", "reviews_review"."comment", "reviews_review"."datetime" FROM "reviews_review" WHERE ("reviews_review"."user_id" > 4000 OR "reviews_review"."user_id" IN (SELECT U0."user_id" AS Col1 FROM "reviews_review" U0))'
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы