SQL面试聚焦多表关联、时间窗口、去重统计、空值处理四大高频痛点,强调将业务需求精准翻译为可验证的SQL逻辑。

真实业务场景中的SQL面试题,不考冷门函数,专挑高频痛点:多表关联混乱、时间窗口计算不准、去重统计出错、空值处理踩坑。核心是看你会不会把业务需求准确翻译成SQL逻辑。
一、订单漏斗分析:从下单到支付的转化率怎么算?
常见错误是直接用 COUNT(*) 做分母,忽略用户可能多次下单或跨天支付。正确思路是按“用户+日期”粒度对齐,再逐层下钻。
关键操作:
- 用 LEFT JOIN 关联订单表和支付表,ON order_id 且支付时间 ≥ 下单时间(防未来支付干扰)
- 用 CASE WHEN 判断是否支付成功(pay_status = 'success'),避免 COUNT(pay_id) 隐式过滤 NULL
- 分母用 COUNT(DISTINCT user_id, DATE(create_time)),分子用 COUNT(DISTINCT CASE WHEN paid THEN user_id END)
二、近7天活跃用户留存:次日/7日留存怎么写不翻车?
留存本质是“今天来了的人,X天后还来没?”,不是简单 group by 日期后求比例。必须先锚定“首访日”,再查该批人在后续日期是否存在行为。
实用写法:
- 子查询先取“近7天每天的新首次访问用户”(用 MIN(login_date) over(partition by user_id) 找首访日)
- 主查询用自连接:A表是首访记录,B表是该用户在首访后第N天的登录记录(B.login_date = DATE_ADD(A.first_day, INTERVAL N DAY))
- GROUP BY A.first_day 后,用 COUNT(B.user_id)/COUNT(A.user_id) 算留存率,自动处理重复登录和跨周期问题
三、商品销量TOP3:同销量并列怎么处理?
用 LIMIT 3 会丢数据;用 ROW_NUMBER() 会强行拆并列;真实业务要保留所有并列第三名。
推荐方案:
- 用 RANK() 或 DENSE_RANK(),按销量 DESC 排序
- WHERE 排名
- 若需兼容老版本MySQL(无窗口函数),可用自连接统计大于当前销量的记录数 + 1,再过滤
四、用户等级动态更新:如何用一条SQL批量更新会员等级?
别写循环或多个UPDATE。真实业务中等级常由累计消费、最近30天订单数、好评率等组合决定,需一次计算、一次落地。
高效做法:
- 用 UPDATE + JOIN(MySQL)或 UPDATE FROM(PostgreSQL)语法
- JOIN 子查询:聚合用户历史数据(SUM(amount), COUNT(CASE WHEN dt >= TODAY-30 THEN 1 END))
- SET level = CASE WHEN total_amt >= 10000 THEN 'VIP' WHEN recent_orders > 5 THEN 'Gold' ELSE 'Normal' END
- 务必加 WHERE 条件限制更新范围(如只更新 status='active' 的用户),避免锁全表
业务SQL不是炫技,而是用最稳的写法,把“人话需求”变成“机器可执行、结果可验证”的语句。字段别名要见名知义,JOIN 条件必写驱动表,NULL 处理提前想好默认值——这些细节,比写出复杂窗口函数更能体现工程素养。










