With the following models:
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
If I want to find order operations containing at least one article, this works as expected:
OrderOperation.objects.filter(ordered_articles__in=articles)
However, if I want to find order operations with all the articles in the order, what is the correct way to do it?
OrderOperation.objects.filter(ordered_articles=articles)
raises a ProgrammingError: more than one row returned by a subquery used as an expression
error (I understand why actually).
A simple solution:
order_operations = OrderOperation.objects.all()
for article in articles:
order_operations = order_operations.filter(ordered_articles=article)
It's just one query, but with an inner join per article. For more than a few articles Willem’s more ingenious solution should perform better.
We can first construct a set
of articles:
articles_set = set(articles)
Next we can count the number of articles related to the OrderOperation
that appear in that set, and check if that number is equal to the size of that set, like:
from django.db.models import Count
OrderOperation.objects.filter(
ordered_articles__in=articles_set
).annotate(
narticles=Count('ordered_articles')
).filter(
narticles=len(articles_set)
)
Since in a ManyToManyField
, each Article
can occur once per OrderOperation
, if the number of related Article
s that are in the article_set
is the same as the number of elements in the article_set
, we thus know that the two sets are the same.
This will create a query that looks like:
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)
where the article_set
and len(article_set)
are of course replaced by the primary keys of the articles in the set, or the number of elements in that set.
来源:https://stackoverflow.com/questions/54841016/filtering-on-many-to-many-relations-that-fulfill-a-set-of-criteria