like 是「长得像」而不是「等于」,靠两个通配符干活:
% 顶任意长度(包括零个),_ 顶恰好一个字符。
Demo 里 LIKE '%球%' 中 4 件、LIKE '%拍' 中 2 件;
LIKE '_球拍' 给 0 行,LIKE '__球拍' 给 2 行——
四个字的「羽毛球拍」,一个下划线顶不住。
要搜百分号、下划线本身,得用 ESCAPE 声明转义符:
LIKE '%\%%' ESCAPE '\' 才找得到 50%。
LIKE '%张%' 用不上索引,前缀不确定就只能全表扫。
前一章的子句都是「精确条件」:=、>、in。
这一篇开始「像不像」,也是搜索框背后最常见的那一句。
like 本身不难,难在两处:_ 到底顶几个字,以及搜「百分号」的时候为什么搜不到。
一、百分号:任意长度,包括零个字符
LIKE '%球%' 是「中间有球」,% 能顶零个、一个、很多个字符。 Demo 里四件全中:篮球、足球、
羽毛球拍、乒乓球拍。
% 放后面就是「以某字开头」:LIKE '张%' 找姓张的;
放前面是「以某字结尾」:LIKE '%拍' 找两个拍的。
not like 是取反,但要留意 null:like 碰上 null 给的是「未知」,不是「假」,
所以 not like 也筛不出 null 的那些行。
二、下划线:只顶一个字符
_ 顶恰好一个字符,多一个少一个都不匹配。
LIKE '_球' 是「两个字,第二个是球」,篮球、足球中,羽毛球拍不中。
这个差别用一句话验最快:LIKE '_球拍' 给 0 行,LIKE '__球拍' 给 2 行。
因为「羽毛球拍」是四个字,得两个下划线。
实用场景:_ 常用来卡「长度固定」的编码,比如订单号 DD___________(13 位)。
但更清楚的写法是把长度写进约束,别靠 _ 数。
三、转义:要搜通配符本身
问题:想找「名字里含百分号」的记录,
LIKE '%%%' 不行——% 全被当通配符了,等于「什么都匹配」。
办法是声明一个转义符:LIKE '%\%%' ESCAPE '\'。 \% 就表示「字面的百分号」,\_ 同理。
Demo 里前者找出 50%,后者找出 a_b。
这里有一处两边不一样:MySQL 的默认转义符就是反斜杠,不写 ESCAPE 也能过;
SQLite 没有默认转义符,不写就匹配不到。 跨库的 SQL 一律显式写 ESCAPE。
四、大小写:答案取决于排序规则
SELECT 'abc' LIKE 'ABC' 在 Demo 里给 1,
不区分大小写。 MySQL 默认的 utf8mb4_0900_ai_ci 也不区分——
名字末尾的 ci 就是 case insensitive。
换成 _bin 或 _cs 结尾的排序规则就区分了。
所以「like 区分大小写吗」这个问题问得不完整,该问「这个列用的是哪个排序规则」。
真要强制不区分(或强制区分),可以在比较时临时转:WHERE LOWER(name) LIKE 'abc%'。
代价是用不上索引,慎用。
五、性能:百分号放前面,索引就废了
LIKE '张%' 能用索引——前缀是确定的,B+ 树能直接定位到「张」那一段。
LIKE '%张%' 用不上——开头不确定,只能一行行扫。
数据量一大,这就是全表扫描,几百万行要跑几秒到几十秒。
真要搜中间的内容,MySQL 走全文索引(FULLTEXT),或者干脆上专门的搜索引擎。
别用 %...% 硬扛。
六、动手 Demo:两个通配符,一次转义,一处两边不一样
Demo 五段:先用 % 找三种位置,再用 _ 卡长度,然后建一张小表专门试转义。
第四段是这一篇的重点:同一句 SQL,写不写 ESCAPE 结果不同,
而且这里和 MySQL 的默认行为还不一样。
import _q46 as Q
c = Q.con()
# 百分号:任意长度
c.execute("SELECT name FROM goods WHERE name LIKE '%球%'")
c.execute("SELECT name FROM goods WHERE name LIKE '%拍'")
c.execute("SELECT name FROM goods WHERE name NOT LIKE '%球%'")
# 下划线:只顶一个字符
c.execute("SELECT name FROM goods WHERE name LIKE '_球'")
c.execute("SELECT name FROM goods WHERE name LIKE '_球拍'") # 0 行
c.execute("SELECT name FROM goods WHERE name LIKE '__球拍'") # 2 行
# 转义:要搜百分号、下划线本身
c2 = Q.blank()
c2.execute("CREATE TABLE codes (v TEXT)")
c2.executemany("INSERT INTO codes VALUES (?)",
[("50%",), ("a_b",), ("abc",)])
c2.execute("SELECT v FROM codes WHERE v LIKE '%\\%%' ESCAPE '\\'")
c2.execute("SELECT v FROM codes WHERE v LIKE '%\\_%' ESCAPE '\\'")
c2.execute("SELECT v FROM codes WHERE v LIKE '%\\%%'") # 这里 0 行
# 大小写
c.execute("SELECT 'abc' LIKE 'ABC' AS ci")
实跑输出:
--------------------------------------------------------------------
第二十课:like 的两个通配符,和转义那个坑
--------------------------------------------------------------------
百分号:代表任意长度,包括零个字符
SQL> SELECT name FROM goods WHERE name LIKE '%球%'
name
---------
篮球
足球
羽毛球拍
乒乓球拍
(4 行)
**`%球%` 是「中间有球」**,四件全中:篮球、足球、羽毛球拍、乒乓球拍。
**`%` 能顶零个、一个、很多个字符。**
SQL> SELECT name FROM goods WHERE name LIKE '%拍'
name
---------
羽毛球拍
乒乓球拍
(2 行)
**`%拍` 是「以拍结尾」**,只中两件。
**把 `%` 放前面就是「以某字开头」**,`LIKE '张%'` 找姓张的。
SQL> SELECT name FROM goods WHERE name NOT LIKE '%球%'
name
---------
跳绳
护腕
运动水壶
运动鞋
运动袜
瑜伽垫
(6 行)
**`not like` 就是取反**,剩下六件。
**注意 null 的行两边都不中**——`like` 碰上 null 给的是未知,不是假。
下划线:只顶一个字符,多一个少一个都不行
SQL> SELECT name FROM goods WHERE name LIKE '_球'
name
-----
篮球
足球
(2 行)
**`_球` 是「两个字,第二个是球」**,只中篮球和足球。
SQL> SELECT name FROM goods WHERE name LIKE '_球拍'
name
-----
(0 行)
SQL> SELECT name FROM goods WHERE name LIKE '__球拍'
name
---------
羽毛球拍
乒乓球拍
(2 行)
**`_球拍` 给 0 行,`__球拍` 给 2 行。** **因为「羽毛球拍」是四个字,一个下划线顶不住,得两个。**
**这就是 `_` 和 `%` 的区别**:`_` 是「恰好一个」,`%` 是「随便几个」。
转义:要搜百分号本身怎么办
SQL> SELECT v FROM codes
v
----
50%
a_b
abc
(3 行)
**表里三条:`50%`、`a_b`、`abc`。** 现在想精确找「含百分号的那条」。
SQL> SELECT v FROM codes WHERE v LIKE '%\%%' ESCAPE '\'
v
----
50%
(1 行)
**`ESCAPE '\'` 声明「反斜杠是转义符」**,于是 `\%` 表示「字面的百分号」,不再当通配符。
**`\_` 同理,能搜到下划线本身。**
SQL> SELECT v FROM codes WHERE v LIKE '%\_%' ESCAPE '\'
v
----
a_b
(1 行)
**这一句给 `a_b`。** **不写 `ESCAPE` 的话,反斜杠就是个普通字符**,下面这一句在**这里**给 0 行。
SQL> SELECT v FROM codes WHERE v LIKE '%\%%'
v
--
(0 行)
**MySQL 不写 `ESCAPE` 也能过,因为它的默认转义符就是反斜杠。**
**这里(SQLite)没有默认转义符,所以必须显式写。**
**这是两边行为不同的一处,跨库的 SQL 一律显式写 `ESCAPE`。**
大小写:这里不敏感,MySQL 看排序规则
SQL> SELECT 'abc' LIKE 'ABC' AS ci
ci
---
1
(1 行)
**给 1,说明这里不区分大小写。**
**MySQL 默认的 `utf8mb4_0900_ai_ci` 结尾那个 `ci` 就是 case insensitive,也不区分;**
**换成 `_bin` 或 `_cs` 的排序规则就区分了。**
**所以「like 区分大小写吗」这个问题,答案取决于列上的排序规则,不是取决于 `like`。**
性能:百分号放前面,索引就废了
**`LIKE '张%'` 能用上索引**——前缀确定,B+ 树能定位到「张」开头的那一段。
**`LIKE '%张%'` 用不上**——开头不定,只能一行行扫。 **数据量一大,这就是全表扫描。**
**真要搜中间的内容,就别在 MySQL 里硬扛**,上全文索引或者专门的搜索引擎。
第一段 %。 4 件、2 件、6 件——% 换个位置,筛出来的完全是不同的东西。
第二段 _。 _球 给 2 行;
_球拍 给 0 行、__球拍 给 2 行,这一个对比把 _ 的「恰好一个」讲透了。
第三段建表。 三条数据专门留着:50%、a_b、abc。
第四段转义。 带 ESCAPE 的两句各给 1 行;不带的给 0 行——这里没有默认转义符。
第五段大小写。 给 1,不区分。
30 秒实验
LIKE '%球'与LIKE '%球%'各给几行?差在哪?- 把
ESCAPE '\'里的反斜杠换成!,LIKE '%!%%' ESCAPE '!'还给50%吗? LIKE '张_'能找出「张伟」吗?LIKE '张__'呢?
常见误区
- 以为
_能顶多个字符。 它只顶一个,四个字的商品名要两个下划线,写一个就一行都筛不出来, 还不报错。 - 搜百分号时直接写
LIKE '%%%'。 那是「什么都匹配」,必须用ESCAPE声明转义符才有意义。 - 在大表上用
LIKE '%关键词%'。 索引完全用不上,几百万行就是全表扫描;该上全文索引就上。
自测三问(点开对答案)
1. `%` 和 `_` 的区别是什么? —— `%` 顶任意长度(包括零个),`_` 顶恰好一个字符。 2. 怎么搜「名字里含百分号」的记录? —— `LIKE '%\%%' ESCAPE '\'`,先声明转义符,再用转义符包住百分号。 3. 为什么 `LIKE '%张%'` 用不上索引? —— 前缀不确定,B+ 树没法定位起点,只能逐行扫。`LIKE '张%'` 才能用。like 是按条件找。下一篇换一种玩法——不看条件,随机抽几条出来。
- ☐ (本篇完,下一篇 5.2 随机查询:rand())