3.1 列表达式、常用函数与 NULL 的三值逻辑


3.1 列表达式、常用函数与 NULL 的三值逻辑

本节摘要:SELECT 的列位置可以放表达式——运算、拼接、函数调用皆可,加工后的列用 AS 起别名;常用函数集中在字符串、日期、数值三族;而 NULL 不是值而是"未知",它让比较运算返回第三个结果"未知",只有 IS NULL 能准确找到它。本节把这三件事一次讲透,NULL 的篇幅最大,因为它是全书陷阱的源头。

一条查询引出的三个意外

从一条看似无害的报表查询开始。需求:算出每本书的库存金额,并标出出版了多少天。

-- 库存金额 = 价格 × 库存;出版天数 = 今天减去出版日期 SELECT title, price * stock AS stock_value, DATEDIFF(CURDATE(), published_at) AS days_since_pub FROM books;
+-----------------------+-------------+-----------------+ | title | stock_value | days_since_pub | +-----------------------+-------------+-----------------+ | 数据库系统概念 | 12.00 | 2732 | | SQL必知必会 | 1552.00 | 2277 | | 算法导论 | 896.00 | 5313 | | 深入理解计算机系统 | NULL | NULL | +-----------------------+-------------+-----------------+

三个意外依次现身。意外一:第一行的库存金额算出来只有 12.00——因为演示数据里这本书此刻价格恰好是 1.00,运算没有问题,但读数的人必须知道这列是乘出来的,别把中间表当原始事实。意外二:第四本书两列全是 NULL——它还没定出版日期,published_at 为 NULL,而 NULL 参与任何运算结果都是 NULL:未知的日子减去今天,天数自然也未知。意外三:NULL 出现在数字列里,却没有让整行消失——它以一种"存在但不可知"的姿态挂在结果集里,等你后续处理。

先解决前半截:表达式与函数。列位置能放的东西远比想象多——算术运算(+ - * /)、字符串拼接、函数调用、标量子查询(第 6 章)、CASE(下一节)。三族函数速览:

-- 字符串族:拼接、截取、大小写、长度 SELECT CONCAT(title, '(', stock, '本)') AS 标签, SUBSTRING(title, 1, 6) AS 前六字, CHAR_LENGTH(title) AS 字符数 FROM books LIMIT 2;
+------------------------------+------------+--------------+ | 标签 | 前六字 | 字符数 | +------------------------------+------------+--------------+ | 数据库系统概念(12本) | 数据库系统 | 7 | | SQL必知必会(30本) | SQL必知 | 8 | +------------------------------+------------+--------------+
-- 日期族:格式化、间隔、提取部件 SELECT DATE_FORMAT(published_at, '%Y年%m月') AS 出版月份, YEAR(published_at) AS 年, CURDATE() + INTERVAL 30 DAY AS 三十天后 FROM books WHERE id = 1;
+--------------+--------+------------+ | 出版月份 | 年 | 三十天后 | +--------------+--------+------------+ | 2019年03月 | 2019 | 2026-09-21 | +--------------+--------+------------+

两族函数的方言差异最明显:日期函数 MySQL 用 DATE_FORMAT,PostgreSQL 用 TO_CHAR;字符串拼接 MySQL 用 CONCAT,标准 SQL 用两个竖线。换数据库时优先核对函数表,语法骨架倒是大同小异。

NULL:不是值,是未知

现在正面处理第三列的 NULL。定义先行:NULL 表示"这一格的值未知或不存在",它不是 0、不是空字符串、不是 'NULL' 文本。四个东西在数据库里是四种完全不同的状态:库存为 0 表示"确确实实没有货",库存为 NULL 表示"不知道有多少货"——前者是事实,后者是无知。

因为 NULL 是未知,比较运算遇到它就产生第三种结果。普通比较只有真、假两态,而"未知"参与的比较既不能说真也不能说假,只能返回未知(UNKNOWN)。这就是所谓三值逻辑:真、假、未知。看实测:

-- 实验:NULL 参与的各种比较,返回 1 为真、0 为假、NULL 为未知 SELECT (NULL = NULL) AS 等于自己, (NULL <> 1) AS 不等于一, (NULL IS NULL) AS 是不是NULL, (1 = 1) AS 普通比较;
+--------------+--------------+--------------+--------------+ | 等于自己 | 不等于一 | 是不是NULL | 普通比较 | +--------------+--------------+--------------+--------------+ | NULL | NULL | 1 | 1 | +--------------+--------------+--------------+--------------+

NULL = NULL 的结果不是 1(真)而是 NULL(未知)——两个未知互相比较,答案仍是未知。推论非常实用:WHERE price = NULL 永远筛不出任何行,因为条件结果不是真。找 NULL 唯一正确的姿势是 IS NULL,反过来是 IS NOT NULL。

WHERE 只放行结果为"真"的行,"假"与"未知"都被拦下。这个规则在 NOT 面前会变得反直觉:

-- 反直觉实验:NOT 对未知的传播 SELECT NOT (NULL = 1) AS 非真即假吗, (NULL = 1 OR 1 = 1) AS 或上真, (NULL = 1 AND 1 = 2) AS 且上假;
+------------------+------------+------------+ | 非真即假吗 | 或上真 | 且上假 | +------------------+------------+------------+ | NULL | 1 | 0 | +------------------+------------+------------+

三个结果连起来读:NOT 未知还是未知;"未知 OR 真"因第二项为真而整体为真;"未知 AND 假"因第二项为假而整体为假。三值逻辑的运算表不过这几条,但第 6 章 NOT IN 的著名翻车现场正是"NOT 未知 = 未知"这一条掀起的——先在这里立好碑。

图 3-1 NULL 三值逻辑判定图

图 3-1 NULL 三值逻辑判定图

与 NULL 共事的日常

空值在实际工作里最常出现在报表展示与排序两个场景。展示场景用 COALESCE 补默认值——它接受多个参数,返回第一个非 NULL 的:

-- 出版日期未定时显示"待定",天数未知时显示横杠 SELECT title, COALESCE(DATE_FORMAT(published_at, '%Y-%m'), '待定') AS 出版信息, COALESCE(CAST(DATEDIFF(CURDATE(), published_at) AS CHAR), '-') AS 已出版天数 FROM books ORDER BY stock_value兜底 ASC LIMIT 3;

上面的 ORDER BY 故意写了一个不存在的列名兜底,执行会报错——正确写法是把表达式或别名原样放进 ORDER BY。修正后:

SELECT title, price * stock AS stock_value, COALESCE(DATE_FORMAT(published_at, '%Y-%m'), '待定') AS 出版信息 FROM books ORDER BY stock_value ASC; -- 别名在 ORDER BY 中可用(1.3 节的执行顺序)
+-----------------------+-------------+--------------+ | title | stock_value | 出版信息 | +-----------------------+-------------+--------------+ | 深入理解计算机系统 | NULL | 待定 | | 数据库系统概念 | 12.00 | 2019-03 | | 算法导论 | 896.00 | 2012-01 | +-----------------------+-------------+--------------+

注意 NULL 参与乘法后 stock_value 也成了 NULL,而 MySQL 升序排序把 NULL 排在最前——"未知"反而压过一切实数。想控制 NULL 位置,MySQL 提供 ORDER BY stock_value IS NULL, stock_value 的写法(NULL 往后放),PostgreSQL 提供 NULLS LAST 关键字。报表的空值哲学一句话:存储层诚实存 NULL,展示层按需补值,别把补过的值写回存储——一旦把"待定"写进日期列,第 4 章的日期函数全部罢工。

⚠️ 常见坑:拿 WHERE price <> NULL 或 WHERE price = NULL 找空值,查询"成功"返回零行,不报错、不报空,静默地什么都没干。凡是涉及可空列的过滤,先问自己一句"这一列可能有 NULL 吗",可能就补一个 OR 列 IS NULL 的分支。

💡 关键直觉:把 NULL 读成"问号"而不是"零"。问号加一百还是问号,问号等于问号答案还是问号;想要"问号变答案",只能用 IS NULL 认出它、用 COALESCE 替换它。

本节要点回顾

  • 列位置即表达式:运算、拼接、函数、CASE 都可以,AS 别名让下游引用方便,但别名只在 ORDER BY 可见、WHERE 不可见;
  • 三族函数是日常工具箱:字符串族拼接截取、日期族格式化求差、数值族四舍五入,方言差异集中在函数名;
  • NULL 是未知不是零:与 0、空串、'NULL' 文本是四种状态,语义完全不同;
  • 三值逻辑:NULL 参与比较得未知,未知过不了 WHERE,NOT 传播未知——NOT IN 陷阱的种子在此埋下;
  • 四个工具:IS NULL 认出、COALESCE 补值、NULL-aware 排序控制位置、聚合时记住函数会跳过 NULL(第 4 章展开)。

表达式解决"算什么",NULL 解决"未知怎么算"。下一节补上最后一块拼图:条件分支 CASE——让查询自己打标签、分档位。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U