Fullstack · 第二轮 System Design 后的 SQL 追问

Salesforce SQL:找上个自然月消费额最高的用户

Salesforce 咖啡点单设计后追问上月消费最高用户;本文明确成功订单、自然月边界、退款与同分口径,给可执行 SQL 和独立数据库验证。

题目重点:Salesforce 咖啡点单设计后追问上月消费最高用户;本文明确成功订单、自然月边界、退款与同分口径,给可执行 SQL 和独立数据库验证。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练表 users(user_id,name) 与 orders(order_id,user_id,status,paid_at_utc,net_amount_cents);paid 订单的 net_amount_cents 是已考虑退款后的净额。真实系统若退款为独立事件,需要先 join/汇总退款再计算净额。
  • ‘上个月’按面试时先约定的业务时区自然月计算,由应用把起止转换成 UTC 参数 :start_utc/:end_utc;使用半开区间 [start,end),不是过去 30 天。
  • 只汇总 status='paid',无已支付订单的用户不参与排名;若多名用户并列第一,训练版全部返回并按 user_id 稳定排序。

解题路线

  1. 先确认口径:支付完成时间还是下单时间、退款抵扣方式、是否跨门店汇总、同分返回一个还是全部。主查询选 paid_at_utc,按 user_id 汇总净分金额。金额用整数最小货币单位或精确 Decimal,避免浮点累计误差。
  2. 用 CTE totals 在上月时间窗内过滤 paid 订单并 GROUP BY user_id;再用 DENSE_RANK() OVER (ORDER BY total_cents DESC) 给同一最大额相同的排名。过滤 spend_rank=1 得全部并列最高用户,最后 join users 取展示姓名。
  3. 当本月无符合条件订单时 totals 为空,输出空表;如业务想显示零消费用户共同第一,必须明确改变候选集与 LEFT JOIN 语义。若记录包含多币种,要先按指定汇率与结算时点归一化,不能直接把 cents 相加。

手动走一遍

训练例:上月 paid 订单 U1 为 800 与 200 分,U2 为 1000 分,U3 的 5000 分订单为 cancelled。聚合后 U1/U2 都是 1000 分,并列返回;U3 不计。恰好在结束时刻的订单属于下一个月,不进入本次查询。

SQL 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

WITH totals AS (
  SELECT o.user_id, SUM(o.net_amount_cents) AS total_cents
  FROM orders AS o
  WHERE o.status = 'paid'
    AND o.paid_at_utc >= :start_utc
    AND o.paid_at_utc < :end_utc
  GROUP BY o.user_id
), ranked AS (
  SELECT user_id, total_cents,
         DENSE_RANK() OVER (ORDER BY total_cents DESC) AS spend_rank
  FROM totals
)
SELECT u.user_id, u.name, ranked.total_cents
FROM ranked
JOIN users AS u ON u.user_id = ranked.user_id
WHERE ranked.spend_rank = 1
ORDER BY u.user_id;

复杂度

N 为扫描的上月已支付订单数、U 为其中不同用户数。带 (status,paid_at_utc,user_id) 等合适索引可缩小时间范围;聚合通常 O(N),对 U 个用户排名排序 O(U log U),中间聚合空间 O(U)。具体执行计划由数据库、数据分布和索引决定,不能把 SQL 文本复杂度当固定性能。

易错点与追问

容易失分的边界

  • 用 NOW()-30 days 代替‘上个自然月’,月底和闰年结果错误。
  • 用 BETWEEN 包含结束时刻,边界订单可能被相邻月份重复计算。
  • 把取消/未支付订单计入消费,或把退款前金额当净消费。
  • ORDER BY total DESC LIMIT 1 无声丢失并列第一用户。
  • 在多币种数据上不换算直接相加金额。

继续深挖

  • 若每家门店各取上月消费最高用户,按 store_id 分区排名,并明确同一订单属于哪家店。
  • 若退款在下月发生,消费额按交易月重述还是退款月抵扣?两种财务口径要有版本。
  • 若查询量高,怎样维护月度用户净消费汇总表并对晚到退款重算?
✓已复制微信号!