Reputation: 3321
I have models of books and people:
from django.db import models
class Book(models.Model):
author = models.ManyToManyField('Person')
class Person(models.Model):
name = models.CharField(max_length=16)
I simplified them a bit here. How do I craft a django query to get all of the authors of the books? With SQL I would do a select on the intermediate table and join it with the people table to get the name, but I'm not sure how to do something similar here... Of course, there are people in the Person table that are not book authors, or I could just get Person.objects.all().
Upvotes: 2
Views: 1740
Reputation: 33420
As easy as 1,2,3 with Filtering on annotations:
from django.db.models import Count
Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)
For curiosity, i generated the SQL from each of the ways proposed on this topic:
In [9]: Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name", COUNT("testapp_book_author"."book_id") AS "count_book" FROM "testapp_person" LEFT OUTER JOIN "testapp_book_author" ON ("testapp_person"."id" = "testapp_book_author"."person_id") GROUP BY "testapp_person"."id", "testapp_person"."name", "testapp_person"."id", "testapp_person"."name" HAVING COUNT("testapp_book_author"."book_id") > 0 LIMIT 21; args=(0,)
Out[9]: [<Person: Person object>]
In [10]: Person.objects.exclude(book=None)
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name" FROM "testapp_person" WHERE NOT (("testapp_person"."id" IN (SELECT U0."id" FROM "testapp_person" U0 LEFT OUTER JOIN "testapp_book_author" U1 ON (U0."id" = U1."person_id") LEFT OUTER JOIN "testapp_book" U2 ON (U1."book_id" = U2."id") WHERE (U2."id" IS NULL AND U0."id" IS NOT NULL)) AND "testapp_person"."id" IS NOT NULL)) LIMIT 21; args=()
Out[10]: [<Person: Person object>]
In [11]: Person.objects.filter(pk__in=Book.objects.values_list('author').distinct())
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name" FROM "testapp_person" WHERE "testapp_person"."id" IN (SELECT DISTINCT U1."person_id" FROM "testapp_book" U0 LEFT OUTER JOIN "testapp_book_author" U1 ON (U0."id" = U1."book_id")) LIMIT 21; args=()
Out[11]: [<Person: Person object>]
Maybe this can help you choose.
Personnaly, i prefer the version by Chris because it is the shortest. On the other hand, I don't know for sure about the impact of having subqueries which is the case for the two other ways. That said, they do demonstrate interresting QuerySet concepts:
Annonation, is aggregation per value of the queryset. If you use aggregate(Count('book')) then you will get the total number of books. If you use annotate(Count('book')) then you get a total number of book per value of the queryset (per Person). Also, each person has a 'count_book' attribute which is pretty cool: Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)[0].count_book
Subqueries, very useful to create complex queries or optimize queries (merge querysets, generic relation prefetching for example).
Upvotes: 3
Reputation: 239290
Easiest way:
Person.objects.filter(book__isnull=False)
That will select all people that have at least one book associated with them.
Upvotes: 3
Reputation: 55962
You can get all the ID's of the authors quite easily using
Book.objects.all().values_list('author', flat=True).distinct()
As jpic points out below
Person.objects.filter(pk__in=Book.objects.values_list('author').distinct())
will give you all the person objects and not just their id's.
Upvotes: 1
Reputation: 1768
This chapter of the django book answers all your model needs with examples very similar to yours, including a ManyToMany relation. http://www.djangobook.com/en/2.0/chapter10/
Upvotes: -1