JOIN前须检查NULL、空字符串及非法值;LEFT JOIN过滤条件应放ON而非WHERE;多表JOIN顺序影响结果;键值需用COALESCE/NULLIF等归一化且语义一致。

JOIN前必须先检查NULL和空字符串
脏数据最常卡在ON条件上——比如用user_id关联,但一端是NULL,另一端是' '(空格字符串),或者数值型字段混入了'N/A'。这些值不会报错,但会静默丢掉整行记录,还查不出原因。
实操建议:
- 对所有JOIN键字段,先跑
SELECT COUNT(*) FROM table WHERE key IS NULL OR TRIM(key) = '' OR key = 'N/A' - 数值型字段额外加
NOT REGEXP '^[0-9]+$'(MySQL)或TRY_CAST(key AS INT) IS NULL(SQL Server / PostgreSQL 12+) - 别依赖应用层清洗——JOIN执行时SQL引擎不认业务规则,只认字面值匹配
LEFT JOIN后WHERE条件误杀左表NULL行
这是最隐蔽的坑:写LEFT JOIN ... ON a.id = b.user_id WHERE b.status = 'active',表面看想保留左表所有用户、只取右表活跃记录,结果b.status为NULL的行全被WHERE过滤掉了,LEFT变成INNER。
实操建议:
- 把右表的过滤条件从
WHERE挪到ON子句里:LEFT JOIN b ON a.id = b.user_id AND b.status = 'active' - 如果必须用WHERE(比如要排除左表某些行),先明确区分:左表条件放WHERE,右表条件放ON
- 执行后立刻查
SELECT COUNT(*) FROM result WHERE b_user_id IS NULL,验证LEFT是否真生效
多表JOIN时ON条件顺序影响结果集大小
三个表A-B-C连JOIN,A LEFT JOIN B ON ... LEFT JOIN C ON ...和A LEFT JOIN C ON ... LEFT JOIN B ON ...看似等价,但B和C本身有重复键或NULL时,中间结果膨胀会导致最终行数差异极大——尤其当C表某user_id对应50条日志,而B表该用户只有1条资料,顺序不同会让50行日志各自匹配1次还是只匹配1次。
实操建议:
- 优先让“主干表”(如用户表)在最左,窄表(字段少、去重后行数少)靠右
- 对可能爆炸的关联(如订单→订单明细),先用
GROUP BY或DISTINCT预聚合右表:(SELECT user_id, MAX(status) AS status FROM b GROUP BY user_id) b - 用
EXPLAIN看rows估算值,若某JOIN步骤显示百万级扫描,基本就是顺序或过滤逻辑出问题
用COALESCE/NULLIF提前归一化不一致键值
不同来源的ID可能格式混乱:有的带前缀'U123',有的纯数字123,有的补零'00123'。硬JOIN会失败,但用CAST或SUBSTRING又难维护。
实操建议:
- 统一提取数字部分:
COALESCE(NULLIF(TRIM(LEADING 'U' FROM id)), id)(去掉U前缀,再fallback原值) - 补零对齐:
LPAD(REPLACE(id, 'U', ''), 5, '0')→ 把'123'变'00123','U45'变'00045' - 关键点:这些转换必须写在JOIN的
ON两侧,且确保两边逻辑完全一致;否则索引失效,性能暴跌
真正麻烦的不是写清洗逻辑,而是确认清洗后的值在业务语义上确实等价——比如'00123'和'123'在数据库里是同一用户,但'U123'可能是测试账号。这个边界得靠业务方拍板,SQL只能保证它不崩。

















