length 是这一章最大的坑:这里数字符,length('张伟') 给 2;
MySQL 数字节,同一个给 6(一个汉字 3 字节)。 要数字符得用 char_length。
十一个函数:length、trim、reverse、repeat、replace、substring、left/right、
concat、lower/upper、lpad/rpad。
concat 有个雷:只要有一个参数是 null,整个结果就是 null。
concat_ws 会跳过 null,拼地址拼日期用它更安全。
上一类是数字,这一类是文本——也是实际工作里用得最多的一类。 字符串函数有两个反复出现的坑:「长度」到底数什么,以及「拼上 null 会怎样」。 这两个坑都不会报错,只会给你一个「看着差不多」的错结果。
一、length:这里数字符,MySQL 数字节
length('abc') 给 3,length('张伟') 也给 2。 这里(SQLite)数的是「字符」。
MySQL 的 length 数的是「字节」:abc 还是 3,
但 张伟 给 6——utf8mb4 下一个汉字占 3 字节。 同一个函数名,两边意思不同。
要数字符,MySQL 得写 char_length(这里也给 2)。
校验「昵称不超过 10 个字」这种需求,写 length 在 MySQL 上会误判成 3 个汉字就超了。
二、trim、repeat:修剪与重复
trim(' ab ') 给 ab——去掉两头空格,中间的不动。 原始长度 4,trim 之后 2。
它只能去空格(MySQL 还能指定字符,写 TRIM(BOTH 'x' FROM s))。
去不掉全角空格、制表符这类看不见的东西,那种得用 replace 硬换。
repeat('ab', 3) 给 ababab。 做分隔线、拼占位符时会用,业务里不多。
三、lpad 与 rpad:补齐到指定长度
lpad('7', 3, '0') 给 007——左边补零到 3 位,做流水号最常用。
rpad('ab', 5, '*') 给 ab 右边补三个星号。
第二个参数是「补完后的总长度」,不是「补几个」——补到 3 位就给 3 位,不是补 3 个。
这个参数最容易理解错。
如果原串已经比目标长度长,lpad 会截断,不是原样返回。
这一条在 MySQL 和这里都一样,用的时候留意。
四、substring、left、right:三种取片段
substring(str, 2, 3) 是「从第 2 个字符起,取 3 个」,给 bcd。
起始位置从 1 开始,不是从 0——写惯编程语言的人第一下就会错。
负数从尾部数:substring('abcdef', -3) 给 def。
left(s, n) 是「取前 n 个」,right(s, n) 是「取后 n 个」。
取中间某一段(比如从身份证里取生日)就靠 substring:substring(id_card, 7, 8)。
这一招在数据清洗里天天用。
五、reverse、replace、lower、upper
reverse('abcdef') 给 fedcba,
reverse('张伟') 给「伟张」——按字符反转,不会把汉字切坏。
replace 是「全替换」,replace('a-b-c', '-', '+') 给 a+b+c,
三处都换了。 只换第一处得自己拼 substring。
lower/upper 只对英文字母有用,
对汉字没影响。 做「不区分大小写的比较」时常配着用:lower(name) = 'abc'。
六、concat 与 concat_ws:拼接,以及 null 那个雷
concat('张', '伟') 给「张伟」。
但 concat('张', NULL) 给 NULL。 只要有一个参数是 null,整个结果就是 null——
不跳过、不当空串,直接全变 null。
这是 concat 最大的雷:拼地址时某个字段没填,整条地址变成 NULL,报表上就是一片空白,
而且不报错。
concat_ws 会跳过 null:concat_ws('-', 'a', NULL, 'b') 给 a-b。
拼地址、拼日期这类活儿,一律用它。 真要用 concat,得先 ifnull(字段, '')。
七、动手 Demo:十一个函数跑一遍,两个坑各撞一次
Demo 六段:长度、修剪与重复、补齐、取片段、反转与替换、拼接。
第一段的 length 和最后一段的 concat 是这一篇的两个重点,
其余四个是查手册就能用的常规操作。
import _q46 as Q
c = Q.con()
# length:这里数字符,MySQL 数字节
c.execute("SELECT LENGTH('abc') AS en, LENGTH('张伟') AS cn, "
"CHAR_LENGTH('张伟') AS cn2")
# trim 与 repeat
c.execute("SELECT TRIM(' ab ') AS t, LENGTH(' ab ') AS before, "
"LENGTH(TRIM(' ab ')) AS after, REPEAT('ab', 3) AS r")
# lpad 与 rpad
c.execute("SELECT LPAD('7', 3, '0') AS l, RPAD('ab', 5, '*') AS p")
# 取片段:substring、left、right
c.execute("SELECT SUBSTRING('abcdef', 2, 3) AS s1, "
"SUBSTRING('abcdef', -3) AS s2")
c.execute("SELECT LEFT('abcdef', 3) AS l, RIGHT('abcdef', 3) AS r")
# reverse、replace、lower、upper
c.execute("SELECT REVERSE('abcdef') AS rev, "
"REPLACE('a-b-c', '-', '+') AS rep, "
"LOWER('ABC') AS lo, UPPER('abc') AS up")
# concat 与 concat_ws:null 那个雷
c.execute("SELECT CONCAT('张', '伟') AS a, CONCAT('张', NULL) AS b, "
"CONCAT_WS('-', '2026', '10', '30') AS c")
实跑输出:
--------------------------------------------------------------------
第二十五课:字符串函数,第一个坑就在 length
--------------------------------------------------------------------
length:这里数字符,MySQL 数字节
SQL> SELECT LENGTH('abc') AS en, LENGTH('张伟') AS cn, CHAR_LENGTH('张伟') AS cn2
en cn cn2
------------------
3 2 2
(1 行)
**`length('abc')` 给 3,`length('张伟')` 也给 2。** **这里(SQLite)`length` 数的是「字符」。**
**MySQL 的 `length` 数的是「字节」**:`abc` 还是 3,
**但 `张伟` 给 6**——一个汉字在 utf8mb4 里占 3 字节。
**所以要数字符,MySQL 得用 `char_length`**(这里也给 2)。
**同一个函数名,两边意思不同,这是最容易在迁移时踩空的一处。**
trim、repeat、lpad、rpad:修剪与填充
SQL> SELECT TRIM(' ab ') AS t, LENGTH(' ab ') AS before, LENGTH(TRIM(' ab ')) AS after,
REPEAT('ab', 3) AS r
t before after r
-----------------------------------
ab 4 2 ababab
(1 行)
**`trim` 去掉两头空格**(中间不动):原本长度 4,trim 之后 2。 **`repeat` 把字符串重复 n 遍。**
SQL> SELECT LPAD('7', 3, '0') AS l, RPAD('ab', 5, '*') AS p
l p
--------------
007 ab***
(1 行)
**`lpad('7', 3, '0')` 给 `007`**——补零做流水号最常用;
`rpad('ab', 5, '*')` 给 `ab***`。 **`l` 是左补、`r` 是右补。**
**注意第二个参数是「补完后的总长度」,不是「补几个」**——补到 3 位就给 3 位,不是补 3 个。
substring、left、right:三种取片段
SQL> SELECT SUBSTRING('abcdef', 2, 3) AS s1, SUBSTRING('abcdef', -3) AS s2
s1 s2
------------
bcd def
(1 行)
SQL> SELECT LEFT('abcdef', 3) AS l, RIGHT('abcdef', 3) AS r
l r
------------
abc def
(1 行)
**`substring(str, 2, 3)` 是「从第 2 个字符起,取 3 个」,给 `bcd`。**
**注意起始位置从 1 开始,不是从 0。**
**负数从尾部数**:`-3` 给最后三个 `def`。 **`left`/`right` 是「取前 n 个/取后 n 个」的简写。**
reverse、replace、lower、upper
SQL> SELECT REVERSE('abcdef') AS rev, REPLACE('a-b-c', '-', '+') AS rep, LOWER('ABC') AS lo,
UPPER('abc') AS up
rev rep lo up
---------------------------------
fedcba a+b+c abc ABC
(1 行)
**`reverse('张伟')` 给「伟张」**——按字符反转,不是按字节,
**这一点在这里是对的,MySQL 拿 utf8 字符反转也不会切坏汉字。**
**`replace` 是「全替换」,不是只换第一处。** **要只换第一处得自己拼 `substring`。**
concat 与 concat_ws:拼接,以及 null 那个雷
SQL> SELECT CONCAT('张', '伟') AS a, CONCAT('张', NULL) AS b, CONCAT_WS('-', '2026', '10',
'30') AS c
a b c
-----------------------------
张伟 NULL 2026-10-30
(1 行)
**`concat('张', '伟')` 给「张伟」,中间不用加空格。**
**第二列给 NULL——这就是 `concat` 最大的雷:只要有一个参数是 null,整个结果就是 null。**
**不会跳过、也不会当空字符串,直接全变 null。**
**`concat_ws` 是「带分隔符」的版本,它会跳过 null**,
**所以 `concat_ws('-', 'a', NULL, 'b')` 给 `a-b`。**
**拼地址、拼日期这类活儿,`concat_ws` 比 `concat` 安全。**
第一段长度。 abc 给 3、张伟 给 2——这里数字符。 MySQL 上同一个 张伟 给 6。
第二段修剪。 trim 之后长度从 4 变 2,repeat 把 ab 重复成 6 个字符。
第三段补齐。 007 与「ab 加三个星号」——第二个参数是总长度。
第四段取片段。 bcd、def,再 abc、def——一个从中间取,一个从头尾取。
第五段反转与替换。 fedcba、a+b+c、abc、ABC。
第六段拼接。 头两列是「张伟」和 NULL——
concat 碰上 null 就全变 null。 concat_ws 那句给 2026-10-30,中间的 null 会被跳过。
30 秒实验
LENGTH('张伟')与CHAR_LENGTH('张伟')在这里都是 2,在 MySQL 上分别是多少?CONCAT('a', NULL, 'b')与CONCAT_WS('-', 'a', NULL, 'b')分别给什么?LPAD('12345', 3, '0')会给什么?和你想的一样吗?
常见误区
- 用
length卡「不超过 N 个字」。 MySQL 数的是字节,一个汉字算 3, 10 个汉字就被判成 30 超长。该用char_length。 - 用
concat拼可能为空的字段。 一个字段是 null,整条结果变 null,而且不报错。 用concat_ws或套ifnull。 - 以为
substring从 0 开始。 它从 1 开始,写substring(s, 0, 3)在 MySQL 上给空串。
自测三问(点开对答案)
1. `length('张伟')` 在本沙箱和 MySQL 分别给什么?为什么? —— 这里给 2(数字符),MySQL 给 6(数字节,一个汉字 3 字节)。要数字符用 `char_length`。 2. `concat` 碰上 null 会怎样? —— 整个结果变成 NULL,不跳过也不当空串。要用 `concat_ws` 或先 `ifnull` 兜底。 3. `substring('abcdef', 2, 3)` 给什么?起始位置从几开始? —— 给 `bcd`,从第 2 个字符起取 3 个。起始位置从 1 开始,不是 0。文本过完了,下一篇轮到时间——那里有一件事比函数本身更重要:「今天是哪天」到底谁说了算。
- ☐ (本篇完,下一篇 6.4 时间函数)