Using .exclude(boolean_field=True) in Django and Postgres
A common Postgres “gotcha” I’ve run into before is the handling of NULL
values. Equality comparisons in Postgres to NULL
are neither true nor false.
Consider this data set:
sample_table
id | name | boolean_field |
---|---|---|
1 |
Sample A |
True |
2 |
Sample B |
False |
3 |
Sample C |
NULL |
In Postgres, if we perform SELECT * FROM sample_table WHERE boolean_field = TRUE;
, we’ll get our expected one row.
id | name | boolean_field |
---|---|---|
1 |
Sample A |
True |
That translates in Django to SampleTable.objects.filter(boolean_field=True)
.
Great, we get what we expect!
But now consider SampleTable.objects.all().exclude(boolean_field=True)
. We’d expect to get the 2nd and 3rd rows right?
Wrong! Django translates that query as SELECT * FROM sample_table WHERE NOT(boolean_field = True);
. Since comparisons to NULL
values are neither true nor false, this statement only returns the second row.
id | name | boolean_field |
---|---|---|
2 |
Sample B |
False |
I wish Django would translate this using the special IS NOT
operator.
SELECT * FROM sample_table WHERE boolean_field IS NOT True;
That returns the rows we expect.