在SQL优化中,谨慎操作NULL值至关重要。NULL值作为特殊的标记,表示数据缺失或未知,其在数据库中的处理需特别小心,稍有不慎就可能引发一系列问题。
一、NULL值带来的影响
(一)对查询性能的影响
1. 索引失效:当在索引列上进行NULL值判断时,索引可能会失效,导致数据库引擎放弃使用索引而进行全表扫描。这将极大地降低查询效率,尤其是在大数据量的表中,全表扫描的时间成本非常高。
2. 增加计算复杂性:NULL值的存在会使查询的计算变得更加复杂。在进行聚合计算、排序等操作时,需要额外的处理来排除或考虑NULL值,这会增加数据库的计算量和处理时间。例如,在使用SUM、AVG等聚合函数时,如果列中存在NULL值,这些值将被忽略,但计算过程仍需要考虑其存在,从而影响计算结果的准确性和性能。
(二)对数据准确性的影响
1. 意外结果:如果在查询中没有正确处理NULL值,可能会导致意外的结果。例如,在比较操作中使用NULL值时,除了NULL与NULL相等之外,其他与NULL的比较结果都是未知的,这可能导致查询结果不符合预期[^3^]。
2. 数据一致性问题:在一些复杂的业务逻辑中,如果对NULL值的处理不当,可能会导致数据的不一致性。例如,在多表连接查询中,如果某个连接条件涉及的列为NULL,可能会导致连接结果不正确,进而影响到整个查询的数据准确性。
二、解决方案
1. 合理设计表结构:在创建表时,尽量避免将字段设置为允许NULL值,除非确实有明确的业务需求。对于一些关键字段,如主键、外键等,应确保其不为NULL,以保证数据的完整性和一致性。
2. 使用默认值:为可能为NULL的字段设置合理的默认值,这样可以避免插入NULL值,同时也能保证数据的完整性。
3. 谨慎处理查询中的NULL值:在编写SQL查询语句时,要充分考虑NULL值的影响,避免直接对NULL值进行判断和计算。可以使用IS NULL、IS NOT NULL等运算符来显式地处理NULL值,或者使用COALESCE、NULLIF等函数来替换NULL值为指定的值。
三、谨慎操作NULL值
大多数编程语言都包含布尔数据,该类型数据仅有两个值 TRUE 和 FALSE。这种逻辑体系被称为二值逻辑,即任何事物要么是真(TRUE),要么是假(FALSE)。然而在 SQL中,如下图所示,存在第三个值—未知,也就是 UNKNOWN,因此 SQL 的逻辑体系被 称为三值逻辑。UNKNOWN 在我们的日常生活中有着相当广泛的应用,例 如在填写问卷时,如果用户不愿透露某些信息,相应的录入项就会缺失。在公司组织结构中,也可能出现某些职位(如董事长或总经理)没有上级领导的情况。为了在 SQL 中表示这类情况,我们需要设定一个特殊的标记。这个标记在 SQL 中既不是 一个具体的值,也不是一个变量,它就是 NULL。在数据表中,NULL 通常显示为一个空字段,表示数据项的值未知,不确定是否存在,或者根本就没有 相应的数据。
图片
图 三值逻辑示意
在大多数编程语言中,尝试访问 NULL 值通常会导致错误。然而在 SQL 中,这不会引发错误,但会影响运算结果。例如在下面所示的查询语句中,对 NULL 值进行减法操作会返回 NULL,这可能会影响我们的最终计算结果。
当我们使用比较运算符(如 =、<>、<、> 等)将 NULL 与其他值进行比较时,结果既不是真(TRUE)也不是假(FALSE),而是未知(UNKNOWN)。这是因为 NULL 代表的是未知,它可能代表任何值。正如以下所示的查询语句中,无论是将 NULL 与数值比较,还 是将两个 NULL 值相比较,返回的结果都是 NULL。这是因为 NULL 与任何值都不等同, 即使是两个 NULL 之间也不相等。因此不能断言两个未知的值是相同的,同样也不能断言 它们是不同的。
需要注意的是,在 SQL 中,WHERE、HAVING 以及 CASE WHEN 子句仅返回逻辑运算结果为真的数据记录,而不会返回结果为假或未知的记录。这可能会在使用过程中引起一些混淆。下面以一个例子来说明。假设有一个存储用户 id、用户姓名和用户年龄的临时 用户表 tmp_user,数据抽样如下所示。
当执行以下的查询语句,即过滤 age 不为空、不为 15 的记录。
我们发现结果返回空集,因为使用的是等值比较,所以如果 NOT IN 碰到了 NULL 值,也不会有任何返回。当函数或表达式的参数中包含 NULL 值时,其结果通常也是 NULL。例如,在尝试计算 NULL 值的绝对值(使用 ABS 函数)时将返回 NULL。对 NULL 值进行加、减、乘、除等数值运算,结果也将是 NULL。这种处理 NULL 值的方式需要在进行数据分析和处理时特别注意,以避免出现意外的空结果集。
而在使用聚合函数(如 SUM、COUNT、AVG 等)时,这些函数通常会在计算之前排除 NULL 值。以下面的查询语句为例,假设我们要统计用户临时表中年龄的分布,包括求和、计算平均值、计数等操作。
可以看到,COUNT(*) 总是返回数据的行数,不受空值的影响,而 SUM、COUNT、AVG 都只计算 age 列不为空的数据。
而 在 SQL 的 分 组 聚 合 操 作 中, 总 是 将 所 有 的 NULL 值 分 到 同 一 个 组, 包 括DISTINCT、GROUP BY 以及窗口函数中的 PARTITION BY。当 NULL 较多时,会导致潜在的数据倾斜风险,从而拖慢任务执行速度。在连接操作时,连接键中存在 NULL,判定NULL = NULL 不成立,NULL <> NULL 也不成立,因此可能会导致返回的结果集与预期不符。
而在排序操作中,SQL 标准没有定义 NULL 值的排序顺序,但是为 ORDER BY 定义了Nulls First 和 Nulls Last 选项,用于明确指定空值排在其他数据之前或者之后。例如 Spark默认将 NULL 作为最小值,升序时排在最前,而 Oracle 和 PostgreSQL 则默认将 NULL 作为最大值,升序时排在最后。
NULL 值作为一种特殊的存在,无论它出现在哪种运算中,都可能导致意料之外的结果。因此在数据处理之前,我们通常需要对 NULL 值进行筛选和处理,以避免出现潜在的问题。