ThinkPHP where('col', '') 无法匹配 NULL 行,UPDATE 永远返回 0
某次数据修复任务:一张门店表里有几条记录,某个关联字段明显是"空的"——print_r 打印出来是 [offline_org_id] =>,和空字符串在视觉上完全一样。于是写了一条很自然的 ThinkPHP 链式更新:
$n = $db->table('store')
->where('code', 'S206444')
->where('org_id', '') // 看着是空,就 where 空字符串
->update(['org_id' => 'ORG_192645']);
// $n === 0执行完,返回 0。反复确认 code 条件没问题,目标行就在那里,但就是改不到。
现象
UPDATE受影响行数为 0,不报错,不抛异常print_r/var_dump打印该字段时显示为空([org_id] =>),与真正的空字符串视觉无差- 换成
find()查询配合where('org_id', '')也返回null,仿佛行不存在
根因
两个层面的陷阱叠加在一起:
第一层——显示欺骗:PHP 的 print_r 输出 NULL 和 '' 的外观完全相同,都是 [field] =>。你看到的"空",底层可能是 NULL,也可能是真正的空字符串 '',肉眼无法区分。
第二层——SQL NULL 语义:ANSI SQL 规定,任何用 = 与 NULL 比较的表达式结果都是 NULL(不是 true 也不是 false),WHERE 子句把 NULL 视为 false,该行被直接排除。ThinkPHP 的 where('col', '') 会生成 WHERE col = '',遇到列值为 NULL 的行,一条都匹配不到。
where('col', $val) 在 ThinkPHP 5/6 和 Laravel Eloquent 里都只是把 $val 拼成 col = $val。当 $val = '' 时生成 col = '',而数据库里列值是 NULL 的行,在 col = '' 的条件下求值结果是 NULL,被 WHERE 直接过滤掉。数据修复脚本、批量回填脚本最容易踩这个坑——表面上跑完了,实际上一行没改到。
诊断三连
一旦 UPDATE 返回 0 行,先跑这条 SQL 把 NULL 和空字符串区分开:
SELECT
org_id,
org_id IS NULL AS is_null,
org_id = '' AS is_empty,
LENGTH(org_id) AS len
FROM store
WHERE code = 'S206444';判读规则:
| is_null | is_empty | len | 实际值 |
|---------|----------|------|-----------|
| 1 | NULL | NULL | NULL |
| 0 | 1 | 0 | '' |
| 0 | 0 | N | 有内容 |
LENGTH 返回空白(不是 0)就是 NULL,锁定根因。
正解
把条件改成 IS NULL OR = '' 兜底,三种写法任选:
方案 A:Raw SQL(最稳)
$n = $db->execute(
"UPDATE store SET org_id = :new
WHERE code = :code
AND (org_id IS NULL OR org_id = '')",
['new' => 'ORG_192645', 'code' => 'S206444']
);方案 B:ThinkPHP 链式
$n = $db->table('store')
->where('code', 'S206444')
->where(function ($q) {
$q->whereNull('org_id')->whereOr('org_id', '');
})
->update(['org_id' => 'ORG_192645']);方案 C:Laravel Eloquent
DB::table('store')
->where('code', 'S206444')
->where(function ($q) {
$q->whereNull('org_id')->orWhere('org_id', '');
})
->update(['org_id' => 'ORG_192645']);修完之后立即验证:
SELECT org_id, org_id IS NULL AS still_null
FROM store WHERE code = 'S206444';
-- 期望: still_null=0, org_id='ORG_192645'写数据修复脚本时,只要字段含义是"无值",就必须用 IS NULL OR col = '' 双条件。依赖 ORM 自动处理、或凭 print_r 目测判断,都是不可靠的。
预防
- Schema 层:如果业务语义是"无值",选定一种表达方式并坚持——要么
NOT NULL DEFAULT '',要么DEFAULT NULL,但不要让 NULL 和空字符串共存于同一列 - 修复脚本范式:养成双条件反射,将
IS NULL OR = ''作为所有"空"字段匹配的默认写法 - 对偶陷阱:注意反向坑——
whereOr('col', $emptyVar)在某些写法下会匹配所有空值行,读路径慎用
一句话外卖
print_r看着是空,SQL 里不一定是'';ORM 的where('col', '')永远跳过 NULL 行,数据修复脚本必须写IS NULL OR = ''。