In SQL, the placement of a condition, whether in the ON clause or the WHERE clause, can affect the results. filter()
in Django seems to put conditions in the WHERE clause.
Say I have these models:
class Nations(models.Model):
deleted = models.BooleanField(default=False)
class People(models.Model):
nation = models.ForeignKey(Nations, on_delete=models.CASCADE)
deleted = models.BooleanField(default=False)
And I want to find the nations with no people in them, I could do:
SELECT nations.id, nations.deleted
FROM nations
LEFT JOIN people
ON people.nation_id = nations.id
WHERE people.id IS NULL;
But let's say people can be soft deleted, and I want to find nations with no undeleted people in them. I would do:
SELECT nations.id, nations.deleted
FROM nations
LEFT JOIN people
ON people.nation_id = nations.id
AND people.deleted IS FALSE
WHERE people.id IS NULL;
However, in Django, if I do:
Nations.objects.filter(people__id__isnull=True, people__deleted=False)
This is equaivalent to the query:
SELECT nations.id, nations.deleted
FROM nations
LEFT JOIN people
ON people.nation_id = nations.id
WHERE people.id IS NULL
AND people.deleted IS FALSE;
Unlike the desired query, this will not include nations that have deleted people only! How can I get Django to move the soft delete check into the ON clause?
Reading you last sentence it seems you want to include nations with no people and nations with only deleted people. To get that, you simply exclude those nations that have undeleted people:
There is a little-known feature since Django 2.0 named
FilteredRelation
which allows adding additional filters to a relationship. The result is an additional criterion in theON
clause of the join:The resulting SQL looks pretty much like what you are after: