SQL 优化的“最后一公里”,谨慎操作 NULL 值

数据库 其他数据库
NULL 值作为一种特殊的存在,无论它出现在哪种运算中,都可能导致意料之外的结果。因此在数据处理之前,我们通常需要对 NULL 值进行筛选和处理,以避免出现潜在的问题。

在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,这可能会影响我们的最终计算结果。

SELECT 1 - NULL;
-- 返回 NULL
  • 1.
  • 2.

当我们使用比较运算符(如 =、<>、<、> 等)将 NULL 与其他值进行比较时,结果既不是真(TRUE)也不是假(FALSE),而是未知(UNKNOWN)。这是因为 NULL 代表的是未知,它可能代表任何值。正如以下所示的查询语句中,无论是将 NULL 与数值比较,还 是将两个 NULL 值相比较,返回的结果都是 NULL。这是因为 NULL 与任何值都不等同, 即使是两个 NULL 之间也不相等。因此不能断言两个未知的值是相同的,同样也不能断言 它们是不同的。

SELECT NULL = 0;
SELECT NULL <> 0; 
SELECT NULL <= 0; 
SELECT NULL = NULL; 
SELECT NULL != NULL;
-- 都返回 NULL
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

需要注意的是,在 SQL 中,WHERE、HAVING 以及 CASE WHEN 子句仅返回逻辑运算结果为真的数据记录,而不会返回结果为假或未知的记录。这可能会在使用过程中引起一些混淆。下面以一个例子来说明。假设有一个存储用户 id、用户姓名和用户年龄的临时 用户表 tmp_user,数据抽样如下所示。

SELECT *
FROM tmp_user; 
-- user_id name age 
123 bob 15
345 ac 17 
348 NULL NULL
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

当执行以下的查询语句,即过滤 age 不为空、不为 15 的记录。

SELECT `name`
FROM tmp_user 
WHERE age NOT IN (NULL, 15);
  • 1.
  • 2.
  • 3.

我们发现结果返回空集,因为使用的是等值比较,所以如果 NOT IN 碰到了 NULL 值,也不会有任何返回。当函数或表达式的参数中包含 NULL 值时,其结果通常也是 NULL。例如,在尝试计算 NULL 值的绝对值(使用 ABS 函数)时将返回 NULL。对 NULL 值进行加、减、乘、除等数值运算,结果也将是 NULL。这种处理 NULL 值的方式需要在进行数据分析和处理时特别注意,以避免出现意外的空结果集。

-- 都返回 NULL
SELECT ABS(NULL);
SELECT 1 + NULL;
  • 1.
  • 2.
  • 3.

而在使用聚合函数(如 SUM、COUNT、AVG 等)时,这些函数通常会在计算之前排除 NULL 值。以下面的查询语句为例,假设我们要统计用户临时表中年龄的分布,包括求和、计算平均值、计数等操作。

SELECT SUM(age)
,AVG(age)
,COUNT(age)
,COUNT(*)
FROM tmp_user; 
-- 返回结果
32 16.0 2 3
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

可以看到,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 作为最大值,升序时排在最后。

-- Spark SQL 
SELECT age 
FROM tmp_user 
ORDER BY age ASC; 
-- age 列,可以看到 NULL 升序排最前
NULL 
15 
17
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

NULL 值作为一种特殊的存在,无论它出现在哪种运算中,都可能导致意料之外的结果。因此在数据处理之前,我们通常需要对 NULL 值进行筛选和处理,以避免出现潜在的问题。

责任编辑:武晓燕 来源: 数仓宝贝库
相关推荐

2015-04-23 10:30:42

华为

2022-07-29 09:03:17

AIOPS运维工具

2022-07-26 07:35:30

数据库HTAP系统

2015-12-11 10:46:01

2017-02-21 12:30:21

数据中心智能终端网络

2017-02-21 13:30:42

数据网络终端

2012-09-24 15:07:09

云ERP恩信科技云应用

2011-12-25 20:54:57

移动支付

2022-04-19 08:09:11

PON光纤网络

2023-12-12 08:31:44

智能运维场景

2015-11-03 13:55:44

物联网最后一公里

2017-09-04 16:49:25

2014-09-28 10:00:38

2009-09-07 11:47:22

无线交换机

2017-11-27 08:54:43

数据信息化智能

2019-12-16 09:33:08

浪潮

2018-01-12 05:13:35

2012-04-24 10:29:10

VDSL2光接入
点赞
收藏

51CTO技术栈公众号