Фильтрация по отношениям «многие ко многим», которые отвечают набору критериев - PullRequest
0 голосов
/ 23 февраля 2019

Со следующими моделями:

class OrderOperation(models.Model):
    ordered_articles = models.ManyToManyField(Article,
                                              through='orders.OrderedArticle')

class OrderedArticle(models.Model):
    order_operation = models.ForeignKey(OrderOperation)
    article = models.ForeignKey(Article)

articles = ... # some queryset containing multiple articles

Если я хочу найти операции заказа, содержащие хотя бы одну статью, это работает должным образом:

OrderOperation.objects.filter(ordered_articles__in=articles)

Однако, если я хочу найти операции заказа со всеми статьями в заказе, как правильно это сделать?

OrderOperation.objects.filter(ordered_articles=articles) вызывает ошибку ProgrammingError: more than one row returned by a subquery used as an expression (я понимаю, почему на самом деле).

Ответы [ 2 ]

0 голосов
/ 23 февраля 2019

Простое решение:

order_operations = OrderOperation.objects.all()
for article in articles:
    order_operations = order_operations.filter(ordered_articles=article)

Это всего лишь один запрос, но с внутренним объединением для каждой статьи.Для более чем нескольких статей более изобретательное решение Виллема должно работать лучше.

0 голосов
/ 23 февраля 2019

Сначала мы можем построить set статей:

articles_set = <b>set(</b>articles<b>)</b>

Затем мы можем подсчитать количество статей, связанных с OrderOperation, которые появляются в этом наборе, и проверить, равно ли это числок размеру этого набора, например:

from django.db.models import <b>Count</b>

OrderOperation.objects.filter(
    ordered_articles__in=articles_set
).annotate(
    <b>narticles=Count('ordered_articles')</b>
).filter(
    narticles=<b>len(articles_set)</b>
)

Так как в ManyToManyField каждый Article может встречаться один раз за OrderOperation, если число связанных Article s, которые находятся вarticle_set равно количеству элементов в article_set, поэтому мы знаем, что эти два набора одинаковы.

Это создаст запрос, который выглядит следующим образом:

SELECT orderoperation.*
       COUNT(orderoperation_article.article_id) AS narticle
FROM orderoperation
JOIN orderoperation_article ON orderoperation_id = orderoperation.id
WHERE orderoperation.article_id IN (<i>article_set</i>)
GROUP BY orderoperation.id
HAVING COUNT(orderoperation_article.article_id) = <i>len(article_set)</i>

где article_set и len(article_set), конечно, заменены первичными ключами статей в наборе или количеством элементов в этом наборе.

...