过滤满足一系列标准的多对多关系

问题描述 投票:1回答:2

使用以下型号:

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

python django many-to-many django-orm
2个回答
3
投票

简单的解决方案:

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

它只是一个查询,但每篇文章都有一个内连接。对于超过几篇文章,Willem的更巧妙的解决方案应该表现更好。


1
投票

我们可以先构建一篇set文章:

articles_set = set(articles)

接下来,我们可以计算与该集合中出现的OrderOperation相关的文章数量,并检查该数字是否等于该集合的大小,如:

from django.db.models import Count

OrderOperation.objects.filter(
    ordered_articles__in=articles_set
).annotate(
    narticles=Count('ordered_articles')
).filter(
    narticles=len(articles_set)
)

因为在ManyToManyField中,每个ArticleOrderOperation可以出现一次,如果在Article中相关article_sets的数量与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 (article_set)
GROUP BY orderoperation.id
HAVING COUNT(orderoperation_article.article_id) = len(article_set)

其中article_setlen(article_set)当然被集合中文章的主键或该集合中的元素数量所取代。

© www.soinside.com 2019 - 2024. All rights reserved.