《数据库基础语法》9. 小心 SQL 中的空值陷阱

从本节开始我们将会学习进阶内容。进阶内容将会介绍更加复杂的多表查询、子查询、集合运算以及各种高级的数据分析技术,能够让我们真正体会到 SQL 数据处理和分析的强大。

作为进阶的开始,我们先来讨论一下 SQL 中的空值问题,因为空值的特殊性导致我们很容易出现一些错误和问题。

空值与三值逻辑:

SQL 中的空值(NULL)是一个特殊的值,代表了 缺失 / 未知 的数据或者不适用的情况。例如,用户在注册时没有提供电子邮箱地址,那么该用户的邮箱就未知;是否怀孕对于男性员工就不适用。

对于大多数的编程语言而言,逻辑运算的结果只有两种情况:真(Ture)或者假(False)。但是对于 SQL 而言,逻辑运算结果存在三种情况:真、假 或者 未知(Unknown)

对于 SQL 查询中的 WHERE 条件,只有结果为真的数据才会返回,结果为假或者未知都不会返回。

而布尔值和 NULL 进行逻辑运算的话,那么结果为 NULL,除非通过短路求值提前判断结果,举个例子

-- AND 要求两边都为真,所以左边的 true 决定不了结果,因此会检测 NULL,而两者逻辑运算结果为 NULL
true AND NULL; -- NULL
-- AND 左边为假,直接判断结果,所以右边的 NULL 不会检测了,所以结果为false
false AND NULL; -- false

-- or要求有一边为真即可,左边为 true,所以右边的 NULL 不检测了,结果为true
true OR NULL; -- true
-- 左边为 false,所以看右边,而右边为 NULL,所以结果是 NULL
false OR NULL; -- false

-- 如果 NULL 在左边,不用想了,结果必为 NULL
-- 同理对 NULL 取反,结果依旧是 NULL

空值比较与运算:

任何值与 NULL 值进行比较时,结果无法确认是真还是假。以下比较运算的结果都是未知:

NULL = 0
NULL != 0
NULL > 1
NULL = NULL
NULL != NULL

NULL 等于 NULL 的运算结果未知,NULL 不等于 NULL 的运算结果也未知。因此,为了判断某个值是否为空,SQL 提供了 IS NULL 和 IS NOT NULL 谓词。

另外,当表达式或者函数涉及 NULL 值时,通常结果也是 NULL 值。例如:

100 + NULL;
UPPER(NULL);

不过也存在一些例外,比如:

SELECT CONCAT('hello', NULL); -- hello

该查询只有在 MySQL 返回 NULL,CONCAT 函数在 Oracle、SQL Server 以及 PostgreSQL 中将 NULL 当作空字符串处理,因此返回字符串 "hello"。由于不同数据库采取了不同的实现,因此在使用时这些函数时需要小心。

空值的排序:

我们之前介绍了 ORDER BY 排序,并且讨论了空值的排序规则。由于 SQL 标准没有提出明确要求,导致在不同的数据库中存在两种空值排序方式:

  • Oracle 和 PostgreSQL,认为空值最大,升序时空值排在最后,降序时空值排在最前;同时支持使用 NULLS FIRST 和 NULLS LAST 指定空值的顺序
  • MySQL 和 SQL Server,认为空值最小,升序时空值排在最前,降序时空值排在最后

我们可以利用 COALESCE 函数或者 CASE 表达式解决空值的排序问题,上面也给出了 CASE 表达式相关的示例。下面我们介绍一下 SQL 中关于空值处理的函数。

空值的函数:

SQL 中定义了两个与空值转换相关的函数:NULLIF 和 COALESCE。

NULLIF(expr1, expr2) 函数接受 2 个参数,如果第一个参数等于第二个参数,返回 NULL;否则,返回第一个参数的值。

SELECT NULLIF(1, 2), NULLIF(3, 3); -- 1	<null>

因为 1 不等于 2,该查询的第一列结果为 1;因为 3 等于 3,第二列结果为 NULL。

NULLIF 函数的一个常见用途是防止除零错误:

SELECT 1 / 0; -- 除零错误

SELECT 1 / NULLIF(0, 0); -- <null>

第一个查询中被除数为 0,出现除零错误(MySQL 可能不会提示错误);第二个查询使用 NULLIF 函数将被除数 0 转换为 NULL,整个结果为 NULL。

COALESCE:

COALESCE(expr1, expr2, expr3, ...) 函数接受一个参数列表,并且返回第一个非空的参数;如果所有参数都为空,返回 NULL。

SELECT COALESCE(type, 'UNKNOWN') FROM staff WHERE type IS NULL;
/*
UNKNOWN
UNKNOWN
UNKNOWN
UNKNOWN
UNKNOWN
 */

专有函数:

除了SQL 标准中定义的表达式之外,许多数据库还实现了一些类似的扩展函数:

  • Oracle 提供了 NVL(expr1, expr2) 以及 NVL2(expr1, expr2, expr3) 函数
  • MySQL 提供了 IFNULL(expr1, expr2) 以及 IF(expr1, expr2, expr3) 函数
  • SQL Server 提供了 ISNULL(expr1, expr2) 函数

NVL、IFNULL、ISNULL 它们都相当于只有两个参数的COALESCE,但是 NVL2 和 IF 比较特殊、类似于编程语言中的三元运算符,对于NVL2来说,如果expr1不为空,那么返回expr2的值,否则返回expr3的值;而对于IF也是一样,如果 expr1 不为空、并且为真,那么返回 expr2,否则返回 expr3。

分组与聚合函数中的空值:

GROUP BY 子句、DISTINCT 运算符将所有的空值视为相同,因此将它们分为一组。

我们介绍了常见的聚合函数(AVG、SUM、COUNT 等),聚合函数通常会忽略空值。

NOT IN 中的空值:

IN 操作符用于查询位于列表之中的数据,NOT IN 用于查询不位于列表中的数据。

SELECT * FROM staff WHERE type in ('管理人员', NULL);

该查询是不会返回 type 为 NULL 的记录,原因在于 IN 操作符使用等号(=)依次与列表中的数据进行比较,该查询等价于以下形式:

SELECT * FROM staff WHERE type = '管理人员' OR type = NULL;

任何值与 NULL 比较的结果都是未知;因此没有数据满足该条件,也就没有返回结果。为了避免各种情况下空值可能带来的问题,可以利用 SQL 提供的空值处理函数将 NULL 值转换为其他数据。

空值(NULL)是 SQL 中容易被忽略的地方,经常会导致一些不可预知的错误结果。需要注意 SQL 在各个子句和函数中对于空值的处理方式。一个比较通用的解决方法就是利用 COALESCE 函数或者 CASE 表达式将这些空值转换成确定的数据,而且 COALESCE 函数 和 CASE 表达式可以用在各种地方,比如:SELECT、WHERE、GROUP BY、ORDER BY 等等。

原文地址:https://www.cnblogs.com/traditional/p/11719091.html