应统一使用sum(case when then 1 else 0 end)替代count(if())或count(case),因其对null处理明确、支持加权计数、逻辑清晰且跨数据库兼容;count(if())非标准sql,部分数据库不支持,而sum(case)是ansi标准,优化器友好且避免重复扫描。

用 SUM(CASE WHEN ...) 替代 COUNT(IF(...))
多数人第一反应是用 COUNT(IF(status = 'completed', 1, NULL)),但这个写法在 MySQL 中虽能跑通,在 PostgreSQL 或 SQL Server 上会报错——IF 不是标准 SQL 函数。真正跨数据库兼容、语义清晰、且优化器友好的写法是 SUM(CASE WHEN ... THEN 1 ELSE 0 END)。
关键点在于:CASE WHEN 是 ANSI 标准,所有主流数据库都支持;SUM 对整数求和比 COUNT 处理混合类型(含 NULL)更轻量;而且当多个条件并列时,可一次性写出全部分支,避免重复扫描:
SELECT SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_cnt, SUM(CASE WHEN status = 'pending' AND created_at > '2025-01-01' THEN 1 ELSE 0 END) AS recent_pending_cnt, SUM(CASE WHEN amount > 1000 AND user_tier = 'vip' THEN 1 ELSE 0 END) AS high_value_vip_cnt FROM orders;
注意:别用 COUNT(CASE WHEN ... THEN 1 END) ——这会把不满足条件的行当作 NULL 忽略,结果看似一样,但部分数据库(如旧版 Presto)对 COUNT(NULL) 的处理逻辑不稳定,而 SUM 始终明确。
WHERE 子句里别对字段套函数
常见性能杀手是这类写法:WHERE YEAR(order_date) = 2025 或 WHERE UPPER(name) = 'ALICE'。哪怕 order_date 上有索引,加了函数后索引就失效,强制全表扫描。
正确做法是把函数移到右边,让左边保持原始列引用:
WHERE order_date >= '2025-01-01' AND order_date-
WHERE name = 'alice' COLLATE utf8mb4_0900_as_cs(如果需要大小写敏感) - 真要模糊前缀匹配?用
WHERE name LIKE 'ali%',确保该列有 B-tree 索引
复合索引也得按这个逻辑设计。比如常查 status 和 created_at 组合,建索引应为 CREATE INDEX idx_status_created ON orders (status, created_at),而不是反过来——因为等值条件(status = 'completed')必须放前面,范围条件(created_at > ...)才能生效。
复杂条件计数优先走子查询过滤,而非外层 CASE
当条件涉及多表关联、窗口函数或高选择性过滤(比如“近 7 天内下单且完成支付且未退款的用户数”),硬塞进一个 CASE 里会导致整个 orders 表被全扫一遍,再逐行判断。此时应该先缩小数据集:
SELECT COUNT(*) AS qualified_user_count
FROM (
SELECT DISTINCT user_id
FROM orders o
JOIN payments p ON o.order_id = p.order_id
WHERE o.status = 'completed'
AND p.status = 'paid'
AND o.created_at >= NOW() - INTERVAL '7 days'
AND NOT EXISTS (
SELECT 1 FROM refunds r WHERE r.order_id = o.order_id
)
) t;
这种写法让数据库能在子查询阶段就利用 idx_status_created、payments(order_id, status) 等索引快速定位,再做去重和关联,比在外层 CASE 里拼一堆 JOIN + EXISTS 更可控。尤其当结果集远小于原表时,性能差距可达数量级。
别忽略执行计划里的 “rows examined”
写完条件计数语句后,一定要跑 EXPLAIN(MySQL)或 EXPLAIN ANALYZE(PostgreSQL)。重点关注两列:
-
rows或Plan Rows:预估扫描行数,是否接近表总行数? -
Extra或Buffers:有没有出现Using temporary、Using filesort、Seq Scan?
如果发现扫描行数远高于实际匹配数,说明索引没用上,或者条件写法触发了隐式转换(比如用字符串比较数字字段:WHERE user_id = '123')。这时候再回看 WHERE 子句,逐个检查字段类型、函数包裹、NULL 处理是否合理。
最常被跳过的细节是:你以为加了索引就万事大吉,但只要 WHERE 里有一个条件让索引失效,整个联合索引就可能被弃用——不是“部分生效”,而是“全盘不用”。











