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

发布于 2024-10-22 01:21:41 字数 172 浏览 3 评论 0原文

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

,理论上

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

这是不可能的。

我该如何解决这个问题?

干杯, 米奇

I have a query that requires to filter exactly 2 authors with the ID

Theoretically,

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

which is not possible.

How can I solve this problem?

Cheers,
Mickey

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

暮倦 2024-10-29 01:21:42

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

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.

Not intuitive at first but the answer is right in front of us.

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

If you want an exact match, you could potentially further filter this result by those items that only have exactly 2 authors.

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

If you want exact matches dynamically, how about something like this?:

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.
成熟的代价 2024-10-29 01:21:42

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

如果后端是 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))

New questions are pointing to this one as a duplicate, so here is an updated answer (for one specific backend).

If the backend is Postgres, the SQL you want is (assuming the M2M table is called bookauthor):

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

You can get Django to generate nearly this SQL.

First, pip install django-sql-utils. Then create this Array class:

from django.db.models import Func

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

And now you can write your ORM queryset:

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))
转身以后 2024-10-29 01:21:42

使用 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])
)

我使用了 distinctordering< /code> 以确保 ID 不会重复并且始终保持相同的顺序。

此解决方案在生成的 SQL 中仅生成一个 JOIN,而链接多个 .filter() 方法会在查询中生成多个 JOIN

When using Postgres as a database, you could use ArrayAgg to collect author IDs in an array and then filter by that array:

from django.contrib.postgres.aggregates import ArrayAgg

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

I've used distinct and ordering to make sure that IDs will have no duplicates and will always be in the same order.

This solution produces only one JOIN in the resulting SQL, whereas chaining several .filter() methods produces several JOINs in the query.

奈何桥上唱咆哮 2024-10-29 01:21:42

Q 对象会帮助你。 文档< /a>

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

Q objects will help you. Docs

Book.objects.filter(Q(author__id=1) & Q(author__id=2))
错爱 2024-10-29 01:21:42

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

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

You can use an "IN" query.
Django Docs

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文