NULL不是空——数据库里最反直觉的设计,90%新人踩过的坑

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

数据库里有一个设计,让无数新手怀疑人生------NULL。

你以为NULL代表"空"、"什么都没有"、"等于空白"。但数据库告诉你:WHERE column = NULL查不出任何数据。你以为COUNT(*)能统计所有行,但COUNT(column)却漏掉了一些。你写了一个if (value == null)的判断,结果数据死活对不上。

这一切的根源在于------NULL在数据库里,根本不是空值,而是一个特殊标记,表示"未知"或"不适用"。

今天把NULL这件事讲清楚,帮你避开那些反直觉的坑。


几个先搞明白的概念

NULL的本质:NULL不是空字符串'',不是0,不是false。它是一个独立的特殊值,表示"这个字段当前没有确定的值"。你可以把它理解成一张问卷上的"未作答"------它不是"否",也不是"空白",而是"我不知道"。

三值逻辑:大多数编程语言只有TRUE和FALSE两种结果。但数据库引入了NULL之后,逻辑运算变成了三值:TRUE、FALSE、UNKNOWN。当运算中涉及NULL时,结果可能就是UNKNOWN------而WHERE条件只返回TRUE的行,UNKNOWN被当作FALSE处理,这就是为什么很多查询"查不出数据"的根本原因。

空字符串 vs NULL:空字符串''是一个确定的值------它就是一个长度为0的字符串。NULL表示"没有值"。''和NULL在数据库中是完全不同的两个概念。


NULL的五大反直觉陷阱

陷阱一:WHERE column = NULL 查不出任何数据

这是新手必踩的坑。

sql 复制代码
-- 你以为这样能查出所有phone为空的记录
SELECT * FROM users WHERE phone = NULL;
-- 结果:0行

-- 正确写法
SELECT * FROM users WHERE phone IS NULL;

为什么?因为NULL不等于任何东西,包括它自己。在数据库中:

sql 复制代码
NULL = NULL  →  UNKNOWN(不是TRUE)
NULL != NULL →  UNKNOWN(不是TRUE)

NULL = NULL的结果是UNKNOWN,而WHERE只返回TRUE的行,所以查出来是0行。必须用IS NULLIS NOT NULL来判断。

陷阱二:COUNT(column) 忽略NULL值

sql 复制代码
-- 假设users表有100行,其中10行phone为NULL

SELECT COUNT(*) FROM users;     -- 100(统计所有行)
SELECT COUNT(phone) FROM users; -- 90(忽略NULL值)

COUNT()统计行数,不管字段是什么。COUNT(column)只统计该字段非NULL的行数。如果你想知道"有多少人有手机号",用COUNT(phone);如果你想知道"总共有多少人",用COUNT()。

陷阱三:NULL参与运算,结果还是NULL

sql 复制代码
SELECT 1 + NULL;        -- NULL
SELECT 'hello' || NULL; -- NULL
SELECT NULL = 0;        -- UNKNOWN(不是FALSE)
SELECT NULL = '';       -- UNKNOWN(不是FALSE)
SELECT NULL AND TRUE;   -- UNKNOWN(不是FALSE)

任何值和NULL运算,结果都是NULL。这在实际业务中会造成很多bug:

sql 复制代码
-- 计算员工总薪资
SELECT salary + bonus FROM employees;
-- 如果某个员工bonus是NULL,整条记录的总薪资就是NULL

正确做法是用COALESCE把NULL替换为默认值:

sql 复制代码
SELECT salary + COALESCE(bonus, 0) FROM employees;
-- NULL变成0,计算正常

陷阱四:NOT IN 遇到NULL,整个查询结果为空

这是最隐蔽的一个。

sql 复制代码
-- 假设子查询返回了 (1, 2, NULL)
SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM orders);

-- 如果orders表里有user_id为NULL的记录,整个查询返回0行

为什么?因为NOT IN在底层展开成:

sql 复制代码
WHERE id != 1 AND id != 2 AND id != NULL

id != NULL的结果是UNKNOWN,整个AND表达式变成UNKNOWN,WHERE不返回任何行。

解决办法:用NOT EXISTS替代NOT IN,或者在子查询中排除NULL:

sql 复制代码
-- 方案一:NOT EXISTS
SELECT * FROM users u WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id
);

-- 方案二:子查询排除NULL
SELECT * FROM users WHERE id NOT IN (
    SELECT user_id FROM orders WHERE user_id IS NOT NULL
);

陷阱五:排序时NULL的位置

不同数据库对NULL的排序处理不同:

sql 复制代码
SELECT * FROM users ORDER BY phone ASC;
-- MySQL:NULL排在最前面
-- Oracle/PostgreSQL:NULL排在最后面

如果你不确定NULL的排序行为,最好显式指定:

sql 复制代码
-- PostgreSQL
SELECT * FROM users ORDER BY phone ASC NULLS FIRST;
SELECT * FROM users ORDER BY phone DESC NULLS LAST;

-- MySQL(用IF/CASE处理)
SELECT * FROM users ORDER BY IF(phone IS NULL, 1, 0), phone ASC;

NULL的正确打开方式

判断NULL :用IS NULLIS NOT NULL,别用=!=

处理NULL参与计算 :用COALESCE提供默认值。

sql 复制代码
SELECT COALESCE(phone, '未登记') FROM users;
SELECT salary + COALESCE(bonus, 0) FROM employees;

聚合函数对NULL的态度

函数 对NULL的处理
COUNT(*) 统计所有行,不忽略NULL
COUNT(column) 忽略该列的NULL值
SUM(column) 忽略NULL值
AVG(column) 忽略NULL值,且分母也不计NULL行
MAX/MIN 忽略NULL值
GROUP BY NULL值被分到同一组

避免NULL的设计思路

如果业务上某个字段"必须有值",在建表时就加上NOT NULL约束,并设置默认值:

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    phone VARCHAR(20)  -- 允许NULL,因为确实可能没登记
);

NOT NULL的字段就别留NULL。NULL存在的每一处,都是未来查询时可能踩的坑。


总结

NULL不是空值,是"未知"。记住三句话:

  1. 判断NULL用IS NULL,别用=
  2. NULL参与运算结果是NULL,用COALESCE处理
  3. NOT IN遇到NULL会吞掉所有结果,改用NOT EXISTS

建表时能NOT NULL就别留NULL,少一个NULL,少十个bug。

小耶在手,SQL不愁。

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
这个DBA有点耶3 小时前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
镜舟科技3 小时前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
Databend4 小时前
从湖仓升级为 Agent 时代的数据控制面,Snowflake 和 Databricks 有哪些布局
大数据·数据库·agent
ClouGence7 小时前
SQL Server CDC 能放到 Always On 备库读吗?一文讲透原理与实践
数据库·sql server
先吃饱再说1 天前
存储的进化:从 MySQL 到浏览器缓存,数据到底住在哪?
数据库
Nturmoils1 天前
字段太多看不全,ksql 的展开模式和输出控制怎么用
数据库·后端
Databend1 天前
Agent 轨迹分析与归因的数据工程实践
大数据·数据库·agent
这个DBA有点耶1 天前
SQL改写进阶:标量子查询的“隐形代价”与消除实战
数据库·mysql·架构
smallyoung1 天前
数据库乐观锁深度解析:MySQL、PostgreSQL 实战 + Spring Boot 集成指南
数据库·mysql·postgresql