周测:JOIN 陷阱专项
月报交付。复盘这一周踩过的所有 JOIN 坑,来一场限时专项测评。
复盘 · 10 min
专项 12 题 · 110 min
- JOIN 之后 count 变多了,列出三种可能原因
参考答案
三种原因:① 一对多放大--连了 order_items 这类明细表,行数由「多」的一端决定;② 连接条件漏写或恒真(如 user_id = user_id),退化成笛卡尔积,行数 ≈ 两表行数之积;③ 右表连接列不唯一,本该一对一的关系实际是一对多。排查顺序:先笔算期望行数,再看 EXPLAIN 的估行数。
- 一对多连接导致订单金额被重复累加,写出两种修复方案
参考答案
方案一:先聚合再连接--CTE/子查询里按 order_id 把明细 sum 成「每单一行」,再 join 回 orders 做聚合;方案二:不连明细,直接用 orders.total_amount(seed 的 §B④ 已把它对齐成明细之和)。口述要点:重复累加的根源是「左表的列被多端的行重复携带」,先压缩多端就消掉了。
- 用三种写法查「没有下过单的用户」并对比计划
参考答案
三种写法:LEFT JOIN + o.id IS NULL(Hash Left Join,不用去重,大数据量通常最快);NOT EXISTS(优化器常改写成 anti join,计划和 LEFT JOIN 版几乎一样);EXCEPT(两边先去重再做集合减法)。EXPLAIN 对比三种节点,结论写进 notes.md。
- LEFT JOIN 后
count(b.id)与count(*)结果不同,解释原因参考答案
LEFT JOIN 匹配不上时左行保留、右侧全 NULL:count(*) 数行,NULL 行也算 1;count(右表.非空列) 跳过 NULL。差值 = 没匹配上的左行数,「没下单显示 0」就是靠它实现的。
- 剩余 8 题:LeetCode 中等难度多表题
参考答案
选题集中在 INNER/LEFT JOIN、多表 GROUP BY、HAVING--正是本周标「高频」的内容。限时内做完;错题沿用第一周的归因法分三类(没懂概念 / 记不住语法 / 看错题意)记进 mistakes.md。
今日复盘
- GMV 三个口径对账:全部订单 / 只算已支付 / 已支付且剔除负金额
参考答案
月报第一件事是把口径钉死:同一个「GMV」,三种算法三个数--全部订单(含未支付、已取消)、只算已支付、再把 D01 埋的负金额压成 0。三列并排摆出来,差额来自哪一眼就看见。用 LEFT JOIN 是因为 city 可能为空,别让 INNER 把订单吃掉。选哪个口径都算对,错的是不把选择写进报表备注。
select coalesce(u.city, '未知') as 城市, sum(o.total_amount) as 全部口径, sum(o.total_amount) filter (where o.status = 2) as 已支付口径, sum(greatest(o.total_amount, 0)) filter (where o.status = 2) as 剔负口径 from orders o left join users u on u.id = o.user_id group by 1 order by 已支付口径 desc nulls last; - 按「城市 × 状态」双维度统计订单数与 GMV
参考答案
粒度 = GROUP BY 列的全体:一行代表「某城市的某状态」。city 为 NULL、status 为 NULL 的行各自成组,这是口径的一部分,不是脏输出。
select u.city, o.status, count(*) as 单数, sum(o.total_amount) as gmv from orders o join users u on o.user_id = u.id group by u.city, o.status order by u.city nulls last, o.status; - 翻一遍本周 notes.md,标出还讲不利索的点