NULL值
SQL支持用NULL符号来表示缺少的值,它使用的是三值谓词逻辑,这意味着谓词的计算结果可以是TRUE、FALSE或UNKNOWN(未知)。
T-SQL在这方面遵循了标准。在SQL中处理NULL和UNKNOWN可能非常容易让人困惑,因为人们在直觉上更习惯于用二值逻辑(TRUE、FALSE)来进行思考。更为添乱的是,SQL中不同语言元素处理NULL和UNKNOWN的方式也有所不同。我们先从三值谓词逻辑说起。如果逻辑表达式只涉及已经存在的值,那么最终的计算结果要么为TRUE,要么为FALSE;但是当逻辑表达式涉及缺少的值时,其计算结果就是UNKNOWN。例如,考虑谓词salary > 0;当salary等于1000时,表达式计算结果为TRUE;当salary等于-1000时,表达式计算结果为FALSE;当salary是NULL时,表达式的计算结果就是UNKNOWN。
对于TRUE和FALSE值,SQL的处理方式比较直观,可能符合大多数人的期望。例如,如果某个查询过滤条件(WHERE和HAVING子句)中出现了谓词salary > 0,能够让表达式计算结果为TRUE的那些行或组就被返回,而让表达式计算结果为FALSE的那些行或组则被过滤掉。例如,如果某个查询过滤条件(WHERE和HAVING子句)中出现了谓词salary > 0,能够让表达式计算结果为TRUE的那些行或组就被返回,而让表达式计算结果为FALSE的那些行或组被过滤掉。
在不同的语言元素中,SQL对UNKNOWN的处理也有所不同(对于一些人来说,不一定是他们原本期望的方式)。SQL对查询过滤条件处理的正确定义是:“接受TRUE”就意味着要过滤掉FALSE和UNKNOWN。反之,SQL对CHECK约束处理的正确定义是:“拒绝FALSE”就意味着要接受TRUE和UNKNOWN。如果SQL使用的是二值谓词逻辑,那么“接受TRUE”和“拒绝FALSE”就不会有什么区别。但在三值谓词逻辑中,“接受TRUE”则会拒绝UNKNOWN(接受TRUE,因而要拒绝FALSE和UNKNOWN),而“拒绝FALSE”则会接受UNKNOWN(拒绝FALSE,因而要接受TRUE和UNKNOWN)。前面的例子使用了谓词salary > 0,一个取值为NULL的salary将导致表达式的计算结果为UNKNOWN。如果这个谓词出现在查询的WHERE子句中,则salary列取值为NULL的行也将被过滤掉。如果在表的CHECK约束中也包含这个谓词条件,则salary列取值为NULL的行也将被过滤掉。
UNKNOWN的一个微妙之处是当对它取反(negate)时,结果仍然是UNKNOWN。例如,对于谓词NOT (SALARY>0),当salary是NULL时,salary > 0的计算结果是UNKNOWN,NOT UNKNOWN的结果仍然是UNKNOWN。
让人可能感到吃惊的是,对两个NULL值进行比较的表达式(NULL=NULL),其计算结果也竟为UNKNOWN。因为NULL值代表的是一个缺少的值或不可知的值,所以实际上无法判断一个不可知的值是否等于另一个。为此,SQL提供了两个谓词IS NULL和IS NOT NULL,用它们来取代=NULL和<>NULL。
为了更形象地说明问题,以下通过例子来演示上述的三值谓词逻辑。Sales.Customers表有三个属性country、region,以及city,用于保存客户的位置信息。所有的位置都包含国家(country)和城市(city)字段。其中一些位置信息包含区域(region)(如country:USA,region:WA,city:Seattle),而有一些位置则缺少区域信息或没有可用的区域信息(如country:UK,region:NULL,city:London)。考虑以下查询,它想返回区域(region)等于WA的所有客户:
2 FROM Sales.Customers
3 WHERE region = N'WA';
该查询会生成以下输出:
在Customers表的91行记录中,查询返回3行region列等于WA的记录,没有返回region列有值,但不等于WA的行(谓词计算结果为FALSE),也没有返回region列为NULL的那些行(谓词计算结果为UNKNOWN)。
以下查询想返回region不等于WA的所有客户:
2 FROM Sales.Customers
3 WHERE region <> N'WA';
该查询会生成以下输出:
如果你期望得到88行记录(表中的91行,再减去前面查询返回的3行),可能会惊奇地发现这个查询只返回了28行记录。但是记住,查询过滤条件“接受TRUE”意味着它既会拒绝让表达式计算结果为FALSE的行,也会拒绝让表达式计算结果为UNKNOWN的那些行。所以这个查询返回的行都是region列中有值,而且不等于WA的那些行。但不会返回region列不等于WA的行,也不会返回region列为NULL的行。如果使用谓词NOT (region = N'WA'),得到的结果也完全一样,因为对于region是NULL的行,表达式region=N'WA'的计算结果是UNKNOWN,NOT (region=N'WA')的计算结果还是UNKNOWN。
如果想查找region是NULL的所有行,不应该使用谓词region=NULL,因为这个表达式对于所有行的计算结果都是UNKNOWN,而不论行中是提供了region值,还是缺少region值(即NULL)。以下查询将返回一个空的结果集:
2 FROM Sales.Customers
3 WHERE region =NULL;
相反,在这种情况下应该使用谓词IS NULL:
2 FROM Sales.Customers
3 WHERE region ISNULL;
如果想返回region列不等于WA的所有行(既包括region列有值,但不为WA的那些行;也包括region列缺少值的那些行),则需要在查询过滤条件中显示地增加一个对NULL值的测试,例如:
2 FROM Sales.Customers
3 WHERE region <> N'WA'OR region ISNULL;
在用于比较和排序目的的不同语言元素中,SQL处理NULL的方式也有所不同。一些元素认为两个NULL值彼此相等,而另一些则认为它们不相等。
例如,当进行分组和排序时,认为两个NULL值是相等的。也就是说,GROUP BY子句会在每个组中重新组织所有的NULL值,就像有具体值的列一样;ORDER BY子句也会对所有NULL值进行排序。至于NULL值应该排在有效值之前还是之后,ANSI SQL把它留给了具体的产品实现。T-SQL是把NULL值排在了有效值之前。
考虑前面提到的查询过滤条件“接受TRUE”。对两个NULL值进行比较的表达式结果生成UNKNOWN;因此,这样的行将被过滤掉。
ANSI SQL有两种UNIQUE约束:一种将多个NULL值视为相等的(值允许有一个NULL值),另一种则将多个NULL值视为不同的(允许有多个NULL值)。SQL Server只实现了前者。
记住这些SQL在处理UNKNOWN和NULL值方面不一致的地方,以及发生逻辑错误的潜在可能,在编写每一条查询语句时应该明确地意识到正在使用的是三值谓词逻辑。如果默认的处理并不是你想要的效果,就必须显示地进行干预;否则,只要确保SQL的默认行为是你实际上需要的就可以了。