如何进行多对多 Django 查询来查找具有 2 个给定作者的书籍?

问题描述 投票:0回答:5

我有一个查询,需要使用 ID 过滤 2 位作者

理论上,

Book.objects.filter(author__id=1, author__id=2). 

这是不可能的。

如何解决这个问题?

干杯, 米奇

python django many-to-many django-queryset
5个回答
51
投票

一开始并不直观,但答案就在我们面前。

Book.objects.filter(author__id=1).filter(author__id=2)

如果您想要完全匹配,您可以通过那些只有 2 位作者的项目进一步过滤此结果。

Book.objects.annotate(count=Count('author')).filter(author__id=1)\
                .filter(author__id=13).filter(count=2)

如果你想要动态精确匹配,像这样怎么样?:

def get_exact_match(model_class, m2m_field, ids):
    query = model_class.objects.annotate(count=Count(m2m_field))\
                .filter(count=len(ids))
    for _id in ids:
        query = query.filter(**{m2m_field: _id})
    return query

matches = get_exact_match(MyModel, 'my_m2m_field', [1, 2, 3, 4])

# matches is still an unevaluated queryset, so you could run more filters
# without hitting the database.

1
投票

新问题指向此问题重复,因此这里是更新的答案(针对一个特定后端)。

如果后端是Postgres,那么你想要的SQL是(假设M2M表名为

bookauthor
):

SELECT *
FROM book
WHERE
    (SELECT ARRAY_AGG(bookauthor.author_id)
     FROM bookauthor
     WHERE bookauthor.book_id = book.id) = Array[1, 2];

你可以让 Django 生成几乎这个 SQL。

首先,

pip install django-sql-utils
。然后创建这个
Array
类:

from django.db.models import Func

class Array(Func):
    function = 'ARRAY'
    template = '%(function)s[%(expressions)s]'

现在您可以编写 ORM 查询集:

from sql_util.utils import SubqueryAggregate
from django.contrib.postgres.aggregates import ArrayAgg

books = Book.objects.annotate(
            author_ids=SubqueryAggregate('author__id', Aggregate=ArrayAgg)
        ).filter(author_ids=Array(1, 2))

0
投票

当使用 Postgres 作为数据库时,您可以使用

ArrayAgg
收集数组中的作者 ID,然后按该数组进行过滤:

from django.contrib.postgres.aggregates import ArrayAgg

qs = (
    Book.objects
    .annotate(
        authors=ArrayAgg("author__id", distinct=True, ordering=("author__id",))
    )
    .filter(authors=[1, 2])
)

我使用了

distinct
ordering
来确保 ID 不会重复并且始终保持相同的顺序。


-3
投票

Q 对象会帮助你。 文档

Book.objects.filter(Q(author__id=1) & Q(author__id=2))

-6
投票

您可以使用“IN”查询。 Django 文档

Book.objects.filter(author__id__in=[1,2])

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