DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

MySQL CONCAT 拼接姓名后,LIKE 搜不到姓氏为 NULL 的记录

MySQL 可空姓名字段参与 CONCAT 后,整个拼接结果变为 NULL,LIKE 因而漏掉记录。按姓名格式选择逐参数 COALESCE 或 CONCAT_WS,并区分空字符串与空值。

姓名搜索使用 CONCAT(first_name, ' ', last_name) LIKE '%Alex%',名字明明含有 Alex,只要姓氏是 NULL,这一行就不出现在结果中。直接查 first_name 又能找到,很容易把问题误判为关联查询丢了记录。

这里处理 MySQL 8.4 中可空字符串列的拼接。CONCAT() 的任意参数为 NULL,整个结果就是 NULL;再对它做 LIKE,结果仍为 NULL,不能满足 WHERE 的筛选条件。空字符串 '' 与 NULL 不同,不能只凭页面上都显示为空就判断数据一致。

把拼接结果单独取出来

先用不读取业务表的表达式观察差别:

SELECT
    CONCAT('Alex', ' ', NULL) AS full_name,
    CONCAT('Alex', ' ', NULL) LIKE '%Alex%' AS name_matches,
    CONCAT('Alex', ' ', '') AS name_with_empty_last;

前两列应为 NULL,第三列是末尾带一个空格的 Alex 。这能把故障缩小到字符串表达式本身,不需要先改表结构或关联条件。

在实际查询中,可以临时将原来的拼接表达式、first_name IS NULL 和 last_name IS NULL 加到只读诊断的投影列里,并限定为一条已知记录。保留租户、权限和其他过滤条件,避免为了排查而放大查询范围。如果拼接结果并非 NULL,就应继续检查实际模式、字符内容和排序规则;下面的修改不处理这些原因。

保留固定分隔位置:拼接前逐项补空串

如果姓名格式要求始终在两部分之间保留一个空格,把空值处理放到每个可空参数上:

SELECT
    CONCAT(COALESCE('Alex', ''), ' ', COALESCE(NULL, '')) AS full_name,
    CONCAT(COALESCE('Alex', ''), ' ', COALESCE(NULL, ''))
        LIKE '%Alex%' AS name_matches;

COALESCE() 选取第一个非 NULL 参数。第二个参数给出空字符串后,CONCAT() 得到的每一项都不再是 NULL,已有的名字也能保留下来。将字面量换回列名时,查询条件中的表达式可写为:

CONCAT(COALESCE(first_name, ''), ' ', COALESCE(last_name, ''))
    LIKE '%Alex%'

这里只展示需要替换的条件片段;原查询的表关联、其他条件和参数绑定方式应继续保留。应用中把搜索模式作为参数传入,不要把用户输入直接拼到 SQL 文本里。

不要只在最外面写 COALESCE(CONCAT(first_name, ' ', last_name), '')。内层一旦变成 NULL,外层只能把整条姓名替换为空字符串,无法找回本来存在的 Alex。

这种写法保留固定空格,因此只有名字时有尾空格,只有姓氏时有前导空格。是否需要去掉它们,属于姓名显示和匹配规则,需要明确后再处理。

跳过缺失部分:使用 CONCAT_WS

如果缺失某部分时不应该保留它旁边的分隔符,可以改用带分隔符的拼接:

SELECT
    CONCAT_WS(' ', 'Alex', NULL) AS full_name,
    CONCAT_WS(' ', 'Alex', NULL) LIKE '%Alex%' AS name_matches;

CONCAT_WS() 的第一个参数是分隔符。后面的 NULL 参数会被跳过,这里的姓名为 Alex,匹配结果为 1。对应的查询条件片段是:

CONCAT_WS(' ', first_name, last_name) LIKE '%Alex%'

分隔符自身不能是 NULL,否则结果仍为 NULL。另外,CONCAT_WS() 不会跳过空字符串:姓氏为 '' 时仍可能留下空格。只有业务规则确实把空字符串也视为缺失值,才应另行统一这两种输入,不要悄悄改变已有数据含义。

把条件拆成 first_name LIKE ... OR last_name LIKE ... 也会改变匹配对象。它适合“任一字段匹配”的需求,却不能替代所有完整姓名搜索,例如跨越名字、分隔符和姓氏的搜索片段。不要为了处理 NULL 同时改变搜索语义。

用缺失值组合验收,不批量改写姓名

在测试数据里覆盖两列都有值、只有名字、只有姓氏、两列均为 NULL、某列为空字符串几种情况。核对最终拼接文本及 LIKE 的结果,而不只是查询有没有返回行。搜索词也应覆盖单部分名称与完整姓名,确认分隔符符合预期。

还要保留 % 和 _ 的模式匹配语义:如果产品要求把它们作为普通字符搜索,应沿用相应的转义规则;给参数补空字符串并不会解决模式转义问题。

这项修复只需要调整查询表达式,不必把表里的全部 NULL 更新成 ''。保留修改前的 SQL 与匹配样本,先在测试环境核对结果,再随应用查询变更上线;需要回退时恢复原查询即可。它修复的是缺失字段导致姓名丢失,不代表查询性能已经改善。