在数据库管理与开发工作中,处理“空”值是一项基础且至关重要的技能,当数据缺失、未知或不适用时,我们需要一种机制来表示这种状态,这就是数据库中的 NULL。NULL 的处理方式与普通数据截然不同,理解其本质并掌握正确的书写方法是避免数据错误和查询异常的关键。

理解 NULL 的本质
必须明确一个核心概念:NULL 不等于空字符串 (),也不等于数字 0。
NULL:表示“未知”、“缺失”或“不适用”,它是一个状态,而不是一个具体的值,在一个用户表中,如果某个用户没有填写“中间名”字段,那么该字段的值就应该是NULL,因为我们不知道其中间名是什么,而不是他的中间名是一个空字符串。- 空字符串 ():表示一个长度为零的字符串,它是一个确定的值,我们知道这个值是什么——空”,在问卷调查中,如果用户明确选择了“无”或者清空了输入框,我们可以将其存为空字符串。
- 数字
0:表示一个具体的数值,商品库存为0,意味着库存确实为零,而不是库存数量未知。
混淆这三者会导致业务逻辑的混乱,在数据库设计之初,就应该明确每个字段在数据缺失时应如何表示。
在表设计中定义 NULL
字段是否允许为 NULL,是在创建表(CREATE TABLE)时通过 NULL 或 NOT NULL 约束来定义的。
NULL:表示该字段可以接受NULL值,这是大多数数据库的默认设置。NOT NULL:表示该字段必须包含一个非NULL的值,在插入或更新记录时,如果未给该字段提供值,数据库会报错。
示例:创建一个用户表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL, -- 用户名不能为空
email VARCHAR(100), -- 邮箱可以为空,默认允许 NULL
phone VARCHAR(20) NOT NULL, -- 电话不能为空
bio TEXT NULL -- 个人简介可以为空,显式声明 NULL
);
在这个例子中,username 和 phone 是必填字段,而 email 和 bio 是可选字段,当用户注册时没有提供邮箱或个人简介,这两个字段就可以被设置为 NULL。
插入与更新 NULL 值
当需要向数据库写入 NULL 值时,主要有两种方式。
插入数据 (INSERT)
- 显式使用
NULL关键字:在VALUES子句中直接写NULL。-- 插入一个没有邮箱的用户 INSERT INTO users (username, email, phone, bio) VALUES ('zhangsan', NULL, '13800138000', NULL); - 省略字段:如果字段被定义为允许
NULL,在INSERT语句中不列出该字段,数据库会自动为其填充NULL。-- 同样是插入一个没有邮箱和简介的用户,但省略了这两个字段 INSERT INTO users (username, phone) VALUES ('lisi', '13900139000');这种方式代码更简洁,是推荐的做法。

更新数据 (UPDATE)
当需要将一个已有值的字段清空(设置为未知状态)时,可以在 SET 子句中使用 NULL 关键字。
-- 将用户ID为1的邮箱更新为 NULL UPDATE users SET email = NULL WHERE id = 1;
这个操作常用于用户删除其绑定信息,例如取消绑定邮箱或手机号。
查询 NULL 值
查询 NULL 值是开发者最容易出错的地方。不能使用 或 运算符来判断 NULL,因为 NULL 不等于任何值,包括它自己。
WHERE email = NULL—— 错误写法,永远无法返回任何结果。WHERE email != NULL—— 错误写法,同样永远无法返回任何结果。
正确的做法是使用 IS NULL 和 IS NOT NULL 运算符。
IS NULL:用于查找字段值为NULL的记录。-- 查询所有没有提供邮箱的用户 SELECT * FROM users WHERE email IS NULL;
IS NOT NULL:用于查找字段值不为NULL的记录。-- 查询所有提供了邮箱的用户 SELECT * FROM users WHERE email IS NOT NULL;
最佳实践与注意事项
-
NULLvs. 空字符串:根据业务含义选择,如果信息“未知”或“不适用”,用NULL,如果信息“明确为空”,用空字符串。nickname(昵称)字段,用户没设置就是NULL;用户设置了昵称后又清空,可以存为空字符串 ,表示“我决定不要昵称”。 -
聚合函数对
NULL的处理:大多数聚合函数会忽略NULL值。
| 函数名 | 对 NULL 的处理 |
示例 |
|---|---|---|
COUNT(column_name) |
忽略 NULL 值,只统计非 NULL 的行数。 |
COUNT(email) 统计有邮箱的用户数。 |
COUNT(*) |
不忽略 NULL,统计所有行的总数。 |
COUNT(*) 统计表内总用户数。 |
SUM(column_name) |
忽略 NULL 值,只对非 NULL 的值求和。 |
SUM(score) 只计算有分数的用户的总分。 |
AVG(column_name) |
忽略 NULL 值,只对非 NULL 的值求平均。 |
AVG(score) 只计算有分数的用户的平均分。 |
-
索引与
NULL:在大多数数据库系统(如 MySQL, PostgreSQL)中,可以为包含NULL值的列创建索引,但查询IS NULL的性能可能略低于查询具体值,设计时需权衡。
-
唯一约束与
NULL:在支持 SQL 标准的数据库中,被UNIQUE约束的列可以包含多个NULL值,因为NULL代表未知,两个未知的值不被视为“相等”,一个phone_number列可以有多个NULL值,表示多个用户都没有提供电话号码。
相关问答 FAQs
在业务代码中(如 Java, Python),如何处理从数据库读出的 NULL 值?
解答: 这取决于编程语言和数据库驱动,当从数据库查询出一个 NULL 值时,驱动程序会将其映射为语言中的特定“空”类型。
- 在 Java 中,使用 JDBC 时,
ResultSet的getString(),getInt()等方法如果读到NULL,会返回null(对于对象类型)或0(对于基本类型,如getInt()),为了避免将0误判为真实数据,应先使用wasNull()方法检查上一列是否为NULL,更好的做法是使用能处理NULL的对象类型,如Integer而非int。 - 在 Python 中,使用如
psycopg2(PostgreSQL) 或mysql-connector(MySQL) 等库,NULL值通常会被转换为None,在代码中,你需要显式地判断变量是否为None,然后进行相应的业务逻辑处理,if user.bio is None: ...,以避免NoneType错误。
外键字段可以为 NULL 吗?这在什么场景下有用?
解答: 可以,外键字段完全允许为 NULL,前提是该字段在定义时没有被加上 NOT NULL 约束,这是一个非常有用的设计,用于表示可选的或非强制的关联关系。
- 典型场景:员工与经理的关系,在一个
employees表中,可以有一个manager_id字段作为外键,引用employees表自身的id,普通员工的manager_id指向其经理的id,公司最高级别的领导(如 CEO)没有上级经理,此时他的manager_id字段就可以设置为NULL,这完美地表达了“没有经理”这一业务状态,而无需创建一个特殊的“虚拟经理”记录,这种设计既简洁又符合逻辑,是处理层级或可选关系时的标准做法。