LIKE不能直接用于JOIN条件,因多数数据库不支持或性能极差;应将模糊逻辑移至WHERE/EXISTS中过滤,或用CTE/子查询预生成匹配对,避免ON中动态拼接导致索引失效。

LIKE不能直接用于JOIN条件,但可以用ON子句中嵌套
SQL标准不支持在JOIN语法里把LIKE当连接谓词直接写成ON t1.name LIKE t2.pattern这种形式——它语法合法,但多数引擎(如MySQL 5.7、PostgreSQL)会拒绝执行或报错,因为LIKE右侧必须是常量或参数,不能是列引用(除非该列值本身是完整通配模式且已预处理)。真实可行的方式是把模糊匹配逻辑放在ON子句里,且确保右侧模式字段不含未转义的%或_,否则结果不可控。
- PostgreSQL允许
ON t1.name LIKE t2.pattern,但要求t2.pattern每行都是有效模式(如'john%'),否则匹配行为难以预测 - MySQL 8.0+支持,但性能极差:无法使用索引,全表扫描+逐行字符串匹配,大表连接时可能卡死
- 更稳妥的做法是提前用
CONCAT('%', t2.keyword, '%')构造模式,再LIKE,但要注意NULL传播:任一操作数为NULL,整个表达式返回NULL,导致连接丢失
用LEFT JOIN + LIKE模拟“关键词包含”关系
典型场景是主表orders要关联到配置表keyword_rules,找出订单标题含任意关键词的规则。这时不能依赖等值连接,得靠LIKE做文本包含判断。
SELECT o.order_id, o.title, r.rule_id, r.keyword
FROM orders o
LEFT JOIN keyword_rules r ON o.title LIKE CONCAT('%', r.keyword, '%');
注意三点:
-
r.keyword必须不含%或_,否则CONCAT后变成多义通配符;若原始数据含这些字符,先用REPLACE(r.keyword, '%', '\%')并加ESCAPE '\' - 这个连接会产生笛卡尔式膨胀:一个
title匹配多个keyword,就生成多行;需后续用ROW_NUMBER() OVER (PARTITION BY o.order_id ORDER BY r.priority)去重 - MySQL中
CONCAT遇到NULL返回NULL,所以r.keyword IS NOT NULL必须显式加在ON或WHERE里,否则整行被过滤掉
性能陷阱:LIKE左侧字段没索引,右侧又不是前缀匹配
如果写成ON t1.name LIKE CONCAT('%', t2.code, '%'),数据库无法利用t1.name上的B-tree索引,因为通配符在开头。即使t2.code只有几条,t1表也要全扫一遍。
- 能改用前缀匹配就优先用:
ON t1.name LIKE CONCAT(t2.code, '%')——此时t1.name索引可生效(前提是索引建在该字段上) - 想查“结尾匹配”,可用
RIGHT(t1.name, LENGTH(t2.suffix)) = t2.suffix代替LIKE '%xxx',部分引擎能走索引 - 真要全文模糊,别硬扛:
fulltext索引(MySQL)或tsvector(PostgreSQL)才是正解,LIKE只是临时补丁
替代方案:用REGEXP/RLIKE避免LIKE的语义局限
LIKE只支持%和_,遇到“含数字+字母组合”或“非连续空格分隔”就抓瞎。MySQL的RLIKE、PostgreSQL的~能写更精确的模式。
SELECT u.user_id, u.email
FROM users u
JOIN domains d ON u.email RLIKE CONCAT('@', d.domain_name, '$');
这比LIKE '@%' || d.domain_name更准——防止@gmail.com误匹配@gmail.com.cn。但代价是:
- 正则引擎比
LIKE慢一个数量级,尤其复杂表达式 - MySQL的
RLIKE不走索引;PostgreSQL的~在无函数索引时也全表扫 - 不同数据库正则语法不兼容(比如MySQL不支持
\d,得写[0-9])
真正难的是平衡精度和速度:模糊匹配本身就不该出现在高并发主路径里,要么预计算打标签,要么扔给Elasticsearch。











