为什么 Django 外键 id __in 查询无法匹配 None?
Why does a Django foreign key id __in query fail to match None?
在可空外键上过滤查询集时,我可以按 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!
我不知道为什么 foo_id__in=[123, None]
中的 None
会被忽略,但我想出的最佳解决方法如下:
Bar.objects.filter(Q(foo_id=foo.id) | Q(foo_id=None)).count()
关键在于,在SQL中,NULL
表示一个未知值,无法使用普通运算符进行比较:
SELECT NULL = NULL;
-- => NULL
(此语法不适用于所有数据库引擎 - 例如,SQL 服务器 - 在这些引擎中你必须编写类似 SELECT CASE WHEN NULL = NULL THEN 't' ELSE 'f' END
的内容,但结果是相同的:NULL = NULL
计算为 NULL
,这是错误的。)
推理是,例如,如果您有两个不知道姓氏的人,您会将他们标记为 NULL
- 因为他们都是 NULL
,你不能断定他们有相同的姓氏(就像你不能断定他们有不同的姓氏一样 - 你只是不知道其中一种方式)。
因此,NULL
不等于另一个 NULL
...但它也与另一个 NULL
没有区别:NULL <> NULL
也 returns 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
在吗? Maaaybe;我有一个我不知道它是什么的值,所以我不能说 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()
在可空外键上过滤查询集时,我可以按 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!
我不知道为什么 foo_id__in=[123, None]
中的 None
会被忽略,但我想出的最佳解决方法如下:
Bar.objects.filter(Q(foo_id=foo.id) | Q(foo_id=None)).count()
关键在于,在SQL中,NULL
表示一个未知值,无法使用普通运算符进行比较:
SELECT NULL = NULL;
-- => NULL
(此语法不适用于所有数据库引擎 - 例如,SQL 服务器 - 在这些引擎中你必须编写类似 SELECT CASE WHEN NULL = NULL THEN 't' ELSE 'f' END
的内容,但结果是相同的:NULL = NULL
计算为 NULL
,这是错误的。)
推理是,例如,如果您有两个不知道姓氏的人,您会将他们标记为 NULL
- 因为他们都是 NULL
,你不能断定他们有相同的姓氏(就像你不能断定他们有不同的姓氏一样 - 你只是不知道其中一种方式)。
因此,NULL
不等于另一个 NULL
...但它也与另一个 NULL
没有区别:NULL <> NULL
也 returns 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
在吗? Maaaybe;我有一个我不知道它是什么的值,所以我不能说 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()