在可空外键上过滤查询集时,我可以通过ID值(foo_id=123
)或None (foo_id=None
)进行过滤。但是,如果我尝试通过列表(foo_id__in=[123, None]
)进行过滤,则忽略None
。
为什么会发生这种情况,使用包含None的列表对外键进行过滤的最佳解决方案是什么?
的例子:
from django.db import models
class Foo(models.Model):
name = models.CharField(max_length=100)
class Bar(models.Model):
foo = models.ForeignKey(Foo, on_delete=models.PROTECT,
blank=True, null=True)
foo = Foo.objects.create(name='myfoo')
Bar.objects.create(foo=foo)
Bar.objects.create(foo=None)
Bar.objects.count() # 2
Bar.objects.filter(foo_id=foo.id).count() # 1
Bar.objects.filter(foo_id=None).count() # 1
Bar.objects.filter(foo_id__in=[foo.id, None]).count() # 1 - Expected 2!
关键在于,在SQL中,NULL
表示一个未知值,无法使用普通操作符进行比较:
SELECT NULL = NULL;
-- => NULL
(此语法并不适用于所有DB引擎-例如,SQL Server -在这些引擎中,您将不得不编写类似SELECT CASE WHEN NULL = NULL THEN 't' ELSE 'f' END
的东西,但结果是相同的:NULL = NULL
计算为NULL
,这是假的。)
原因是,例如,如果你有两个人的姓你不知道,你会把他们标记为NULL
-仅仅因为他们都是NULL
,你不能断定他们有相同的姓(就像你不能断定他们有不同的姓-你只是不知道一种方式或另一种方式)。
所以,一个NULL
不等于另一个NULL
…但它也与另一个NULL
没有什么不同:NULL <> NULL
也返回NULL
。事实上,NULL
感染了所有的操作员:1 + NULL
,1 < NULL
,1 >= NULL
…所有结果都是NULL
。如果你用一个未知的值做任何事情,结果就是一个未知的值。
基本上只有一个操作符可以避免NULL
的这种传染性,那就是IS NULL
:
SELECT NULL IS NULL;
-- => t
与x = NULL
相同,x IN (NULL)
也使用相同的相等比较,它永远不会被计算为真:
SELECT 2 IN (1, NULL);
-- => NULL
SELECT NULL IN (1, NULL);
-- => NULL
SELECT 1 IN (1, NULL);
-- => t
SELECT 2 NOT IN (1, NULL);
-- => NULL
SELECT NULL NOT IN (1, NULL);
-- => NULL
SELECT 1 NOT IN (1, NULL);
-- => t
2
在吗?Maaaaybe;我有一个值,我不知道它是什么,所以我不能说2
是否在那里,因为它可能匹配那个未知的。那另一件我不知道的事呢?难倒我了,可能是1
,或者可能等于另一个未知的东西——或者可能是完全不同的东西。那1
呢?好吧,至于那,我可以看到1
在那里,不管未知的东西是什么或不是。
所以你必须明确地检查NULL
,而不是普通的IN
:
SELECT * WHERE foo_id IN (1, 2) OR foo_id IS NULL;
在django中:
from django.db.models import Q
Bar.objects.filter(Q(foo_id=foo.id) | Q(foo_id__isnull=True)).count()
或者如果您有多个值
Bar.objects.filter(Q(foo_id__in=[1, 2]) | Q(foo_id__isnull=True)).count()
我不知道为什么foo_id__in=[123, None]
中的None
被忽略了,但我想出的最好的解决方案是:
Bar.objects.filter(Q(foo_id=foo.id) | Q(foo_id=None)).count()