7.函数【由浅入深-MySQL】

文章目录

  • [第一章 日期函数](#第一章 日期函数)
    • [1.1 获取当前日期、时间与时间戳](#1.1 获取当前日期、时间与时间戳)
    • [1.2 提取日期部分:DATE() 函数](#1.2 提取日期部分:DATE() 函数)
    • [1.3 日期加减运算:DATE_ADD() 与 DATE_SUB()](#1.3 日期加减运算:DATE_ADD() 与 DATE_SUB())
    • [1.4 计算日期差值:DATEDIFF()](#1.4 计算日期差值:DATEDIFF())
    • [1.5 综合案例一:生日记录表](#1.5 综合案例一:生日记录表)
    • [1.6 综合案例二:留言表与时间窗口查询](#1.6 综合案例二:留言表与时间窗口查询)
    • [1.7 总结与最佳实践](#1.7 总结与最佳实践)
  • [第二章 字符串函数](#第二章 字符串函数)
    • [2.1 字符集探测:CHARSET()](#2.1 字符集探测:CHARSET())
    • [2.2 字节长度与字符长度:LENGTH() 的深度剖析](#2.2 字节长度与字符长度:LENGTH() 的深度剖析)
    • [2.3 大小写转换:UCASE() 与 LCASE()](#2.3 大小写转换:UCASE() 与 LCASE())
    • [2.4 字符串拼接:CONCAT()](#2.4 字符串拼接:CONCAT())
    • [2.5 字符串截取:LEFT()、RIGHT() 与 SUBSTRING()](#2.5 字符串截取:LEFT()、RIGHT() 与 SUBSTRING())
      • [2.5.1 从两端定向截取:LEFT() 与 RIGHT()](#2.5.1 从两端定向截取:LEFT() 与 RIGHT())
      • [2.5.2 灵活定位截取:SUBSTRING()](#2.5.2 灵活定位截取:SUBSTRING())
    • [2.6 查找与替换:INSTR() 与 REPLACE()](#2.6 查找与替换:INSTR() 与 REPLACE())
      • [2.6.1 查找首次出现位置:INSTR()](#2.6.1 查找首次出现位置:INSTR())
      • [2.6.2 全局替换:REPLACE()](#2.6.2 全局替换:REPLACE())
    • [2.7 字符串逐字比较:STRCMP()](#2.7 字符串逐字比较:STRCMP())
    • [2.8 空白字符清理:LTRIM()、RTRIM() 与 TRIM()](#2.8 空白字符清理:LTRIM()、RTRIM() 与 TRIM())
    • [2.9 综合实战:一个需求贯穿所有函数](#2.9 综合实战:一个需求贯穿所有函数)
  • [第三章 数学函数](#第三章 数学函数)
    • [3.1 绝对值函数:ABS()](#3.1 绝对值函数:ABS())
    • [3.2 取整函数:CEILING() 与 FLOOR()](#3.2 取整函数:CEILING() 与 FLOOR())
    • [3.3 格式化小数:FORMAT()](#3.3 格式化小数:FORMAT())
    • [3.4 随机数生成:RAND()](#3.4 随机数生成:RAND())
    • [3.5 取模运算:MOD()](#3.5 取模运算:MOD())
    • [3.6 进制转换详解:BIN()、HEX() 与 CONV()](#3.6 进制转换详解:BIN()、HEX() 与 CONV())
      • [3.6.1 BIN():十进制转二进制](#3.6.1 BIN():十进制转二进制)
      • [3.6.2 HEX():十进制转十六进制](#3.6.2 HEX():十进制转十六进制)
      • [3.6.3 CONV():通用进制转换](#3.6.3 CONV():通用进制转换)
      • [3.6.4 关键限制:浮点数会被截断为整数](#3.6.4 关键限制:浮点数会被截断为整数)
    • [3.7 综合案例:统计报表中的数值处理](#3.7 综合案例:统计报表中的数值处理)
  • [第四章 其他函数](#第四章 其他函数)
    • [4.1 会话与环境信息:USER() 与 DATABASE()](#4.1 会话与环境信息:USER() 与 DATABASE())
      • [4.1.1 查询当前用户:USER()](#4.1.1 查询当前用户:USER())
      • [4.1.2 查询当前数据库:DATABASE()](#4.1.2 查询当前数据库:DATABASE())
    • [4.2 空值处理保险丝:IFNULL()](#4.2 空值处理保险丝:IFNULL())
    • [4.3 数据安全摘要:MD5() 与 PASSWORD()](#4.3 数据安全摘要:MD5() 与 PASSWORD())
      • [4.3.1 MD5 信息摘要算法](#4.3.1 MD5 信息摘要算法)
      • [4.3.2 PASSWORD() 系统加密函数](#4.3.2 PASSWORD() 系统加密函数)
    • [4.4 关键字冲突与命令行历史记录](#4.4 关键字冲突与命令行历史记录)

第一章 日期函数

在数据库应用开发中,日期与时间的处理几乎无处不在------从记录用户注册时间、订单生成时刻,到统计某段时间内的数据增长,再到计算会员有效期剩余天数。MySQL 提供了一套完备的日期函数,帮助开发者高效、精准地操作时间数据。本章将围绕日期函数的核心用法展开,从基础获取到复杂运算,再到实际业务场景中的典型应用,逐一深入讲解。

1.1 获取当前日期、时间与时间戳

MySQL 提供了三个最基本的函数,分别用于获取当前的日期、当前的时间以及当前的日期时间(时间戳)。它们的命名直观且易于记忆。

函数 返回类型 描述 示例返回值
CURRENT_DATE() DATE 当前日期(年-月-日) 2026-08-31
CURRENT_TIME() TIME 当前时间(时:分:秒) 14:23:45
CURRENT_TIMESTAMP() DATETIME / TIMESTAMP 当前日期和时间 2026-08-31 14:23:45

注意 :CURRENT_TIMESTAMP() 与 NOW() 功能完全等价,二者可互换使用。

基础查询示例:

sql 复制代码
SELECT CURRENT_DATE();
+----------------+
| CURRENT_DATE() |
+----------------+
| 2026-08-31     |
+----------------+

SELECT CURRENT_TIME();
+----------------+
| CURRENT_TIME() |
+----------------+
| 14:23:45       |
+----------------+

SELECT CURRENT_TIMESTAMP();
+---------------------+
| CURRENT_TIMESTAMP() |
+---------------------+
| 2026-08-31 14:23:45 |
+---------------------+

一个容易被忽视的细节 :这三个函数虽然返回不同的数据类型,但它们的"源头"都是系统时钟------系统内部存储的是一个完整的日期时间值(精确到秒甚至微秒)。CURRENT_DATE() 本质上是截取了完整时间戳的"年月日"部分,并将其余部分归零;CURRENT_TIME() 则截取了"时分秒"部分。因此,可以理解为所有时间获取函数均基于同一个完整的内部时间,只是在返回给用户时进行了不同的格式裁剪与类型封装 。这与后面要讲的 DATE() 提取函数原理相通。

1.2 提取日期部分:DATE() 函数

当我们有一个 DATETIME 或 TIMESTAMP 类型的字段时,往往只需要关注日期部分(例如统计某天发布的留言数量)。DATE() 函数可以轻松完成这一提取。

语法:

sql 复制代码
DATE(datetime_value)
  • 参数:一个日期时间表达式(字段、常量或函数返回值)。
  • 返回值:DATE 类型,仅包含年、月、日。

示例:

sql 复制代码
SELECT DATE('2026-08-31 14:23:45');
+-----------------------------+
| DATE('2026-08-31 14:23:45') |
+-----------------------------+
| 2026-08-31                  |
+-----------------------------+

-- 与 NOW() 配合使用
SELECT DATE(NOW());
+-------------+
| DATE(NOW()) |
+-------------+
| 2026-08-31  |
+-------------+

在后面的留言表案例中,我们会看到 DATE() 如何用于控制显示格式。

在 MySQL 中,' '(单引号)主要用于表示字符串、日期时间等字面量,记住一句话:字符串/日期加 ' ',数字和 NULL 不加 。

1.3 日期加减运算:DATE_ADD() 与 DATE_SUB()

在实际业务中,我们经常需要在某个日期基础上增加或减少一段时间 ,例如计算订单的预计送达日期、会员到期日等。DATE_ADD() 和 DATE_SUB() 正是为此而生。

语法:

sql 复制代码
DATE_ADD(date, INTERVAL expr unit)
DATE_SUB(date, INTERVAL expr unit)
  • date:起始日期或日期时间。
  • INTERVAL:固定关键字。
  • expr:一个整数,表示要增加或减少的数量(可以为负数,但建议使用对应的函数以保持语义清晰)。
  • unit:单位,支持 YEAR、MONTH、DAY、HOUR、MINUTE、SECOND、WEEK、QUARTER(表示"季度",1 QUARTER = 3 MONTH 4 QUARTER = 1 YEAR) 等。

示例 - 增加日期:

sql 复制代码
SELECT DATE_ADD('2017-10-28', INTERVAL 10 DAY);
+-----------------------------------------+
| DATE_ADD('2017-10-28', INTERVAL 10 DAY) |
+-----------------------------------------+
| 2017-11-07                              |
+-----------------------------------------+

示例 - 减少日期:

sql 复制代码
SELECT DATE_SUB('2017-10-01', INTERVAL 2 DAY);
+---------------------------------------+
| DATE_SUB('2017-10-01', INTERVAL 2 DAY)|
+---------------------------------------+
| 2017-09-29                            |
+---------------------------------------+

灵活运用 :INTERVAL 后的单位可以非常精细,例如增加 3 小时 15 分钟?可以连续使用或使用复合单位(但标准写法是分别加减,或使用 INTERVAL '3:15' HOUR_MINUTE)。通常更推荐分步运算:

sql 复制代码
SELECT DATE_ADD(DATE_ADD('2026-08-31 10:00:00', INTERVAL 3 HOUR), INTERVAL 15 MINUTE);

另外,DATE_ADD 也可以接受负值来实现减法效果,但为了代码可读性,减法则优先使用 DATE_SUB。

1.4 计算日期差值:DATEDIFF()

统计两个日期之间相隔的天数 ,是会员活跃度分析、账龄计算等场景的常用操作。DATEDIFF() 函数直接返回天数差。

语法:

sql 复制代码
DATEDIFF(date1, date2)
  • 返回 date1 - date2 的天数(可正可负)。
  • 只比较日期部分,忽略时间部分 (即使传入 DATETIME,也仅取日期计算)。

示例:

sql 复制代码
SELECT DATEDIFF('2017-10-10', '2016-09-01');
+------------------------------------+
| DATEDIFF('2017-10-10', '2016-09-01') |
+------------------------------------+
| 404                                |
+------------------------------------+

若两个日期包含时间,差值仅依赖日期:

sql 复制代码
SELECT DATEDIFF('2026-08-31 23:59:59', '2026-08-30 00:00:01');
+--------------------------------------------------------+
| DATEDIFF('2026-08-31 23:59:59', '2026-08-30 00:00:01') |
+--------------------------------------------------------+
| 1                                                      |
+--------------------------------------------------------+

虽然实际间隔不足两天,但按日期算只差 1 天。

1.5 综合案例一:生日记录表

假设我们需要创建一个简单的表来记录用户的生日,并希望插入当天的日期。

建表语句:

sql 复制代码
CREATE TABLE tmp (
    id INT PRIMARY KEY AUTO_INCREMENT,
    birthday DATE
);

插入当前日期:

sql 复制代码
INSERT INTO tmp (birthday) VALUES (CURRENT_DATE());

查询结果:

sql 复制代码
SELECT * FROM tmp;
+----+------------+
| id | birthday   |
+----+------------+
| 1  | 2026-08-31 |
+----+------------+

陷阱警示 :如果尝试向 DATE 类型的字段插入 CURRENT_TIME(),会发生什么?

sql 复制代码
INSERT INTO tmp (birthday) VALUES (CURRENT_TIME());
-- 执行成功,但会产生警告(Warning)

由于 CURRENT_TIME() 返回的是 '14:39:20' 这样的时间字符串,而 MySQL 在将时间字符串隐式转为 DATE 类型时,无法解析出有效的年月日,因此会将其转换为 '0000-00-00'(或根据 SQL 模式可能报错)。这提醒我们:数据类型必须严格匹配,日期字段就应传入日期值或完整的日期时间值(MySQL 会自动截取日期部分,但时间部分必须合法)。如果传入纯时间,结果将是无效日期,切勿依赖这种隐式转换。

1.6 综合案例二:留言表与时间窗口查询

我们构建一个留言表,包含留言内容和发送时间,并在此基础上实现两种常见查询需求:仅显示日期不显示时间,以及查询最近 2 分钟内发布的留言。

建表与数据插入:

sql 复制代码
CREATE TABLE msg (
    id INT PRIMARY KEY AUTO_INCREMENT,
    content VARCHAR(30) NOT NULL,
    sendtime DATETIME
);

INSERT INTO msg (content, sendtime) VALUES ('hello1', NOW());
INSERT INTO msg (content, sendtime) VALUES ('hello2', NOW());

稍等片刻后再插入一条,以便后面观察时间窗口的效果。

需求一:显示所有留言信息,但发布日期只显示日期,不显示时间。

利用 DATE() 函数即可:

sql 复制代码
SELECT content, DATE(sendtime) AS send_date FROM msg;
+---------+------------+
| content | send_date  |
+---------+------------+
| hello1  | 2026-08-31 |
| hello2  | 2026-08-31 |
+---------+------------+

需求二:查询在 2 分钟内发布的帖子。

这里的核心思路是:取当前时间 NOW(),然后判断留言的发送时间是否在"当前时间减去 2 分钟"之后。使用 DATE_ADD() 对 sendtime 进行加 2 分钟运算,再与 NOW() 比较。

sql 复制代码
SELECT *
FROM msg
WHERE DATE_ADD(sendtime, INTERVAL 2 MINUTE) > NOW();

理解这条查询 :

对于每条留言,计算其 sendtime 加上 2 分钟后的时间点,如果这个时间点晚于当前时刻,说明该留言的发送时刻距离现在不足 2 分钟(因为 sendtime + 2分钟 > now() 等价于 sendtime > now() - 2分钟)。这种写法可读性强,直接体现了"未来 2 分钟边界"的概念。

也可以采用等价写法:

sql 复制代码
SELECT *
FROM msg
WHERE sendtime > DATE_SUB(NOW(), INTERVAL 2 MINUTE);

两种方式效果相同,根据个人习惯选择。

1.7 总结与最佳实践

本章详细梳理了 MySQL 日期函数的核心成员:

函数 核心用途
CURRENT_DATE() / CURRENT_TIME() / CURRENT_TIMESTAMP() 获取系统当前日期、时间、日期时间
DATE() 从日期时间中提取日期部分
DATE_ADD() / DATE_SUB() 对日期进行加减运算(支持年、月、日、时、分、秒等多种单位)
DATEDIFF() 计算两个日期之间的天数差(忽略时间)
NOW() 获取当前日期时间(与 CURRENT_TIMESTAMP() 同义)

关键原则:

  1. 类型一致 :向 DATE 字段插入值时应使用日期或日期时间类型,避免传入纯时间引发隐式转换问题。
  2. 时间窗口查询 :利用 DATE_ADD 或 DATE_SUB 构建动态时间边界,比硬编码时间更灵活。
  3. 显示与存储分离 :存储时使用完整的 DATETIME 可保留更多信息,显示时通过 DATE() 按需裁剪,满足不同场景需求。

第二章 字符串函数

如果说日期函数定义了数据的"时间刻度",那么字符串函数则赋予了数据库"文本手术刀"的能力。从用户输入的清洗、报表格式的组装,到数据迁移时的编码校验,字符串操作几乎渗透在每一个业务模块中。

MySQL 提供的字符串函数族极为丰富,但许多初学者容易陷入"逐个记忆函数名"的枯燥循环。本章将打破这种散点式学习,按照 "探测编码 → 计算长度 → 转换大小写 → 拼接组装 → 截取片段 → 查找替换 → 清理比较" 这条数据处理流水线,系统性地讲解所有核心字符串函数。同时,我们将深入剖析最易混淆的"字节 VS 字符"问题,彻底解开 UTF-8 变长编码的谜团。

2.1 字符集探测:CHARSET()

在拼接或处理字符串之前,首要任务是摸清数据的"出身"------字符集编码。不同编码下的存储规则和处理函数返回值可能存在天壤之别。

CHARSET(str) 函数用于返回字符串参数所使用的字符集名称。

语法:

sql 复制代码
CHARSET(str)

实战案例 :获取 EMP 表中员工姓名列 ename 的字符集。

sql 复制代码
SELECT ename, CHARSET(ename) FROM EMP;

该查询可能返回 utf8mb4、gbk 或 latin1 等结果。在多库多表关联查询时,若关联字段的字符集不一致,即便内容相同也无法匹配索引,甚至引发乱码。因此,CHARSET() 是数据迁移和异构系统对接时的"排障第一站"。

2.2 字节长度与字符长度:LENGTH() 的深度剖析

这是开发者最容易"翻车"的地方,也是面试中的高频考点。MySQL 提供了两个看似相近但本质不同的长度函数:

  • LENGTH(str):返回字符串所占用的字节数(Byte)。
  • CHAR_LENGTH(str):返回字符串的字符数(Character)(视觉上的文字个数)。

核心法则 :一个字符占多少字节,完全取决于字符集。

字符集 ASCII 字符(字母、数字、常见符号) 常用中文字符
latin1 1 字节 不支持
GBK 1 字节 2 字节
UTF-8 / utf8mb4 1 字节 3 字节(部分生僻字 4 字节)

深度解惑:为什么 '通信102' 占用 9 个字节?为什么 '102' 不是 9 个字节?

很多初学者存在一个根深蒂固的误区:"UTF-8 中每个字符固定占 3 个字节" 。这个认知是完全错误的。

事实是 :UTF-8 是一种变长编码(1~4 字节)。为了兼容 ASCII 码,它采用了一套精妙的规则:

  • ASCII 码范围(0~127) 内的字符,包括全部英文大小写字母(a-z、A-Z)、全部数字(0-9)以及常见标点符号,在 UTF-8 中仅占用 1 个字节。
  • 中文、日文、韩文(CJK)等非 ASCII 字符,在 UTF-8 中通常占用 3 个字节(部分扩展字符占用 4 字节)。

基于这个铁律,我们来拆解以下示例:

示例 1 :length('通信102')

  • '通':中文字符 → 占 3 字节
  • '信':中文字符 → 占 3 字节
  • '1':数字(ASCII)→ 占 1 字节
  • '0':数字(ASCII)→ 占 1 字节
  • '2':数字(ASCII)→ 占 1 字节
  • 总计 :3 + 3 + 1 + 1 + 1 = 9 字节

示例 2 :length('abc1你')

  • 'a'、'b'、'c'、'1':均为 ASCII 字符 → 各占 1 字节,合计 4 字节
  • '你':中文字符 → 占 3 字节
  • 总计 :4 + 3 = 7 字节
    所以,'102' 单独出现时,只是三个独立的 ASCII 数字,每个占 1 字节,总共 3 字节。如果按照"固定 3 字节"的谬误去算,得到 9 字节,那就把变长编码当成了 UCS-4 定长编码,属于概念性错误。

实战应用:查询学生姓名占用的字节数

sql 复制代码
SELECT name, LENGTH(name) AS byte_length, CHAR_LENGTH(name) AS char_length 
FROM student;

假设 name 为 '张三',在 UTF-8 下,byte_length 为 6,char_length 为 2。这提醒我们:在设计数据库字段长度(如 VARCHAR(20))时,20 指的是字符数,而非字节数,但 MySQL 底层实际分配的存储空间会按最大字节数计算(如 utf8mb4 下 VARCHAR(20) 最多可占 80 字节)。

2.3 大小写转换:UCASE() 与 LCASE()

报表展示或用户输入归一化时,大小写转换是家常便饭。

  • UCASE(str) 或 UPPER(str):将字符串全部转为大写。
  • LCASE(str) 或 LOWER(str):将字符串全部转为小写。

典型需求 :以首字母小写、其余字母保持不变的方式显示所有员工姓名。

实现思路:截取首字母 → 转小写 → 拼接上剩余部分。

sql 复制代码
SELECT CONCAT(LCASE(SUBSTRING(ename, 1, 1)), SUBSTRING(ename, 2)) AS formatted_name 
FROM EMP;

若 ename 为 'SMITH',则输出 'sMITH'。

2.4 字符串拼接:CONCAT()

CONCAT() 是构建动态报表语句的灵魂工具,支持连接两个或多个字符串参数。

语法:

sql 复制代码
CONCAT(str1, str2, ..., strN)

关键注意 :只要其中任何一个参数为 NULL,整个返回值即为 NULL。若存在 NULL 风险,可结合 IFNULL() 使用。

实战案例 :显示 exam_result 表中的信息,格式为"XXX的语文是XXX分,数学XXX分,英语XXX分"。

sql 复制代码
SELECT CONCAT(name, '的语文是', chinese, '分,数学是', math, '分,英语是', english, '分') AS 成绩单 
FROM exam_result;

2.5 字符串截取:LEFT()、RIGHT() 与 SUBSTRING()

提取身份证号中的出生日期、截取邮箱前缀、处理固定格式编码,都离不开截取函数。

2.5.1 从两端定向截取:LEFT() 与 RIGHT()

  • LEFT(str, length):从左边 起取 length 个字符。
  • RIGHT(str, length):从右边 起取 length 个字符。

2.5.2 灵活定位截取:SUBSTRING()

  • SUBSTRING(str, position):从 position 开始截取到末尾。
  • SUBSTRING(str, position, length):从 position 开始截取 length 个字符。

索引规则 :MySQL 中字符串位置从 1 开始计数,而非 0。

实战案例 :截取 EMP 表中 ename 字段的第二个到第三个字符。

sql 复制代码
SELECT ename, SUBSTRING(ename, 2, 2) AS extracted FROM EMP;

若 ename 为 'JONES',则提取结果为 'ON'(位置 2 是 'O',取 2 个字符得到 'ON')。

2.6 查找与替换:INSTR() 与 REPLACE()

在数据脱敏、敏感词过滤或业务标识更新时,查找和替换是最直接的手段。

2.6.1 查找首次出现位置:INSTR()

INSTR(str, substr) 返回 substr 在 str 中首次出现的位置索引。若找不到则返回 0。

sql 复制代码
SELECT INSTR('Hello World', 'World'); -- 返回 7
SELECT INSTR('Hello', 'x');           -- 返回 0

2.6.2 全局替换:REPLACE()

REPLACE(str, search_str, replace_str) 将 str 中所有 出现的 search_str 替换为 replace_str(该操作默认区分大小写)。

实战案例 :将 EMP 表中所有名字里包含大写 S 的替换为 '上海'。

sql 复制代码
SELECT ename, REPLACE(ename, 'S', '上海') AS replaced_name FROM EMP;

若 ename 为 'SMITH',则结果为 '上海MITH'。该操作仅影响查询结果,不会修改表中原始数据(如需持久化,需配合 UPDATE 语句)。

2.7 字符串逐字比较:STRCMP()

STRCMP(str1, str2) 基于底层字符集的二进制排序规则,逐字符比较两个字符串的大小。

  • 若 str1 等于 str2,返回 0。
  • 若 str1 小于 str2(字典序靠前),返回 -1。
  • 若 str1 大于 str2,返回 1。
sql 复制代码
SELECT STRCMP('A', 'B');   -- 返回 -1
SELECT STRCMP('B', 'A');   -- 返回 1
SELECT STRCMP('AB', 'AB'); -- 返回 0

该函数多用于 ORDER BY 自定义排序逻辑或 CASE WHEN 复杂条件分支中。

2.8 空白字符清理:LTRIM()、RTRIM() 与 TRIM()

用户输入的表单数据常常包含无意识的前后空格,直接存储会导致查询匹配失败(如 WHERE name = 'admin ' 无法匹配 'admin')。

  • LTRIM(str):去除字符串左侧(开头)的空格。
  • RTRIM(str):去除字符串右侧(末尾)的空格。
  • TRIM(str): 用于去除字符串首尾指定字符 ,完整语法为 TRIM([BOTH | LEADING | TRAILING] '字符' FROM 字符串),其中 BOTH 表示去除两端、LEADING 表示只去左侧、TRAILING 表示只去右侧;如果直接写 TRIM(字符串),则默认去除字符串首尾的空格。
sql 复制代码
SELECT LTRIM('   Hello');   -- 返回 'Hello'
SELECT RTRIM('World   ');   -- 返回 'World'
SELECT TRIM('   MySQL   '); -- 返回 'MySQL'

2.9 综合实战:一个需求贯穿所有函数

假设有一张 EMP 员工表(字符集为 utf8mb4),包含 ename(姓名)和 job(职位)字段。现需完成以下数据清洗与报表输出:

  1. 探测 ename 列的字符集。
  2. 查询每个员工姓名占用的字节数和字符数。
  3. 将职位全部转为小写。
  4. 将姓名格式化为"首字母大写,其余字母小写"。
  5. 过滤出姓名中包含字母 'A' 的员工,并将所有 'A' 替换为 '优'。
  6. 去除姓名中可能存在的两侧空格。
  7. 最终拼接生成一句话:"员工 姓名 的职位是 职位"。

完整 SQL 实现:

sql 复制代码
SELECT 
    CHARSET(ename) AS charset_used,
    ename AS original_name,
    LENGTH(ename) AS bytes_occupied,
    CHAR_LENGTH(ename) AS char_count,
    CONCAT(
        UCASE(SUBSTRING(TRIM(ename), 1, 1)), 
        LCASE(SUBSTRING(TRIM(ename), 2))
    ) AS formatted_name,
    LCASE(job) AS job_lower,
    REPLACE(ename, 'A', '优') AS replaced_name,
    CONCAT('员工 ', TRIM(ename), ' 的职位是 ', LCASE(job)) AS introduction
FROM EMP
WHERE INSTR(ename, 'A') > 0;

第三章 数学函数

在数据库查询中,数值处理的需求同样频繁:计算折扣后的价格、统计平均分、生成随机抽样数据、格式化报表输出等。MySQL 提供了一套完整的数学函数,涵盖绝对值、取整、进制转换、随机数生成等常见运算。

与日期、字符串函数不同,数学函数通常具有确定性的运算逻辑(除 RAND() 外),且不受字符集影响。本章将按照"基础运算 → 取整与格式化 → 随机与取模 → 进制转换"的脉络,系统讲解所有核心数学函数。特别地,我们将深入探讨进制转换函数 BIN()、HEX()、CONV() 在处理浮点数时的底层机制,帮助开发者避开常见的"精度陷阱"。

3.1 绝对值函数:ABS()

ABS(number) 返回数值的绝对值,即去掉符号后的非负值。 无论输入正数、负数还是零,结果均为其距离零点的距离。

语法:

sql 复制代码
ABS(number)

示例:

sql 复制代码
SELECT ABS(-100.2);   -- 返回 100.2
SELECT ABS(100.2);    -- 返回 100.2
SELECT ABS(0);        -- 返回 0

该函数常用于消除负偏差,例如计算价格波动幅度或误差距离。

3.2 取整函数:CEILING() 与 FLOOR()

在分页计算、库存单位换算或区间统计时,取整操作不可或缺。MySQL 提供了方向明确的两种取整方式:

  • CEILING(number) :向上取整,返回不小于参数的最小整数值(向正无穷方向取整)。
  • FLOOR(number):向下取整,返回不大于参数的最大整数值(向负无穷方向取整)。

示例:

sql 复制代码
SELECT CEILING(23.04);   -- 返回 24(大于 23.04 的最小整数)
SELECT CEILING(-23.04);  -- 返回 -23(大于 -23.04 的最小整数,即 -23)

SELECT FLOOR(23.7);      -- 返回 23(小于 23.7 的最大整数)
SELECT FLOOR(-23.7);     -- 返回 -24(小于 -23.7 的最大整数,即 -24)

需要注意:CEILING 与 FLOOR 对于负数的处理方向相反,开发者需根据业务语义(如计算租金天数、计费周期)谨慎选择。

3.3 格式化小数:FORMAT()

FORMAT(number, decimal_places) 将数字格式化为带有千位分隔符(逗号)的字符串,并四舍五入保留指定位数的小数。该函数返回的是字符串类型,而非数值类型,因此仅适用于展示层,不宜用于后续数学运算。

语法:

sql 复制代码
FORMAT(number, decimal_places)

示例:

sql 复制代码
SELECT FORMAT(12345.6789, 2);   -- 返回 '12,345.68'(四舍五入保留两位)
SELECT FORMAT(12.3456, 2);      -- 返回 '12.35'
SELECT FORMAT(12.3, 2);         -- 返回 '12.30'(不足位补零)

3.4 随机数生成:RAND()

RAND() 返回一个随机浮点数,范围是 [0.0, 1.0) ------即包含 0,但不包含 1。 该函数在数据抽样、随机排序、生成测试数据等场景中极为实用。

基本用法:

sql 复制代码
SELECT RAND();     -- 返回类似 0.4234234234 的随机值
SELECT RAND();     -- 每次执行结果均不同

进阶用法 :若需生成指定范围的随机整数(如 [min, max]),可结合 FLOOR 运算:

sql 复制代码
SELECT FLOOR(RAND() * 100) + 1;   -- 生成 1 到 100 之间的随机整数

带种子的随机数 :RAND(seed) 支持传入整数种子。若种子固定,则随机序列完全可复现, 这在需要保证测试结果稳定性的场景下非常有用:

sql 复制代码
SELECT RAND(100);   -- 每次执行该语句返回相同值
SELECT RAND(100);   -- 与上行结果一致

3.5 取模运算:MOD()

取余和取模在正数情况下结果相同,区别主要在负数: 取余看被除数符号,取模看除数符号。 要看具体语言,% 不一定都是同一种规则。在 C、C++、Java、JavaScript、MySQL 里,% 通常按取余规则处理:结果符号跟被除数一致。 例如 -5 % 3 = -2。

在大多数编程语言里:

/ 叫除法运算符 ,用于做除法;% 叫取余运算符(remainder operator),用于求两个数相除后的余数。

例如在 C/C++ 中:

cpp 复制代码
7 / 3   // 除法,结果为 2(整数除法)
7 % 3   // 取余,结果为 1

所以代码里一般说:/ 是除法运算符,% 是取余运算符。 "取模"更多是数学上的叫法,编程里 % 经常也被俗称为取模运算符。

MOD(number, denominator) 返回两个数相除的余数(取模运算)。其等价写法为 number % denominator。

语法:

sql 复制代码
MOD(number, denominator)

示例:

sql 复制代码
SELECT MOD(10, 3);    -- 返回 1
SELECT MOD(10.5, 3);  -- 返回 1.5(支持浮点数取模)
SELECT MOD(10, 0);    -- 返回 NULL(除数为零不合法)

取模运算常用于判断奇偶性(MOD(id, 2) = 0 为偶数)、分库分表路由键计算,以及循环轮询逻辑中。

3.6 进制转换详解:BIN()、HEX() 与 CONV()

进制转换在 IP 地址存储、颜色值解析或底层协议处理中经常出现。MySQL 提供了三种相关的进制操作函数,但它们在底层存在一个极易被忽略的关键限制。

3.6.1 BIN():十进制转二进制

BIN(decimal_number) 将十进制数值转换为二进制字符串表示。

3.6.2 HEX():十进制转十六进制

HEX(decimal_number) 将十进制数值转换为十六进制字符串表示。

注:HEX() 具有"双重身份"------若参数为数字,则转换数字本身;若参数为字符串,则返回该字符串每个字符的十六进制编码。 此特性需格外注意。

3.6.3 CONV():通用进制转换

CONV(number, from_base, to_base) 是功能最强大的进制转换函数,可以将一个数从任意进制(2~36)转换为任意另一进制,返回值的字符串表示。

示例:

sql 复制代码
SELECT BIN(10);                       -- 返回 '1010'
SELECT HEX(255);                      -- 返回 'FF'
SELECT CONV('1010', 2, 10);           -- 返回 '10'(二进制转十进制)
SELECT CONV('FF', 16, 10);            -- 返回 '255'(十六进制转十进制)
SELECT CONV(10, 10, 2);               -- 返回 '1010'(十进制转二进制)

3.6.4 关键限制:浮点数会被截断为整数

核心问题 :BIN()、HEX()(数字模式)以及 CONV() 在处理参数时,底层均将输入解释为 整数(BIGINT) 类型。

  • 如果传入浮点数 (如 10.9),MySQL 会隐式截断小数部分 ,仅保留整数部分(10)进行进制转换。不会对小数部分做任何转换。
  • 这意味着:BIN(10.9) 实际上执行的是 BIN(10),返回 '1010',小数 .9 被完全舍弃且无任何警告(取决于 SQL 模式)。

验证示例:

sql 复制代码
SELECT BIN(10.9);      -- 实际返回 '1010',而非小数部分的二进制表示
SELECT HEX(15.99);     -- 返回 'F'(仅处理整数 15)
SELECT CONV(8.7, 10, 2); -- 返回 '1000'(仅处理整数 8)

因此,若需要对浮点数的小数部分进行精确的二进制或十六进制转换,MySQL 的原生函数无法直接做到。标准做法是:将整数部分与小数部分拆开分别处理,或在前端应用程序中完成此类精细转换。在数据库层面,应明确这些函数的整数特性,避免因隐式截断导致数据失真。

另外,CONV() 的 from_base 和 to_base 参数范围是 2 至 36(含),支持数字 0-9 及字母 A-Z 作为进制符号,满足了绝大部分业务对非十进制数据的处理需求。

3.7 综合案例:统计报表中的数值处理

假设有一张 product 商品表,包含 price(价格,浮点数)、sales(销量)字段。现需生成一份统计报表,要求:

  1. 计算每件商品的总销售额(价格 × 销量)。
  2. 对总销售额向上取整(确保计费不遗漏分毫)。
  3. 将结果格式化为保留两位小数的货币显示形式(带千分位分隔符)。
  4. 随机抽取 3 条商品数据进行核对验证。

SQL 实现:

sql 复制代码
SELECT 
    product_name,
    price,
    sales,
    CEILING(price * sales) AS ceiling_total,
    FLOOR(price * sales) AS floor_total,
    FORMAT(price * sales, 2) AS formatted_total,
    MOD(sales, 2) AS sales_parity  -- 1 为奇数,0 为偶数
FROM product
ORDER BY RAND()   -- 随机排序
LIMIT 3;

第四章 其他函数

在系统掌握了日期时间、字符串和数学运算之后,MySQL 工具箱中还剩下一组"多功能辅助函数"。它们虽然不像前三类那样自成体系,却在用户认证、环境探测、空值防御等关键业务场景中扮演着不可替代的角色。本章将重点讲解用户与数据库信息查询、空值处理、以及两种核心数据摘要加密函数,并结合实际的用户登录验证案例,剖析 MD5 与 PASSWORD 函数的底层特性及使用陷阱。

4.1 会话与环境信息:USER() 与 DATABASE()

在编写复杂脚本或调试多租户系统时,明确当前数据库连接的身份与操作环境是第一步。

4.1.1 查询当前用户:USER()

USER() 函数返回当前 MySQL 会话所使用的用户名和主机名组合,格式为 'username'@'hostname'。 这有助于在多用户环境下确认操作者身份。

示例:

sql 复制代码
SELECT USER();
+----------------+
| USER()         |
+----------------+
| 'root'@'localhost' |
+----------------+

4.1.2 查询当前数据库:DATABASE()

DATABASE() 函数返回当前 USE 语句所指定的默认数据库名称。若当前未选择任何数据库,则返回 NULL。 在编写通用脚本时,常用该函数进行前置校验,避免误操作非目标库。

示例:

sql 复制代码
SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| test_db    |
+------------+

4.2 空值处理保险丝:IFNULL()

在查询计算中,NULL 值具有传染性------任何与 NULL 进行的算术或拼接操作往往直接返回 NULL。IFNULL(val1, val2) 是处理这一问题的"第一道防线":当 val1 为 NULL 时返回 val2,否则返回 val1 本身。

语法:

sql 复制代码
IFNULL(expression, replacement_value)

示例:

sql 复制代码
SELECT IFNULL('abc', '123');   -- val1 非空,返回 'abc'
+----------------------+
| IFNULL('abc', '123') |
+----------------------+
| abc                  |
+----------------------+

SELECT IFNULL(NULL, '123');    -- val1 为空,返回 '123'
+---------------------+
| IFNULL(NULL, '123') |
+---------------------+
| 123                 |
+---------------------+

在统计平均分、计算折扣价时,若字段允许为空,建议使用 IFNULL(column, 0) 将空值转为 0,以避免整条计算结果变成 NULL。

4.3 数据安全摘要:MD5() 与 PASSWORD()

在用户密码存储、数据完整性校验等场景中,明文存储是绝对的高危操作。MySQL 提供了两种常用的摘要(Digest)函数,它们将原始字符串通过特定算法映射为固定长度的"指纹"字符串 。核心特征 :输入任意长度,输出固定长度,且不可逆向推导(单向加密)。

4.3.1 MD5 信息摘要算法

MD5(str) 返回字符串的 32 位十六进制 MD5 摘要值。 无论输入的是 1 个字符还是 1MB 的文本,输出永远是 32 个字符的等长字符串(由数字 0-9 和字母 a-f 组成)。

典型场景 :用户注册时,系统不存储明文密码 'hellobit',而是存储其 MD5 值 '242346a1a42308eacd2f7319fcde8da7'(32位)。在登录验证时,将用户输入的明文再次计算 MD5,与数据库存储的摘要值进行比对。

核心实战演示:

假设存在一张 user 用户表,其中 password 字段定义为 CHAR(32),恰好匹配 MD5 的固定长度。

sql 复制代码
-- 插入两条记录:张三明文存储(绝不推荐),李四摘要存储
INSERT INTO user (name, password) VALUES ('张三', 'abcd1234');
INSERT INTO user (name, password) VALUES ('李四', MD5('hellobit'));

验证查询陷阱 :

若直接使用明文比对,将无法匹配摘要存储的记录:

sql 复制代码
-- 错误写法:拿明文去匹配摘要值
SELECT name FROM user WHERE name='李四' AND password='hellobit';
-- 返回 Empty set(无结果)

正确做法:查询时对用户输入的明文动态取摘要:

sql 复制代码
-- 正确写法:输入明文同样经过 MD5 转换后再比对
SELECT name FROM user WHERE name='李四' AND password=MD5('hellobit');
+--------+
| name   |
+--------+
| 李四   |
+--------+

这揭示了 MD5 使用的铁律:存储时用 MD5(原文),查询比对时也必须用 MD5(用户输入),两端保持算法一致。

4.3.2 PASSWORD() 系统加密函数

PASSWORD() 是 MySQL 系统内部用于 mysql.user 权限表中密码字段加密的函数 (注意:MySQL 8.0 起已被废弃,但在老旧系统维护或历史数据迁移时依然常见)。与 MD5 不同,它返回的固定长度为 41 个字符 (格式为 * 加上 40 位十六进制大写字符)。

示例:

sql 复制代码
SELECT PASSWORD('1223');
+-------------------------------------------+
| PASSWORD('1223')                          |
+-------------------------------------------+
| *0D7F7F31D85EF4BCE2C4BB738B9B3319F6D68B0F |
+-------------------------------------------+

观察结果可知,无论输入 '1223' 还是其他任意长度的字符串,输出始终保持 41 位的等长结构。

重要警示 :PASSWORD() 的加密算法与 MD5() 不同,且与 MySQL 版本强相关 (早期版本使用 PASSWORD,后期改用 caching_sha2_password)。在现代应用开发中,严禁将 PASSWORD() 用于业务表(如用户登录表)的密码加密,它仅服务于 MySQL 自身的权限体系。业务端应统一使用 MD5()、SHA1() 或更高级的 SHA2() 配合盐值(Salt)。

4.4 关键字冲突与命令行历史记录

在实际编写 SQL 涉及密码字段操作时,开发者可能会注意到一个现象:在 MySQL 命令行客户端中,当输入包含 PASSWORD(或 PASSWORD())关键字的语句后,若按上下方向键翻找历史命令,有时无法找到之前刚执行的那条插入或查询语句。

原因分析 :

PASSWORD 属于 MySQL 的保留关键字(Reserved Keyword)。在命令行交互环境中,客户端的词法解析器在处理历史缓存时,若遇到存在歧义或特殊语义的关键字,可能中断对该条完整 SQL 的记录,或者因关键字的敏感状态导致上下键检索失效(表现为按下后无响应或跳过该条记录)。

最佳规避方案 :

虽然不直接修改数据库行为,但在编写涉及加密函数或字段名与关键字冲突的 SQL 时,养成以下习惯可避免此类交互困扰:

  • 若列名恰巧命名为了 password 或 user,务必使用反引号(`````)将列名包裹,例如:SELECT `password` FROM user WHERE `password` = MD5('xxx')。
  • 对于函数调用(如 SELECT PASSWORD('123')),因为函数名大写且带括号,通常不被视为普通列名,但鉴于历史记录的兼容性问题,建议在命令行中采用复制粘贴执行替代上下键翻找,或使用 \s 查看状态后执行 source 脚本。

特别说明:此现象源于客户端交互层的词法缓存机制,不影响 SQL 在存储过程、触发器或应用程序驱动中的正常执行。

相关推荐
周小董1 小时前
[1369]Windows上安装PostgreSQL
数据库·windows·postgresql
vilya2 小时前
手机端 Agent 怎么动起来:Kotlin 与 Python 之间的桥
android·agent
高晶2 小时前
Android 新手实战:从零搭建 WebRTC 在线直播系统
android·程序员
alexhilton2 小时前
为什么你的 LazyColumn 仍然卡顿
android·kotlin·android jetpack
>Andre<3 小时前
系统架构全景
android
一只积极向上的小咸鱼3 小时前
第一性原理思考:从拆解问题到用实验检验答案
数据库
这个DBA有点耶4 小时前
时序数据库InfluxDB高基数问题深度拆解:内存索引膨胀机制与OOM完整链路
数据库·架构·dba
SL_staff4 小时前
运维救火实录:钉钉考勤API变更引发工资计算异常,我们用JVS-Logic可视化逻辑引擎实现分钟级热修复
java·mysql·全栈
加油yx4 小时前
MySQL数据类型
数据库·mysql