别让 NULL 拖垮结果:MySQL COALESCE 空值兜底实战详解

简介

COALESCE 是 MySQL 里非常常用的空值处理函数。

它的作用很简单:

text 复制代码
从左到右返回第一个不是 NULL 的值。

最基础示例:

sql 复制代码
SELECT COALESCE(NULL, NULL, 'hello', 'world');

结果:

text 复制代码
+-----------------------------------------+
| COALESCE(NULL, NULL, 'hello', 'world') |
+-----------------------------------------+
| hello                                   |
+-----------------------------------------+

因为 'hello' 是第一个非 NULL 值,后面的 'world' 不会作为结果返回。

如果所有参数都是 NULL

sql 复制代码
SELECT COALESCE(NULL, NULL, NULL);

结果还是:

text 复制代码
NULL

一句话概括:

text 复制代码
COALESCE 负责给 NULL 找一个兜底值。

它常用于这些场景:

  • 字段为空时显示默认文字
  • 多个联系方式按优先级取一个
  • 聚合统计没有结果时返回 0
  • LEFT JOIN 没匹配到数据时补默认值
  • 拼接字符串时避免结果变成 NULL
  • 排序时给空值一个排序规则
  • 批量更新时保留原值或设置默认值

基本语法

sql 复制代码
COALESCE(value1, value2, value3, ...)

执行规则:

text 复制代码
从左到右依次判断,返回第一个非 NULL 的值。

示例:

sql 复制代码
SELECT COALESCE(NULL, 100, 200);

结果:

text 复制代码
100

再看几个例子:

sql 复制代码
SELECT COALESCE('张三', '匿名用户');

结果:

text 复制代码
张三
sql 复制代码
SELECT COALESCE(NULL, '匿名用户');

结果:

text 复制代码
匿名用户
sql 复制代码
SELECT COALESCE(NULL, NULL);

结果:

text 复制代码
NULL

为什么需要 COALESCE?

SQL 里的 NULL 不是空字符串,也不是 0,而是"不知道、没有值"。

很多运算碰到 NULL 后,结果也会变成 NULL

例如:

sql 复制代码
SELECT 100 + NULL;

结果:

text 复制代码
NULL

字符串拼接也一样:

sql 复制代码
SELECT CONCAT('手机号:', NULL);

结果:

text 复制代码
NULL

这就是 NULL 最容易出问题的地方:只要参与计算或拼接,整个结果可能都没了。

COALESCE 兜底后:

sql 复制代码
SELECT 100 + COALESCE(NULL, 0);

结果:

text 复制代码
100
sql 复制代码
SELECT CONCAT('手机号:', COALESCE(NULL, '暂无'));

结果:

text 复制代码
手机号:暂无

准备一组演示数据

后面的例子直接使用这几张表。

sql 复制代码
DROP TABLE IF EXISTS payments;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS departments;

CREATE TABLE departments (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL
);

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50),
  nickname VARCHAR(50),
  real_name VARCHAR(50),
  mobile VARCHAR(20),
  backup_mobile VARCHAR(20),
  email VARCHAR(100),
  avatar VARCHAR(255),
  department_id INT,
  last_login_at DATETIME,
  created_at DATETIME NOT NULL,
  INDEX idx_mobile (mobile),
  INDEX idx_department_id (department_id)
);

CREATE TABLE orders (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10, 2),
  discount DECIMAL(10, 2),
  sort_no INT,
  updated_at DATETIME,
  created_at DATETIME NOT NULL,
  INDEX idx_user_id (user_id),
  INDEX idx_sort_no (sort_no)
);

CREATE TABLE payments (
  id INT PRIMARY KEY AUTO_INCREMENT,
  order_id INT NOT NULL,
  pay_amount DECIMAL(10, 2),
  refund_amount DECIMAL(10, 2),
  INDEX idx_order_id (order_id)
);

INSERT INTO departments (name) VALUES
('研发部'),
('销售部');

INSERT INTO users
(username, nickname, real_name, mobile, backup_mobile, email, avatar, department_id, last_login_at, created_at)
VALUES
('zhangsan', '老张', '张三', '13800000001', NULL, 'zhangsan@example.com', NULL, 1, '2026-01-05 10:00:00', '2026-01-01 10:00:00'),
('lisi', NULL, '李四', NULL, '13900000002', 'lisi@example.com', '/avatar/lisi.png', 2, NULL, '2026-01-02 10:00:00'),
('wangwu', '', '王五', NULL, NULL, 'wangwu@example.com', NULL, NULL, NULL, '2026-01-03 10:00:00'),
(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2026-01-04 10:00:00');

INSERT INTO orders
(user_id, order_no, amount, discount, sort_no, updated_at, created_at)
VALUES
(1, 'A001', 100.00, 10.00, 2, '2026-01-06 10:00:00', '2026-01-05 10:00:00'),
(1, 'A002', 200.00, NULL, NULL, NULL, '2026-01-06 11:00:00'),
(2, 'A003', NULL, NULL, 1, '2026-01-07 10:00:00', '2026-01-07 09:00:00'),
(3, 'A004', 80.00, 5.00, NULL, NULL, '2026-01-08 10:00:00');

INSERT INTO payments (order_id, pay_amount, refund_amount) VALUES
(1, 90.00, NULL),
(2, 200.00, 20.00),
(3, NULL, NULL);

示例一:字段为空时显示默认值

用户头像为空时,显示默认头像。

sql 复制代码
SELECT
  id,
  username,
  COALESCE(avatar, '/avatar/default.png') AS show_avatar
FROM users;

结果类似:

text 复制代码
+----+----------+---------------------+
| id | username | show_avatar         |
+----+----------+---------------------+
|  1 | zhangsan | /avatar/default.png |
|  2 | lisi     | /avatar/lisi.png    |
|  3 | wangwu   | /avatar/default.png |
|  4 | NULL     | /avatar/default.png |
+----+----------+---------------------+

这类场景非常适合 COALESCE(字段, 默认值)

示例二:多个字段按优先级取值

用户展示名按这个顺序取:

text 复制代码
昵称 -> 真实姓名 -> 用户名 -> 匿名用户

SQL:

sql 复制代码
SELECT
  id,
  COALESCE(nickname, real_name, username, '匿名用户') AS display_name
FROM users;

结果类似:

text 复制代码
+----+--------------+
| id | display_name |
+----+--------------+
|  1 | 老张         |
|  2 | 李四         |
|  3 |              |
|  4 | 匿名用户     |
+----+--------------+

第三行结果是空字符串,不是 匿名用户

原因是:

text 复制代码
COALESCE 只判断 NULL,不会把空字符串 '' 当成 NULL。

如果空字符串也要当成空值处理,需要配合 NULLIF

sql 复制代码
SELECT
  id,
  COALESCE(
    NULLIF(nickname, ''),
    NULLIF(real_name, ''),
    NULLIF(username, ''),
    '匿名用户'
  ) AS display_name
FROM users;

这时第三行会显示 王五

示例三:联系方式兜底

联系方式经常不止一个字段,比如手机号、备用手机号、邮箱。

sql 复制代码
SELECT
  id,
  COALESCE(mobile, backup_mobile, email, '无联系方式') AS contact
FROM users;

结果类似:

text 复制代码
+----+---------------------+
| id | contact             |
+----+---------------------+
|  1 | 13800000001         |
|  2 | 13900000002         |
|  3 | wangwu@example.com  |
|  4 | 无联系方式          |
+----+---------------------+

这种写法比多层 CASE WHEN 更短,也更容易看出优先级。

示例四:金额计算时把 NULL 当成 0

订单实付金额 = 订单金额 - 优惠金额。

直接写:

sql 复制代码
SELECT
  order_no,
  amount - discount AS actual_amount
FROM orders;

如果 discountNULL,结果也会是 NULL

推荐写法:

sql 复制代码
SELECT
  order_no,
  COALESCE(amount, 0) AS amount,
  COALESCE(discount, 0) AS discount,
  COALESCE(amount, 0) - COALESCE(discount, 0) AS actual_amount
FROM orders;

结果类似:

text 复制代码
+----------+--------+----------+---------------+
| order_no | amount | discount | actual_amount |
+----------+--------+----------+---------------+
| A001     | 100.00 |    10.00 |         90.00 |
| A002     | 200.00 |     0.00 |        200.00 |
| A003     |   0.00 |     0.00 |          0.00 |
| A004     |  80.00 |     5.00 |         75.00 |
+----------+--------+----------+---------------+

金额字段参与计算时,COALESCE(金额字段, 0) 很常见。

示例五:聚合函数没有结果时返回 0

SUMAVG 这类聚合函数,在没有匹配数据时可能返回 NULL

例如查询一个不存在用户的订单总额:

sql 复制代码
SELECT SUM(amount) AS total_amount
FROM orders
WHERE user_id = 999;

结果:

text 复制代码
NULL

使用 COALESCE 兜底:

sql 复制代码
SELECT COALESCE(SUM(amount), 0) AS total_amount
FROM orders
WHERE user_id = 999;

结果:

text 复制代码
0.00

统计支付金额、退款金额也很常见:

sql 复制代码
SELECT
  COALESCE(SUM(pay_amount), 0) AS total_pay_amount,
  COALESCE(SUM(refund_amount), 0) AS total_refund_amount
FROM payments;

注意:COUNT(*) 本身没有匹配行时会返回 0,通常不需要写成 COALESCE(COUNT(*), 0)

sql 复制代码
SELECT COUNT(*) AS order_count
FROM orders
WHERE user_id = 999;

结果就是:

text 复制代码
0

示例六:LEFT JOIN 后补默认值

LEFT JOIN 没匹配到右表数据时,右表字段会是 NULL

查询用户和部门:

sql 复制代码
SELECT
  u.id,
  COALESCE(u.username, '未命名用户') AS username,
  COALESCE(d.name, '未分配部门') AS department_name
FROM users AS u
LEFT JOIN departments AS d ON u.department_id = d.id;

结果类似:

text 复制代码
+----+------------+-----------------+
| id | username   | department_name |
+----+------------+-----------------+
|  1 | zhangsan   | 研发部          |
|  2 | lisi       | 销售部          |
|  3 | wangwu     | 未分配部门      |
|  4 | 未命名用户 | 未分配部门      |
+----+------------+-----------------+

这类写法适合列表页、导出报表、统计看板。

示例七:拼接字符串时避免结果变 NULL

MySQL 的 CONCAT 只要有一个参数是 NULL,结果就会变成 NULL

sql 复制代码
SELECT CONCAT(username, ' / ', mobile) AS user_contact
FROM users;

如果 mobileNULL,整段拼接结果也会是 NULL

使用 COALESCE

sql 复制代码
SELECT
  CONCAT(
    COALESCE(username, '匿名用户'),
    ' / ',
    COALESCE(mobile, backup_mobile, email, '无联系方式')
  ) AS user_contact
FROM users;

结果类似:

text 复制代码
+-------------------------------+
| user_contact                  |
+-------------------------------+
| zhangsan / 13800000001        |
| lisi / 13900000002            |
| wangwu / wangwu@example.com   |
| 匿名用户 / 无联系方式         |
+-------------------------------+

示例八:按更新时间或创建时间排序

很多表都有 updated_atcreated_at

列表排序时,希望优先按更新时间排序;没有更新时间时,按创建时间排序。

sql 复制代码
SELECT
  order_no,
  updated_at,
  created_at
FROM orders
ORDER BY COALESCE(updated_at, created_at) DESC;

COALESCE(updated_at, created_at) 表示:

text 复制代码
有更新时间就用更新时间,没有更新时间就用创建时间。

示例九:没有排序号的排最后

sort_no 有值的按 sort_no 排,没有值的排最后。

sql 复制代码
SELECT
  order_no,
  sort_no
FROM orders
ORDER BY COALESCE(sort_no, 999999) ASC;

结果类似:

text 复制代码
+----------+---------+
| order_no | sort_no |
+----------+---------+
| A003     |       1 |
| A001     |       2 |
| A002     |    NULL |
| A004     |    NULL |
+----------+---------+

这里的 999999 是一个足够大的兜底值,让 NULL 排到后面。

示例十:UPDATE 时保留旧值

接口更新资料时,有些字段可能没传。没传的字段通常不希望被更新成 NULL

假设传入的新手机号为 @new_mobile,新头像为 @new_avatar

sql 复制代码
SET @new_mobile = NULL;
SET @new_avatar = '/avatar/new.png';

UPDATE users
SET
  mobile = COALESCE(@new_mobile, mobile),
  avatar = COALESCE(@new_avatar, avatar)
WHERE id = 1;

含义:

text 复制代码
新值不是 NULL 就用新值,新值是 NULL 就保留原字段值。

这种写法适合"部分字段更新"。不过如果业务允许把字段主动清空为 NULL,就不能单纯依赖这种写法,需要区分"没传字段"和"传了 NULL"。

COALESCE 和 IFNULL 的区别

IFNULL 是 MySQL 常用函数:

sql 复制代码
IFNULL(expr1, expr2)

含义:

text 复制代码
expr1 不是 NULL,返回 expr1;否则返回 expr2。

它只能接收两个参数。

sql 复制代码
SELECT IFNULL(NULL, '默认值');

等价于:

sql 复制代码
SELECT COALESCE(NULL, '默认值');

COALESCE 可以接收多个参数:

sql 复制代码
SELECT COALESCE(nickname, real_name, username, '匿名用户');

简单二选一时,IFNULLCOALESCE 都可以。多级兜底时,COALESCE 更合适。

另外,COALESCE 是标准 SQL,跨数据库兼容性更好。

COALESCE 和 NULLIF 经常一起用

NULLIF(a, b) 的意思是:

text 复制代码
如果 a = b,返回 NULL;否则返回 a。

例如:

sql 复制代码
SELECT NULLIF('', '');

结果:

text 复制代码
NULL

所以它经常和 COALESCE 组合,用来把空字符串、特殊值先转成 NULL,再做兜底。

把空字符串当成空值:

sql 复制代码
SELECT COALESCE(NULLIF(nickname, ''), '匿名用户') AS display_name
FROM users;

0 当成无效金额:

sql 复制代码
SELECT COALESCE(NULLIF(amount, 0), 1) AS safe_amount
FROM orders;

上面表示:如果 amount0,先转成 NULL,再兜底为 1

COALESCE 和 CASE WHEN 的关系

下面两段 SQL 逻辑接近。

sql 复制代码
SELECT COALESCE(nickname, real_name, username, '匿名用户') AS display_name
FROM users;

CASE WHEN 写:

sql 复制代码
SELECT
  CASE
    WHEN nickname IS NOT NULL THEN nickname
    WHEN real_name IS NOT NULL THEN real_name
    WHEN username IS NOT NULL THEN username
    ELSE '匿名用户'
  END AS display_name
FROM users;

只是判断 NULL 并按顺序兜底时,COALESCE 更短。

如果要写复杂条件,比如状态、金额区间、分数等级,CASE WHEN 更适合。

常见坑一:空字符串不是 NULL

sql 复制代码
SELECT COALESCE('', '默认值') AS result;

结果是空字符串,不是 默认值

因为:

text 复制代码
'' 是一个长度为 0 的字符串,但它不是 NULL。

如果空字符串也要兜底:

sql 复制代码
SELECT COALESCE(NULLIF('', ''), '默认值') AS result;

结果:

text 复制代码
默认值

常见坑二:参数类型尽量一致

不推荐:

sql 复制代码
SELECT COALESCE(NULL, 100, '未知');

参数里既有数字,又有字符串,后续参与计算或排序时容易引发隐式类型转换。

推荐保持同一类结果:

sql 复制代码
SELECT COALESCE(CAST(score AS CHAR), '未知') AS score_text
FROM users;

或者:

sql 复制代码
SELECT COALESCE(score, 0) AS score
FROM users;

常见坑三:WHERE 里包字段可能影响索引

不推荐:

sql 复制代码
SELECT *
FROM users
WHERE COALESCE(mobile, backup_mobile) = '13800000001';

这种写法把字段包进函数表达式里,优化器更难直接使用普通索引。

可以改成更明确的条件:

sql 复制代码
SELECT *
FROM users
WHERE mobile = '13800000001'
   OR (mobile IS NULL AND backup_mobile = '13800000001');

如果查询特别高频,可以考虑补充合适索引、生成列或在数据写入时提前维护一个统一联系方式字段。

常见坑四:不要把缺失数据直接伪装成真实数据

COALESCE 很适合展示兜底,但不一定适合所有统计场景。

例如平均订单金额:

sql 复制代码
SELECT AVG(COALESCE(amount, 0)) AS avg_amount
FROM orders;

这表示:amountNULL 的订单也按 0 参与平均值计算。

如果 NULL 的真实含义是"未知金额",这样会拉低平均值。

另一种写法:

sql 复制代码
SELECT AVG(amount) AS avg_amount
FROM orders;

AVG 会忽略 NULL

所以统计时要先确认 NULL 的业务含义:

text 复制代码
NULL 是应该当作 0,还是应该当作未知并排除?

这个区别会直接影响报表结果。

实战建议

  • 展示字段缺省值,用 COALESCE(field, '默认值')
  • 多字段优先级取值,用 COALESCE(a, b, c, '兜底值')
  • 金额计算前,把可为空金额转成 0,例如 COALESCE(discount, 0)
  • SUM 没有结果时返回 0,用 COALESCE(SUM(amount), 0)
  • COUNT(*) 本身会返回 0,通常不需要再包 COALESCE
  • 空字符串要当成空值时,配合 NULLIF(field, '')
  • 只是简单二选一,IFNULL 也能用;多级兜底优先用 COALESCE
  • WHERE 中不要随手把索引字段包进 COALESCE
  • 参数返回类型尽量一致,减少隐式转换
  • 统计场景里先判断 NULL 的业务含义,再决定是否兜底为 0

总结

COALESCE 的核心就是"按顺序找第一个非 NULL 的值"。

它看起来只是一个小函数,但在列表展示、报表统计、字符串拼接、多字段兼容、LEFT JOIN 补默认值中都非常实用。

写 SQL 时,只要遇到"字段可能为空,但结果不能空"的场景,就可以考虑 COALESCE。同时要记住两个边界:空字符串不是 NULL,统计里的 NULL 不一定等于 0。处理好这两点,空值兜底就不容易出错。

相关推荐
wand codemonkey3 小时前
SpringbootWeb【入门】+MySQL【安装】+【DataDrip安装 】+【连接MySQL】
java·mysql·mybatis
廿一夏11 小时前
MySql存储引擎与索引
数据库·sql·mysql
lzhdim13 小时前
SQL 入门 15:SQL 事务:从 ACID 到四种常见的并发问题
数据库·sql
敲个大西瓜14 小时前
Java项目常用数据归档方式
mysql
kyriewen16 小时前
面试官让我查各部门工资最高的员工,我用AI三秒写出窗口函数,他愣了
后端·mysql·面试
小码工作室16 小时前
使用 HAVING 进行 MySQL 集合筛选
mysql
罗超驿16 小时前
18.事务的隔离性和隔离级别:MySQL面试高频考点全解析
数据库·mysql·面试
小江的记录本17 小时前
【Java基础】Java 8-21新特性:JDK21 LTS:虚拟线程、模式匹配switch、结构化并发、序列集合(附《思维导图》+《面试高频考点清单》)
java·数据库·python·mysql·spring·面试·maven
木心术117 小时前
Windows系统下MySQL与AI工具集成方案:数据存储与调用实践
人工智能·windows·mysql