We’re encountering a very strange problem regarding the negation of Q objects in Django. Let’s just use Football as the example:
class Team(models.Model):
id = UUIDField(primary_key=True)
class Player(models.Model):
id = UUIDField(primary_key=True)
name = models.CharField(max_length=128)
team = models.ForeignKey(Team)
touchdowns = models.IntegerField()
There are 10 teams.
There are 100 players, with 10 on each team. Each team has a player named “Joe”. There is one “Joe” on one team who has scored 5 touchdowns. All other Joe’s have scored 1 touchdown. There are 8 teams where every Player has scored only 1 touchdown.
I want to get the Teams that have a player named Joe that has scored at least 3 Touchdowns.
models.Team.objects.filter(Q(player__name="Joe", player__touchdowns__gte=3)).count()
That returns One, as it should.The negation of that should return 9 (The other 9 teams that don’t have a player named Joe that has at least 3 Touchdowns):
models.Team.objects.filter(~Q(player__name="Joe", player__touchdowns__gte=3)).count()
instead returns any team where everyone on that team has less than 3 Touchdowns (8).
Where am I going wrong? Please note that our actual application of this is much more complicated, so we NEED to use Q objects with negation, we cannot use Exclude.
The best way to ferret out why these differences occur is to investigate the queries that are generated:
django-debug-toolbarcomes with adebugsqlshellcommand that prints the actual query sent to the database after any use of the Django queryset API. For these tests, I used theUsermodel with a join onGroup. I too noticed different counts for selected objects, so on the face it seems like a good correlation to your use-case.User.objects.filter(~Q(username=’jdoe’, groups__name=’Awesome Group’))
User.objects.exclude(Q(username=’jdoe’, groups__name=’Awesome Group’))
The difference here comes in where the INNER JOIN happens. The
Qobject causes the INNER JOIN in the first example and then the selection with the INNER JOIN is negated because of the~. The case ofexclude, the negation happens in parallel to the INNER JOIN.