I have two models like this:
class User(models.Model): email = models.EmailField() class Report(models.Model): user = models.ForeignKey(User)
In reality each model has more fields which are of no consequence to this question.
I want to filter all users who have an email which starts with 'a' and have no reports. There will be more .filter()
and .exclude()
criteria based on other fields.
I want to approach it like this:
users = User.objects.filter(email__like = 'a%') users = users.filter(<other filters>) users = ???
I would like ??? to filter out users who do not have reports associated with them. How would I do this? If this is not possible as I have presented it, what is an alternate approach?
The filter() method is used to filter you search, and allows you to return only the rows that matches the search term.
To do a not equal in Python Django queryset filtering, we can negate a equal with ~ . to call filter with the Q object negated with ~ to return all the Entry results that don't have id 3.
SELECT user.pk, user. email, NOT EXISTS (SELECT U0.pk FROM reports U0 WHERE U0. user = user.pk) AS no_reports FROM user WHERE email LIKE 'a%' AND NOT EXISTS (SELECT U0.pk FROM reports U0 WHERE U0. user = user.pk);
To filter a Python Django query with a list of values, we can use the filter method with in . to search Blog entries with pk set to 1,4 or 7 by calling Blog. objects. filter with the pk_in argument set to [1, 4, 7] .
Note: this answer was written in 2013 for Django 1.5. See the other answers for better approaches that work with newer versions of Django
Use isnull
.
users_without_reports = User.objects.filter(report__isnull=True) users_with_reports = User.objects.filter(report__isnull=False).distinct()
When you use isnull=False
, the distinct()
is required to prevent duplicate results.
As of Django 3.0 you can now use expressions directly in a filter()
, removing the unnecessary SQL clause:
User.objects.filter( ~Exists(Reports.objects.filter(user__eq=OuterRef('pk'))), email__startswith='a' )
SELECT user.pk, user.email FROM user WHERE NOT EXISTS (SELECT U0.pk FROM reports U0 WHERE U0.user = user.pk) AND email LIKE 'a%';
Docs:
For Django 1.11+ you can add EXISTS
subqueries:
User.objects.annotate( no_reports=~Exists(Reports.objects.filter(user__eq=OuterRef('pk'))) ).filter( email__startswith='a', no_reports=True )
This generates SQL something like this:
SELECT user.pk, user.email, NOT EXISTS (SELECT U0.pk FROM reports U0 WHERE U0.user = user.pk) AS no_reports FROM user WHERE email LIKE 'a%' AND NOT EXISTS (SELECT U0.pk FROM reports U0 WHERE U0.user = user.pk);
A NOT EXISTS
clause is almost always the most efficient way to do a "not exists" filter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With