当前位置: 首页 > news >正文

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

关键词:NULL、数据库空值、三值逻辑、NULL陷阱、SQL空值处理


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

数据库里有一个设计,让无数新手怀疑人生——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 查不出任何数据

这是新手必踩的坑。

-- 你以为这样能查出所有phone为空的记录SELECT*FROMusersWHEREphone=NULL;-- 结果:0行-- 正确写法SELECT*FROMusersWHEREphoneISNULL;

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

NULL=NULL→ UNKNOWN(不是TRUENULL!=NULL→ UNKNOWN(不是TRUE

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

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

-- 假设users表有100行,其中10行phone为NULLSELECTCOUNT(*)FROMusers;-- 100(统计所有行)SELECTCOUNT(phone)FROMusers;-- 90(忽略NULL值)

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

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

SELECT1+NULL;-- NULLSELECT'hello'||NULL;-- NULLSELECTNULL=0;-- UNKNOWN(不是FALSE)SELECTNULL='';-- UNKNOWN(不是FALSE)SELECTNULLANDTRUE;-- UNKNOWN(不是FALSE)

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

-- 计算员工总薪资SELECTsalary+bonusFROMemployees;-- 如果某个员工bonus是NULL,整条记录的总薪资就是NULL

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

SELECTsalary+COALESCE(bonus,0)FROMemployees;-- NULL变成0,计算正常

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

这是最隐蔽的一个。

-- 假设子查询返回了 (1, 2, NULL)SELECT*FROMusersWHEREidNOTIN(SELECTuser_idFROMorders);-- 如果orders表里有user_id为NULL的记录,整个查询返回0行

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

WHEREid!=1ANDid!=2ANDid!=NULL

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

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

-- 方案一:NOT EXISTSSELECT*FROMusers uWHERENOTEXISTS(SELECT1FROMorders oWHEREo.user_id=u.id);-- 方案二:子查询排除NULLSELECT*FROMusersWHEREidNOTIN(SELECTuser_idFROMordersWHEREuser_idISNOTNULL);

陷阱五:排序时NULL的位置

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

SELECT*FROMusersORDERBYphoneASC;-- MySQL:NULL排在最前面-- Oracle/PostgreSQL:NULL排在最后面

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

-- PostgreSQLSELECT*FROMusersORDERBYphoneASCNULLSFIRST;SELECT*FROMusersORDERBYphoneDESCNULLSLAST;-- MySQL(用IF/CASE处理)SELECT*FROMusersORDERBYIF(phoneISNULL,1,0),phoneASC;

NULL的正确打开方式

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

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

SELECTCOALESCE(phone,'未登记')FROMusers;SELECTsalary+COALESCE(bonus,0)FROMemployees;

聚合函数对NULL的态度

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

避免NULL的设计思路

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

CREATETABLEusers(idINTPRIMARYKEY,nameVARCHAR(50)NOTNULL,statusTINYINTNOTNULLDEFAULT0,phoneVARCHAR(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不愁。

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


参考文献

  1. SQL-92标准 - 三值逻辑规范
  2. 《SQL反模式》第12章:NULL的处理陷阱
  3. MySQL 8.0 Reference Manual - Working with NULL:https://dev.mysql.com/doc/refman/8.0/en/working-with-null.html
  4. PostgreSQL Documentation - Null Values:https://www.postgresql.org/docs/current/functions-comparison.html
http://www.gsyq.cn/news/1630018.html

相关文章:

  • LiteLLM代理配置优化:解决DeepSeek API Token异常消耗问题
  • WzComparerR2:深入解析冒险岛WZ文件资源的专业提取器
  • Python Tkinter实现SM4国密文件加解密桌面工具开发指南
  • Manus AI深度评测:本地优先的AI编程助手实战账本
  • STM32F429ZI与13DOF传感器融合的嵌入式导航方案
  • WeChatPad:解锁微信多设备同时登录的实用方案
  • WinDiskWriter终极指南:5分钟在Mac上制作Windows启动U盘完整教程
  • Fortify扫描报告深度解析:SQL注入、XSS与反序列化漏洞实战修复指南
  • MuleSoft+LangChain双引擎架构:企业AI落地的交响指挥方案
  • linkinfo.dll 缺失会影响快捷方式吗?路径组件排查顺序
  • 3分钟快速上手:Figma中文汉化插件终极指南
  • 绿色革命来袭!2026中国(武汉)再生金属与新能源材料回收展会抢先看
  • 如何撰写合规高质量的AI模型技术对比博文
  • STM32F407VGT6驱动RGB LED矩阵的嵌入式系统设计
  • Windows网络性能测试利器:iperf3完整安装与使用实战指南
  • 自动驾驶感知 vs 具身智能感知:本质差异全解析
  • TQVaultAE终极指南:彻底解决《泰坦之旅周年版》背包空间不足的5个实用技巧
  • Promptfoo:面向生产环境的LLM提示词质量评估框架
  • 基于鸿蒙HarmonyOS NEXT开发AI电影推荐应用:智能观影新体验与鸿蒙Flutter框架跨端实践
  • MMMU:多模态AI理解能力的专业评估框架技术深度解析
  • 深入解析AI老照片修复技术:基于GFPGAN与Next.js的架构设计与实现原理
  • STM32H750XB与WSEN-ISDS的6DoF运动追踪系统设计
  • E-Hentai漫画批量下载器:免费快速获取完整漫画的终极解决方案
  • 因果推断核心方法与应用实践指南
  • 如何快速实现B站缓存视频格式转换:面向新手的完整指南
  • 基于74HC32与PIC32的硬件去抖动矩阵键盘设计
  • 解密gInk:一款让屏幕标注如呼吸般自然的轻量级神器
  • 格子GEO优化系统源码深度解析:从零搭建AI驱动的内容矩阵
  • 星露谷物语模组加载终极指南:SMAPI完整教程与常见问题解决
  • CSDN原力值快速提升攻略|通用满分冲分指南(2026最新)