How to do many-to-many Django query to find book with 2 given authors?

后端 未结 4 492
夕颜
夕颜 2020-11-30 02:57

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

Theoretically,

Book.objects.filter(author__id=1, author__id=2). 
相关标签:
4条回答
  • 2020-11-30 03:02

    You can use an "IN" query. Django Docs

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

    0 讨论(0)
  • 2020-11-30 03:12

    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))
    
    0 讨论(0)
  • 2020-11-30 03:20

    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.
    
    0 讨论(0)
  • 2020-11-30 03:25

    Q objects will help you. Docs

    Book.objects.filter(Q(author__id=1) & Q(author__id=2))
    
    0 讨论(0)
提交回复
热议问题