过滤满足一组条件的多对多关系

Filtering on many-to-many relations that fulfill a set of criteria

有以下型号:

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 错误(我明白为什么)。

我们可以先构造一个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_set 中相关的 Article 的数量与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_setlen(article_set) 当然会替换为集合中文章的主键,或者该集合中的元素数。

一个简单的解决方案:

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

这只是一个查询,但每篇文章都有一个内部联接。不止几篇文章,Willem 更巧妙的解决方案应该表现更好。