1010101
1010101

Reputation: 981

Django sql query with None in field of WHERE clause not working

I am having issues when writing sql query for django.

If the query is Books.objects.filter(id=1, name='ABC'), the sql query using django.db.connection would be:

cur = connection.cursor()
cur.execute('SELECT title FROM book WHERE id = %s AND name = %s',[id, name])

This works if there is a value in the name field but returns [] if name is None. So what should the equivalent be for Books.objects.filter(id=1, name=None).

What would be the query to accommodate both None and other values?

cur = connection.cursor()
for id, name in [(1, 'A'),(2, None),(3, 'TEST')]:
    cur.execute('????',[id, name])

I was wondering if there would be a query that could be used in place of '????' in the above code.

Upvotes: 1

Views: 759

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476669

What would be the query to accommodate both None and other values.

Django actually accommodates both None and other values. If we look at the query that is generated, it will look like:

>>> print(Book.objects.filter(id=4, name=None).query)
SELECT `app_book`.`id`, `app_book`.`name` FROM `app_book` WHERE (`app_book`.`id` = 4 AND `app_book`.`name` IS NULL)

If you thus pass None in the filter, then it will translate this to IS NULL.

and for a specific string:

>>> print(Book.objects.filter(id=4, name='Book name').query)
SELECT `app_book`.`id`, `app_book`.`name` FROM `app_book` WHERE (`app_book`.`id` = 4 AND `app_book`.`name` = Book name)

You can make it more explicit by using the __isnull lookup [Django-doc]:

Book.objects.filter(id=4, name__isnull=True)

In SQL, you can not check with NULL by writing =, since:

mysql> select NULL = NULL, NULL is NULL, 'foo' is NULL, 'foo' = NULL;
+-------------+--------------+---------------+--------------+
| NULL = NULL | NULL is NULL | 'foo' is NULL | 'foo' = NULL |
+-------------+--------------+---------------+--------------+
|        NULL |            1 |             0 |         NULL |
+-------------+--------------+---------------+--------------+

Indeed, checks with NULL result in NULL as well.

I was wondering if there would be a query that could be used in place of '????' in the above code

You could make a more sophisticated check, like:

cur.execute(
    'SELECT title FROM book WHERE id = %s AND (name = %s or (name = %s) is NULL)',
    [id, name, name]
)

But this makes it more complex and harder to read, and probably it will make the query slower than formatting it properly.

If you want to select all values that satisfy a 2-tuple, you could generate a query like:

from django.db.models import Q
from functools import reduce
from operator import or_

data = [(1, 'A'),(2, None),(3, 'TEST')]

Book.objects.filter(
    reduce(or_, [Q(id=id, name=name) for id, name in data])
)

Upvotes: 2

Related Questions