左连接后的计数口径
左连接会为未匹配的行补一行右侧全 NULL 的记录,于是“连接后的行数”和“真正匹配的数量”不再相等。COUNT(*) 数的是连接后的行,COUNT(右表列) 只数非 NULL 的匹配行,两者混用是报表数字对不上的高频原因。
基本信息
| COUNT(*) | 连接后的全部行;未匹配的左表行也算 1 行 |
|---|---|
| COUNT(右表列) | 只数非 NULL,即真正匹配上的行;未匹配为 0 |
| COUNT(DISTINCT 列) | 去重计数,右侧被放大时更安全 |
| SUM / AVG | 无匹配行时返回 NULL,而不是 0 |
| COALESCE | 给金额类指标兜底成 0,避免报表显示空值 |
| COUNT(1) | 与 COUNT(*) 口径相同,不会跳过 NULL 行 |
一行三种计数
一行三种计数
SELECT u.id,
COUNT(*) AS joined_rows, -- 连接后的行数
COUNT(o.id) AS matched_orders, -- 真正匹配的订单数
COUNT(DISTINCT o.id) AS distinct_orders, -- 右侧被放大时去重
COALESCE(SUM(o.amount), 0) AS total_amount -- 无订单时补 0
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id
ORDER BY u.id; -- 0 单用户:joined_rows=1,matched_orders=0三种口径怎么选
| 写法 | 统计对象 | 典型用途 |
|---|---|---|
| COUNT(*) | 连接结果的全部行 | 确实要数连接后的行数时 |
| COUNT(o.id) | 右表非 NULL 行数 | 每用户订单数,0 单用户得 0 |
| COUNT(DISTINCT o.id) | 右表去重值个数 | 多表放大后仍要数订单条数 |
| SUM(o.amount) | 非 NULL 金额合计 | 无匹配为 NULL,需 COALESCE 兜底 |
| SUM(CASE WHEN …) | 条件命中计数 | 一次扫描算出多个指标口径 |
典型使用场景
- 用户列表带订单数:0 单用户要显示 0,既不能是 1 也不能是空
- 统计每个商品的被购买次数,右侧明细可能一对多
- 找出没有任何记录的主表行:COUNT(右表列) = 0
- 对比“应有数量”与“实际关联数量”,校验数据完整性
- 报表里汇总行与明细行并存时统一口径,避免总数与表格对不上
- 分渠道统计时对缺失渠道补 0,保证图表每条线都连续
反向技巧:想在左连接后找出“没有匹配”的左表行,直接写 WHERE o.id IS NULL,这是排查孤儿数据最直观的写法,比 NOT IN 更安全。
NULL 会污染聚合运算:无匹配行时 SUM 为 NULL,继续参与后续运算仍然是 NULL,报表里要 COALESCE 兜底;也不要把 NULL 当成 0 参与平均,那会改变业务口径。