SQL 数据分析 · 指南 · 2026年10月30日 · 11 分钟阅读

SQL LIKE 模糊查询:% 和 _ 通配符、ILIKE、NOT LIKE 与转义

机场航班信息屏上,一个放大镜照亮所有以相同字母开头的城市名

LIKE 按模式筛选文本,而不是按精确值,也就是常说的「模糊查询」。模式里只有两个通配符:% 匹配任意多个字符(包括零个),_ 恰好匹配一个字符。 所以 city LIKE 'Mos%' 找出所有以「Mos」开头的城市,model LIKE '%300' 找出以「300」结尾的机型,airport_name LIKE '%ovo%' 找出名字里任何位置含有「ovo」的机场。在 PostgreSQL 和 DuckDB 里 LIKE 区分大小写,不区分大小写的版本是 ILIKE。NOT LIKE 取反,但无论 LIKE 还是 NOT LIKE,都不会返回该列为 NULL 的行。

下面的例子都在 Nomto SQL 课程的航空公司数据库上运行,使用 PostgreSQL 语法,引擎是 DuckDB(Nomto SQL 练习场背后的引擎),每个结果表都是真实输出。工作中常见的 MySQL、Hive 和 Spark SQL 有哪些不同,也会在对应的地方说明。

LIKE 基本语法

SELECT columns
FROM table
WHERE text_column LIKE 'pattern';

模式就是一个用单引号包起来的普通字符串。不带通配符时,LIKE 的效果和 = 一样,真正有用的是 % 和 _。模式必须匹配整个值,所以 % 放在哪里非常关键:

模式 含义 匹配
'Mos%' 以「Mos」开头 Moscow
'%sk' 以「sk」结尾 Omsk、Tomsk、Irkutsk
'%ovo%' 包含「ovo」 Domodedovo、Pulkovo
'_____' 恰好五个字符 Kazan、Sochi
'S_r%' S、任意一个字符、r,后面随意 Saratov、Surgut

% 通配符:开头是、结尾是、包含

以某个字符串开头

SELECT airport_code, airport_name, city
FROM airports
WHERE city LIKE 'Mos%'
ORDER BY airport_code;
airport_code airport_name city
DME Domodedovo Moscow
SVO Sheremetyevo Moscow
VKO Vnukovo Moscow

以某个字符串结尾

SELECT model
FROM aircrafts
WHERE model LIKE '%300'
ORDER BY model;
model
Boeing 737-300
Boeing 767-300
Boeing 777-300

包含某个字符串

两边都加 %,意思是「出现在字符串的任何位置」:

SELECT airport_name, city
FROM airports
WHERE airport_name LIKE '%ovo%'
ORDER BY airport_name
LIMIT 6;
airport_name city
Domodedovo Moscow
Ivanovo South Ivanovo
Kemerovo Kemerovo
Khomutovo Yuzhno-Sakhalinsk
Khrabrovo Kaliningrad
Koltsovo Yekaterinburg

完整结果有 12 个机场,这里只截取前六行。

「包含」常常会比你想要的多匹配一些。乘客姓名以「名 姓」的形式全大写存储。用 '%IVANOV%' 搜姓氏 Ivanov 会返回 14,040 张机票,而 '% IVANOV'(一个空格,然后姓氏在最末尾)只返回 7,539 张:

SELECT CASE WHEN passenger_name LIKE '% IVANOV'  THEN 'IVANOV'
            WHEN passenger_name LIKE '% IVANOVA' THEN 'IVANOVA'
            ELSE 'other' END AS surname,
       COUNT(*) AS tickets
FROM tickets
WHERE passenger_name LIKE '%IVANOV%'
GROUP BY surname
ORDER BY tickets DESC;
surname tickets
IVANOV 7539
IVANOVA 6501

宽松的模式悄悄把女性形式 Ivanova 也算了进来。中文数据里是同样的道理:搜 '%张%' 找姓张的人,名字里带「张」字的也会被找出来,应该写 '张%'。数据允许的话,模式要尽量锚定得紧一些。

_ 通配符:恰好一个字符

_ 代表一个字符,不多也不少。五个下划线找出名字是五个字母的城市:

SELECT city
FROM airports
WHERE city LIKE '_____'
ORDER BY city;
city
Anapa
Chita
Kazan
Kirov
Kursk
Kyzyl
Mirny
Nadym
Penza
Pskov
Sochi
Tomsk
Ukhta

两种通配符可以混用:'S_r%' 表示 S,然后任意一个字符,然后 r,后面随意。

SELECT city FROM airports WHERE city LIKE 'S_r%' ORDER BY city;
city
Saransk
Saratov
Strezhevoy
Surgut

Strezhevoy 不是错误:S,下划线匹配到「t」,然后是「r」。_ 匹配任何字符,辅音也算。

中文字符:一个汉字算一个字符

_ 按字符计数,不按字节计数,所以一个汉字对应一个 _:

SELECT '北京' LIKE '__' AS two_chars,
       '北京首都' LIKE '北京%' AS prefix,
       length('北京') AS len;
two_chars prefix len
true true 2

PostgreSQL 和使用 utf8mb4 字符集的 MySQL 也是这样。要找两个字的名字,写 name LIKE '__' 就行。注意 MySQL 的 LENGTH() 返回的是字节数(一个汉字通常是 3),按字符数要用 CHAR_LENGTH()。

不区分大小写:ILIKE 和 LOWER

在 PostgreSQL 和 DuckDB 里,LIKE 逐个字符精确比较,小写字母匹配不上大写字母:

SELECT 'ABC' LIKE 'abc' AS like_result, 'ABC' ILIKE 'abc' AS ilike_result;
like_result ilike_result
false true

WHERE city LIKE 'mos%' 一行都不返回;WHERE city ILIKE 'mos%' 返回莫斯科的三个机场。ILIKE 不是标准 SQL:PostgreSQL、DuckDB、Snowflake 和 Spark SQL(3.3 起)有它,MySQL、SQL Server 和 Hive 没有。通用的做法是把两边都转成小写:

SELECT city FROM airports WHERE lower(city) LIKE 'mos%';

在 MySQL 和 SQL Server 里,是否区分大小写取决于列的排序规则(collation),而默认的排序规则不区分大小写,所以普通的 LIKE 'mos%' 在那里就能找到 Moscow。查询在不同数据库之间迁移时,这一点经常让人意外。

NOT LIKE,以及 NULL 行为什么会消失

NOT LIKE 保留不匹配的行。陷阱在于:如果列是 NULL,LIKE 和 NOT LIKE 都返回 NULL,WHERE 两种情况下都会把这一行丢掉。大约一半的机票在 contact_data 里有邮箱。按地址里是否含下划线来拆分(\_ 是什么意思,下一节会讲):

SELECT COUNT(*) FILTER (WHERE contact_data->>'email' LIKE '%\_%' ESCAPE '\')     AS with_underscore,
       COUNT(*) FILTER (WHERE contact_data->>'email' NOT LIKE '%\_%' ESCAPE '\') AS without_underscore,
       COUNT(*) FILTER (WHERE contact_data->>'email' IS NULL)                    AS no_email,
       COUNT(*) AS total
FROM tickets;
with_underscore without_underscore no_email total
87828 111255 167650 366733

LIKE 加 NOT LIKE 一共是 199,083,不是 366,733:没有邮箱的 167,650 张机票两边都不在。如果「不匹配」应该包括「没有值」,就要明确写出来:WHERE email NOT LIKE '%\_%' ESCAPE '\' OR email IS NULL,或者 WHERE COALESCE(email, '') NOT LIKE ...。背后的三值逻辑见 SQL NULL 值处理。

转义:怎样搜索 % 或 _ 本身

% 和 _ 是通配符,所以要搜这两个字符本身,需要一个转义字符。不转义的话,「含有下划线的邮箱」会匹配所有邮箱:

SELECT COUNT(*) AS emails,
       COUNT(*) FILTER (WHERE email LIKE '%_%')               AS unescaped,
       COUNT(*) FILTER (WHERE email LIKE '%\_%' ESCAPE '\')   AS escaped
FROM (SELECT contact_data->>'email' AS email FROM tickets) t
WHERE email IS NOT NULL;
emails unescaped escaped
199083 199083 87828

'%_%' 的意思是「至少有一个任意字符」,任何非空字符串都满足。加上 ESCAPE '\' 之后,\_ 才表示一个真正的下划线。

各数据库的默认转义字符不一样,复制查询时往往就在这里出错:

显式写一个 ESCAPE 子句在上面这些数据库里都能用,而且转义字符可以任选,比如 LIKE '%!_%' ESCAPE '!',这样就绕开了反斜杠的各种麻烦。

同时匹配多个模式

LIKE 只接受一个模式。要匹配多个,就用 OR 组合条件;如果 WHERE 里还有别的条件,要加上括号:

SELECT city
FROM airports
WHERE (city LIKE 'Nov%' OR city LIKE 'Nor%')
ORDER BY city;
city
Norilsk
Novokuznetsk
Novosibirsk
Novy Urengoy

不加括号的话,WHERE a AND b OR c 会被理解为 (a AND b) OR c,这是多出一堆行的经典原因。

PostgreSQL 还接受 city LIKE ANY (ARRAY['Nov%', 'Nor%'])。DuckDB 不行,会报错 Unsupported comparison "~~" for ANY/ALL subquery,所以在 Nomto SQL 练习场里请用 OR。

模式变复杂时,改用正则表达式。在 DuckDB 里,regexp_matches(city, '^(Nov|Nor|Noy)') 返回 Norilsk、Novokuznetsk、Novosibirsk、Novy Urengoy 和 Noyabrsk。PostgreSQL 用运算符 city ~ '^(Nov|Nor|Noy)',MySQL 用 city REGEXP '^(Nov|Nor|Noy)',Hive 和 Spark SQL 用 city RLIKE '^(Nov|Nor|Noy)'。在 DuckDB 里小心 ~:它要求正则匹配整个字符串,所以 city ~ '^Nov' 什么都不返回,需要写成 city ~ 'Nov.*'。

LIKE 和 = 的区别

= LIKE
匹配 精确的值 带 % 和 _ 的模式
通配符 没有(% 只是普通字符) %、_
速度 总能使用索引 只有固定前缀时才能使用索引
典型用法 status = 'Arrived' city LIKE 'Nov%'

知道完整的值时,就用 =。在 PostgreSQL 和 DuckDB 里,不带通配符的 LIKE 和 = 结果相同('abc ' LIKE 'abc' 和 'abc ' = 'abc' 都是 false)。在 SQL Server 里,= 会忽略末尾空格,而 LIKE 不会,所以那里 'abc ' = 'abc' 为真,'abc' LIKE 'abc ' 却为假。

LIKE 也能用在转成文本的日期上,CAST(scheduled_departure AS VARCHAR) LIKE '2026-08-01%',这里它和按范围查询一样得到 568 个航班。但更推荐范围写法 scheduled_departure >= '2026-08-01' AND scheduled_departure < '2026-08-02':它保留列的类型,不管日期的文本格式如何都能在任何数据库里运行,还能使用索引。更多内容见 SQL 日期函数。

LIKE 的性能:什么时候走索引

在大表上,模式怎么开头决定了查询有多快:

DuckDB、BigQuery、Snowflake、Hive 这类列式分析引擎本来就是扫描整列,课程规模的数据上你感觉不到差别。但在有上百万行的线上 PostgreSQL 或 MySQL 表上,差别会非常明显。面试里「LIKE '%xx%' 为什么不走索引」也是常见问题。

常见错误

找到字符串之后,要从中截取部分内容(SPLIT_PART、SUBSTRING、REPLACE),见 SQL 字符串函数大全。模式筛选也收录在 SQL 速查表里。

常见问题

SQL LIKE 里的 % 是什么意思?

% 匹配任意长度的字符序列,包括空字符串。'Mos%' 能匹配「Mos」「Moscow」以及任何以「Mos」开头的值。_ 恰好匹配一个字符,一个汉字也算一个字符。

SQL LIKE 区分大小写吗?

在 PostgreSQL、DuckDB 和 Oracle 里区分。在 MySQL 和 SQL Server 里取决于排序规则,而默认的排序规则不区分大小写。在 PostgreSQL 或 DuckDB 里要不区分大小写,就用 ILIKE;任何数据库里都可以用 lower(col) LIKE 'pattern'。

LIKE 里怎样转义下划线或百分号?

在它前面加一个转义字符,并用 ESCAPE 声明:col LIKE '%\_%' ESCAPE '\' 会找出含有真正下划线的值。PostgreSQL 和 MySQL 默认用反斜杠,DuckDB 和 SQL Server 没有默认转义字符,所以显式的 ESCAPE 子句是通用写法。SQL Server 还接受 '%[_]%'。

LIKE 能同时匹配多个值吗?

不能直接匹配。用 OR 组合条件(city LIKE 'Nov%' OR city LIKE 'Nor%'),在 PostgreSQL 里用 LIKE ANY (ARRAY[...]),或者改用正则表达式(REGEXP、RLIKE、~)。

LIKE 比 = 慢吗?

带固定前缀的 LIKE 能使用索引,几乎和 = 一样快。以 % 开头的模式必须检查每一行,在大表上会慢很多,除非加上三元组索引(PostgreSQL)或全文索引。

LIKE 能用在数字上吗?

在 PostgreSQL 里需要先转换类型:CAST(col AS TEXT) LIKE '12%'。MySQL 会自动转换。对数字来说,范围条件(col BETWEEN 1200 AND 1299)几乎总是更好的选择。

其他语言: English, Español, Português (Brasil), Tiếng Việt