【问题标题】:Filtering on many-to-many relations that fulfill a set of criteria过滤满足一组标准的多对多关系
【发布时间】:2025-11-28 05:35:02
【问题描述】:

具有以下型号:

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


    【解决方案1】:

    一个简单的解决方案:

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

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

    【讨论】:

    • 谢谢!我切换到这个作为公认的答案,因为它更易于阅读,并且当有几篇文章时执行正常,正如你所说:)
    【解决方案2】:

    我们可以先构造一个set的文章:

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

    接下来我们可以计算出现在该集合中的与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 中,如果article_set 中的相关Articles 的数量与@987654331 中的元素数量相同,则每个Article 可以在每个OrderOperation 中出现一次@,因此我们知道这两个集合是相同的。

    这将创建一个如下所示的查询:

    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) 当然会被集合中文章的主键或该集合中元素的数量替换。

    【讨论】:

    • 嗨 :) 似乎filter(ordered_articles__in=articles_set)filter(ordered_articles__in=articles) 具有完全相同的效果。在这两种情况下,它都会过滤至少包含 1 篇文章的订单操作。
    • @DavidD.: 是的没错,我们在related集合上过滤,重点不是过滤,那么我们应该.annotate(..)满足的文章数那个条件,然后过滤那个数字,所以.annotate(..).filter(..) 部分最终可以解决问题。
    • 非常感谢,这很好用!不幸的是,我无法使用您的解决方案,因为我在使用子查询时过度简化了我的问题。我可以请你看一下this question 吗?非常感谢。