乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

NULL 值处理

导学

NULL 是数据库中最特殊的存在——它表示"未知"或"不存在",而不是空字符串或 0。对 NULL 的误解是 SQL 新手写出 Bug 的首要原因。本节彻底讲透 NULL 的行为和正确处理方式。

定义

NULL:在 SQL 中表示"未知值"或"缺失值"的特殊标记。它与空字符串 ''、数字 0、布尔 FALSE 完全不同。

演示数据准备

DROP TABLE IF EXISTS employees;

CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);

当前 employees 表中的完整数据如下:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

NULL 的核心特性

特性一:与任何值的比较结果都是 UNKNOWN

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT NULL = NULL;   -- 结果:NULL(不是 TRUE)
SELECT NULL = 0;      -- 结果:NULL
SELECT NULL = '';     -- 结果:NULL
SELECT NULL <> NULL;  -- 结果:NULL

操作后结果:

NULL = NULLNULL = 0NULL = ''NULL <> NULL
NULLNULLNULLNULL

结果解读:NULL 与任何值(包括 NULL 自身)的比较结果都是 UNKNOWN。这是 SQL 三值逻辑(TRUE、FALSE、UNKNOWN)的核心。

我们用表数据验证:

-- 错误写法:永远返回空结果
SELECT * FROM employees WHERE score = NULL;

操作后结果:空结果集(0 行)。

结果解读:score = NULL 对每一行都返回 UNKNOWN,而 WHERE 只保留 TRUE 的行,因此没有任何行被返回。

正确写法:

SELECT * FROM employees WHERE score IS NULL;

操作后结果:

emp_idemp_namedeptscore
2白歌技术部NULL

结果解读:IS NULL 是专门判断 NULL 的运算符,正确返回了 score 为 NULL 的白歌。

特性二:算术运算结果为 NULL

当前数据状态:见上文 employees 表完整数据。

执行语句:

SELECT
    emp_name,
    score,
    score + 10 AS score_plus_10,
    score * 2 AS score_double,
    score / 2 AS score_half
FROM employees;

操作后结果:

emp_namescorescore_plus_10score_doublescore_half
大翔100.00110.00200.0050.0000
白歌NULLNULLNULLNULL

结果解读:白歌的 score 为 NULL,因此任何包含 score 的算术运算结果都是 NULL。这不是报错,而是 SQL 的标准行为。

特性三:聚合函数忽略 NULL

当前数据状态:见上文 employees 表完整数据(2 行,其中 1 行 score 为 NULL)。

执行语句:

SELECT COUNT(*) FROM employees;           -- 统计所有行
SELECT COUNT(score) FROM employees;       -- 统计 score 非 NULL 的行
SELECT SUM(score) FROM employees;         -- 忽略 NULL 后求和
SELECT AVG(score) FROM employees;         -- 忽略 NULL 后求平均
SELECT MAX(score) FROM employees;         -- 忽略 NULL 后取最大值
SELECT MIN(score) FROM employees;         -- 忽略 NULL 后取最小值

操作后结果:

COUNT(*)COUNT(score)SUM(score)AVG(score)MAX(score)MIN(score)
21100100.0000100100

结果解读:

  • COUNT(*) = 2:统计所有行,不忽略 NULL
  • COUNT(score) = 1:只统计 score 非 NULL 的行(大翔 100)
  • SUM(score) = 100:忽略 NULL
  • AVG(score) = 100.0000:100 / 1 = 100,忽略 NULL 后除以非 NULL 行数
  • MAX(score) = 100:忽略 NULL 后的最大值
  • MIN(score) = 100:忽略 NULL 后的最小值(唯一非 NULL 值)

易错点:COUNT(列) 和 COUNT(*) 的区别是面试高频考点。COUNT(*) 统计行数,COUNT(列) 统计该列非 NULL 的行数。

SQL 示例

场景一:正确判断 NULL

当前数据状态:见上文 employees 表完整数据。

执行语句(错误):

-- 错误:永远返回空结果
SELECT * FROM employees WHERE score = NULL;

操作后结果:空结果集(0 行)。

执行语句(正确):

SELECT * FROM employees WHERE score IS NULL;

操作后结果:

emp_idemp_namedeptscore
2白歌技术部NULL

执行语句(正确):

SELECT * FROM employees WHERE score IS NOT NULL;

操作后结果:

emp_idemp_namedeptscore
1大翔技术部100.00

结果解读:IS NULL 和 IS NOT NULL 是判断 NULL 的唯一正确方式。= 和 <> 对 NULL 无效。

场景二:将 NULL 替换为默认值

当前数据状态:见上文 employees 表完整数据。

执行语句(COALESCE):

-- COALESCE:返回第一个非 NULL 的值
SELECT emp_name, COALESCE(score, 0) AS score_display FROM employees;

操作后结果:

emp_namescore_display
大翔100.00
白歌0.00

执行语句(IFNULL):

-- IFNULL:MySQL 特有,两个参数版 COALESCE
SELECT emp_name, IFNULL(score, 0) AS score_display FROM employees;

操作后结果:与 COALESCE 相同。

结果解读:COALESCE(score, 0) 逐个检查参数,返回第一个非 NULL 的值。如果 score 为 NULL,则返回 0。IFNULL 是 MySQL 特有的两参数版本,功能相同。

再看一个结合多列的替换例子:

SELECT
    emp_name,
    COALESCE(dept, '待定') AS dept_display,
    COALESCE(score, 0) AS score_safe
FROM employees;

操作后结果:

emp_namedept_displayscore_safe
大翔技术部100.00
白歌技术部0.00

结果解读:COALESCE 可以处理多列的 NULL 替换,是处理 NULL 最灵活的方式之一。本例中 dept 列无 NULL,因此全部显示原部门。

场景三:聚合中的 NULL 陷阱

当前数据状态:见上文 employees 表完整数据(2 行,其中 1 行 score 为 NULL:白歌)。

执行语句:

-- employees 表有 2 行,其中 1 行 score 为 NULL
SELECT COUNT(*) FROM employees;      -- 结果:2
SELECT COUNT(score) FROM employees;  -- 结果:1(忽略 NULL)
SELECT AVG(score) FROM employees;    -- 结果:100(100/1,忽略 NULL)
SELECT SUM(score) FROM employees;    -- 结果:100(忽略 NULL)
SELECT AVG(IFNULL(score, 0)) FROM employees;  -- 结果:50.0000((100+0)/2,NULL 当 0)

操作后结果:

COUNT(*)COUNT(score)AVG(score)SUM(score)AVG(IFNULL(score, 0))
21100.000010050.0000

结果解读:

  • AVG(score) = 100.0000:忽略 NULL 后除以 1(非 NULL 行数)
  • AVG(IFNULL(score, 0)) = 50.0000:(100+0)/2 = 50.0000

关键区别:AVG(score) 忽略 NULL 后除以非 NULL 行数;若希望 NULL 视为 0,需用 AVG(IFNULL(score, 0))。两者的语义完全不同,使用时要根据业务需求选择。

场景四:NOT IN 中的 NULL 陷阱(复习)

当前数据状态:见上文 employees 表完整数据。

执行语句(危险):

-- 危险:永远返回空结果
SELECT emp_name FROM employees WHERE dept NOT IN ('技术部', NULL);

操作后结果:空结果集(0 行)。

结果解读:dept NOT IN ('技术部', NULL) 等价于 dept <> '技术部' AND dept <> NULL。dept <> NULL 的结果是 UNKNOWN,而 WHERE 只保留 TRUE,因此整个查询返回空结果。

正确写法:

SELECT emp_name FROM employees WHERE dept NOT IN ('技术部');

操作后结果:

emp_name

结果解读:当前表中所有员工都在技术部,因此返回空结果。确保 NOT IN 列表中不包含 NULL,才能得到正确结果。

场景五:NULL 的排序行为

当前数据状态:见上文 employees 表完整数据,白歌的 score 为 NULL。

执行语句(升序):

SELECT emp_name, score FROM employees ORDER BY score ASC;

操作后结果:

emp_namescore
白歌NULL
大翔100.00

结果解读:MySQL 5.7 中,NULL 在升序时排在最前面。

执行语句(降序):

SELECT emp_name, score FROM employees ORDER BY score DESC;

操作后结果:

emp_namescore
大翔100.00
白歌NULL

结果解读:NULL 在降序时排在最后面。

如需自定义 NULL 的位置:

-- NULL 排在最后(升序)
SELECT emp_name, score FROM employees ORDER BY score IS NULL, score ASC;

操作后结果:

emp_namescore
大翔100.00
白歌NULL

结果解读:score IS NULL 返回 1(NULL)或 0(非 NULL),先按此排序,非 NULL(0)在前,NULL(1)在后,再按实际 score 值排序。

常见误区

误区正解
"NULL 等于空字符串"NULL 是未知,'' 是确定的空字符串,两者完全不同。
"WHERE column = NULL 可以查 NULL"必须用 IS NULL。
"COUNT(*) 和 COUNT(列) 一样"COUNT(*) 统计所有行;COUNT(列) 忽略 NULL。
"聚合函数把 NULL 当 0"聚合函数忽略 NULL,不是当 0。如果所有值都是 NULL,AVG 返回 NULL 而非 0。
"IFNULL 和 COALESCE 一样"IFNULL 接受两个参数,是 MySQL 特有;COALESCE 接受多个参数,是 SQL 标准函数。

面试考点

Q:IS NULL 和 = NULL 为什么结果不同?

SQL 采用三值逻辑(TRUE/FALSE/UNKNOWN)。任何值与 NULL 的比较结果都是 UNKNOWN,而 WHERE 只保留 TRUE。IS NULL 是专门判断 NULL 的运算符。

Q:查询某列不为某值的所有行(包括 NULL),怎么写?

WHERE column <> '某值' OR column IS NULL。因为 column <> '某值' 对 NULL 返回 UNKNOWN,不会包含 NULL 行。

Q:COUNT(*)、COUNT(1)、COUNT(列) 的区别?

COUNT(*) 和 COUNT(1) 在 MySQL 5.7 中性能相同,都统计所有行数;COUNT(列) 统计该列非 NULL 的行数。

Q:如何计算"包含 NULL 的平均值"(NULL 视为 0)?

AVG(IFNULL(score, 0)) 或 AVG(COALESCE(score, 0))。注意这与默认 AVG(score) 的语义完全不同。

Q:NULL 和 '' 在排序时谁在前?

''(空字符串)是确定的值,按字符串规则排序;NULL 在升序时默认排在最前面。两者位置不同。

小结

  • NULL 表示"未知",不等于 0、空字符串或 FALSE
  • 判断 NULL 只能用 IS NULL / IS NOT NULL
  • 聚合函数忽略 NULL,COUNT(列) 与 COUNT(*) 结果可能不同
  • 用 COALESCE 或 IFNULL 将 NULL 转为有意义的默认值
  • NOT IN 列表中不可包含 NULL,否则结果永远为空
  • NULL 参与的算术运算结果恒为 NULL

下一章引子:除了精确匹配,业务中经常需要模糊查找——比如按姓名前缀搜索员工。

上一页
模式匹配
下一页
UPDATE