case when是唯一通用方案,必须置于set子句中、每个字段独立写case、必带else保留原值、where严格限定范围,且所有分支类型一致;漏else致null、共用case非法、主键交换需临时值规避冲突。

CASE WHEN 是唯一能用一条 SQL 同时更新多行、多字段不同值的通用方案,不依赖数据库版本或扩展语法,MySQL、PostgreSQL、SQL Server、SQLite(3.35+)都支持。其他写法要么不标准,要么只在特定引擎生效。
UPDATE 中 CASE WHEN 的位置和结构必须严格
所有分支必须写在 SET 子句里,每个字段单独一个 CASE 表达式,WHERE 不能省——它不是可选装饰,而是防止误设为 NULL 的安全边界。
-
CASE必须紧跟在=后面,不能放在WHERE或FROM里 - 每个
CASE必须有ELSE分支;漏写会导致没匹配上的行该字段变NULL -
WHERE id IN (1,2,3)不仅提速,更关键的是把影响范围锁死,避免全表扫描后意外覆盖 - 示例:
UPDATE users SET name = CASE id WHEN 1 THEN 'Alice' WHEN 2 THEN 'Bob' ELSE name END, status = CASE id WHEN 1 THEN 'active' WHEN 2 THEN 'pending' ELSE status END WHERE id IN (1,2)
更新多个字段时,每个字段都要独立写 CASE
不能共用一个 CASE 去赋多个列的值。数据库按行计算,每个字段的 CASE 是独立求值的,类型也各自校验。
- 错误写法:
SET (name, status) = CASE id WHEN 1 THEN ('Alice', 'active') ...—— 大多数数据库不支持元组赋值 - 正确写法:每个字段前都加
CASE,ELSE写原字段名(如ELSE name)可保留未匹配行的旧值 - 注意类型一致性:比如
THEN 1和THEN '1'在 PostgreSQL 会报错,在 MySQL 可能隐式转成字符串,但结果不可控 - 分支过多(>50)时,MySQL 解析变慢,PostgreSQL 编译耗时上升,建议拆成多条语句或换临时表
主键/唯一键字段更新要绕开中间冲突
直接用 CASE 交换两行主键值(如 id=1→2,id=2→1)大概率失败,因为数据库逐行检查约束,不是原子替换。
- 典型报错:
Duplicate entry '2' for key 'PRIMARY' - 安全做法:先用临时值占位,再二次更新。例如:
UPDATE t SET id = CASE id WHEN 1 THEN -2 WHEN 2 THEN -1 ELSE id END WHERE id IN (1,2); UPDATE t SET id = CASE id WHEN -2 THEN 2 WHEN -1 THEN 1 END WHERE id IN (-1,-2) - MySQL 8.0+ 可用
UPDATE ... ORDER BY控制顺序,但 PostgreSQL 不支持,跨库方案仍推荐临时值法 - 如果目标字段是唯一索引列(非主键),同样适用该规避逻辑
动态生成 SQL 时防注入和空值陷阱
应用层拼接 CASE 语句时,用户输入不能直插,且 NULL 值需显式处理。
- 禁止:
WHEN {$id} THEN '{$value}'—— $value 来自表单就可能注入 - 应使用参数化构造或白名单映射,比如 PHP 中用
sprintf("WHEN %d THEN '%s'", $id, mysqli_real_escape_string($v))(仅限可信上下文) -
NULL不能写成WHEN col = NULL,必须用WHEN col IS NULL;MySQL 和 PostgreSQL 都不认= NULL - SQLite 对
CASE支持较弱(尤其老版本),分支超 20 个就可能解析失败,大数据量优先走临时表 +UPDATE ... FROM模拟(用JOIN)
真正麻烦的不是语法,而是当字段值来自外部映射表、或需按正则/区间/前缀分组时,硬编码 CASE 很快失控。这时候该切到临时表 + JOIN 更新,而不是硬撑分支数。











