🦏 第6章 内置函数
📄 一句话概括

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 秒实验

  1. LENGTH('张伟') 与 CHAR_LENGTH('张伟') 在这里都是 2,在 MySQL 上分别是多少?
  2. CONCAT('a', NULL, 'b') 与 CONCAT_WS('-', 'a', NULL, 'b') 分别给什么?
  3. LPAD('12345', 3, '0') 会给什么?和你想的一样吗?

常见误区

  1. 用 length 卡「不超过 N 个字」。 MySQL 数的是字节,一个汉字算 3, 10 个汉字就被判成 30 超长。该用 char_length。
  2. 用 concat 拼可能为空的字段。 一个字段是 null,整条结果变 null,而且不报错。 用 concat_ws 或套 ifnull。
  3. 以为 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。

文本过完了,下一篇轮到时间——那里有一件事比函数本身更重要:「今天是哪天」到底谁说了算。

相关笔记