Wes*_*ley 1 django django-models django-3.2
在可以为空的外键上过滤查询集时,我可以按 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)
Run Code Online (Sandbox Code Playgroud)
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!
Run Code Online (Sandbox Code Playgroud)
关键的见解是,在 SQL 中,NULL代表未知值,无法使用普通运算符进行比较:
SELECT NULL = NULL;
-- => NULL
Run Code Online (Sandbox Code Playgroud)
(此语法并不适用于所有数据库引擎 - 例如 SQL Server - 在这些引擎中,您必须编写类似 的内容SELECT CASE WHEN NULL = NULL THEN 't' ELSE 'f' END,但结果是相同的:NULL = NULL计算结果为NULL,这是错误的。)
原因是,例如,如果您有两个人的姓氏您不知道,您会将他们标记为NULL- 并且仅仅因为他们都是NULL,您就不能断定他们具有相同的姓氏(就像你不能断定他们有不同的——你只是不知道其中一种方式)。
因此,aNULL不等于另一个NULL...但它也与另一个没有不同NULL:NULL <> NULL也返回NULL。事实上,NULL感染所有运算符:1 + NULL、1 < NULL、1 >= NULL... 全部结果为NULL。如果您对未知值执行任何操作,则结果也是未知值。
基本上只有一个运算符可以避免 的这种传染性NULL,那就是IS NULL:
SELECT NULL IS NULL;
-- => t
Run Code Online (Sandbox Code Playgroud)
与 一样x = NULL,x IN (NULL)也使用相同的相等比较,它永远不能评估为 true:
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
Run Code Online (Sandbox Code Playgroud)
有2没有?Maaaaybe;我有一个值,我不知道它是什么,所以我不能说它是否2在那里,因为它可能与那个未知的值匹配。那我不知道的另一件事怎么样?打败我的是,可能是1,或者可能等于其他未知的东西——或者它可能是完全不同的东西。怎么样1?嗯,至于这一点,我可以看到1那里,无论未知的东西是什么或不是什么。
IN因此,您必须明确检查,而不是 plain NULL:
SELECT * WHERE foo_id IN (1, 2) OR foo_id IS NULL;
Run Code Online (Sandbox Code Playgroud)
用 Django 术语来说:
from django.db.models import Q
Bar.objects.filter(Q(foo_id=foo.id) | Q(foo_id__isnull=True)).count()
Run Code Online (Sandbox Code Playgroud)
或者如果你有多个值
Bar.objects.filter(Q(foo_id__in=[1, 2]) | Q(foo_id__isnull=True)).count()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
545 次 |
| 最近记录: |