not只能作用于布尔表达式,不能直接修饰字段;正确写法是where not status = 'archived'或where not (type = 'temp' and created_at
WHERE里用NOT排除状态,必须带完整比较表达式
直接写
NOT status会报错或语义错误——NOT不是取字段值的“非”,而是取反一个布尔表达式。它只能作用于status = 'inactive'、status IS NULL这类能返回TRUE/FALSE/UNKNOWN的条件。✅ 正确写法:
WHERE NOT status = 'archived'、WHERE NOT (type = 'temp' AND created_at❌ 错误写法:
WHERE NOT status(语法错)、WHERE NOT 'draft'(缺少左操作数)、SELECT NOT is_valid FROM t(NOT不能出现在SELECT列表)⚠️ 注意:
NOT status IS NULL合法,等价于status IS NOT NULL,但后者更直白,建议优先用后者。布尔字段为NULL时,NOT = 'true'会漏掉整行
MySQL里
BOOLEAN本质是TINYINT(1),PostgreSQL有原生BOOLEAN,但无论哪种,NULL = TRUE结果都是UNKNOWN,而NOT UNKNOWN仍是UNKNOWN,WHERE只保留TRUE行,所以NOT is_active = TRUE会把is_active为NULL的行全过滤掉。想包含NULL(比如“未设置状态”的记录也算无效):
WHERE is_active != TRUE OR is_active IS NULL- 更稳妥:
WHERE COALESCE(is_active, FALSE) = FALSE(把NULL转为FALSE再比)想严格排除仅
TRUE,且明确不想要NULL(即只留FALSE):
WHERE is_active = FALSE(不依赖NOT,避免三值逻辑干扰)用NOT IN排除关联ID?立刻换成NOT EXISTS
写
WHERE id NOT IN (SELECT user_id FROM events)看似简洁,但只要events.user_id里有一个NULL,整个条件恒为UNKNOWN,结果集为空——没有报错,查不到数据,问题极难定位。✅ 推荐写法:
WHERE NOT EXISTS (SELECT 1 FROM events e WHERE e.user_id = users.id)这个写法天然跳过NULL判断,语义清晰(“不存在对应事件”),而且多数数据库能走索引,性能通常优于
NOT IN。其他可选方案:
LEFT JOIN events ON users.id = events.user_id WHERE events.user_id IS NULL(显式、易读、JOIN优化器友好)- 硬加防护:
WHERE id NOT IN (SELECT user_id FROM events WHERE user_id IS NOT NULL)(临时补救,不推荐长期用)NOT条件基本不走索引,别指望靠它提速
NOT status = 'active'、NOT is_deleted、NOT name LIKE '%test%'这些几乎都不走索引,执行计划里大概率是全表扫描。尤其NOT LIKE '%suffix',连前缀索引都用不上。真正高频排除时,更靠谱的做法:
- 建覆盖索引:比如常查
WHERE is_active = FALSE,就给is_active单独建索引- 正向重写:把
NOT status IN ('draft', 'pending')改成status IN ('active', 'archived')(前提是枚举值稳定)- 业务层兜底:先用索引字段快速拉出大集合,再在应用里做二次过滤(适合小结果集+复杂逻辑)
最麻烦的从来不是语法怎么写,而是你不知道字段里有没有NULL、子查询会不会吐NULL、执行计划里到底扫没扫全表——这些点不提前看
EXPLAIN和样本数据,查出来为空时连提示都没有。











