9. 常见手撕 SQL 题怎么准备?
手撕 SQL 是测试、测试开发、数据测试、后端接口测试面试中非常常见的环节。很多候选人一听“手撕 SQL”就开始背答案,比如第二高薪资、连续登录、每组 TopN。背题能帮你应付一部分,但真正面试时,表结构稍微变一下、字段名换一下、条件多一条,就容易卡住。
准备手撕 SQL 的正确方式不是死背,而是按题型建立解题框架。你要知道统计类题用 group by,关联类题用 join,去重类题用 distinct 或分组,排名类题可以用窗口函数,连续登录类题要处理日期连续性。面试时还要边写边解释,让面试官知道你的思路,而不是蒙出一个答案。
一、手撕 SQL 面试考什么
手撕 SQL 主要考四种能力:
- 是否理解表结构和业务含义;
- 是否能把需求翻译成查询条件;
- 是否能选择合适的 SQL 结构;
- 是否能处理边界和重复数据。
测试岗位的 SQL 题通常不会特别偏数据库底层,而是偏业务查询和数据统计。比如订单表、用户表、登录表、成绩表、薪资表、商品表、支付流水表。
二、答题前先问清楚三件事
1. 表结构是什么
不要急着写 SQL。先确认表名、字段名、主键、关联字段、时间字段、状态字段。
2. 输出结果是什么
要输出用户 ID 还是用户姓名?要输出数量还是明细?要不要排序?要不要去重?
3. 过滤条件是什么
是否只统计有效数据?是否只统计成功支付?是否排除删除数据?时间范围怎么定义?
很多 SQL 写错不是语法问题,而是需求理解错。
三、高频题型 1:查询重复数据
题目:查询手机号重复的用户。
表:t_user(id, mobile, name)。
select mobile, count(*) as cnt
from t_user
group by mobile
having count(*) > 1;
如果要查重复手机号对应的完整用户记录:
select *
from t_user
where mobile in (
select mobile
from t_user
group by mobile
having count(*) > 1
);
测试场景:注册接口如果没有唯一校验,可能出现重复手机号。这个题能和测试唯一性校验结合起来讲。
四、高频题型 2:查询第二高薪资
表:employee(id, name, salary)。
写法一:去重排序。
select distinct salary
from employee
order by salary desc
limit 1, 1;
如果要查第二高薪资的员工:
select *
from employee
where salary = (
select distinct salary
from employee
order by salary desc
limit 1, 1
);
注意:如果最高薪有多人,distinct 可以避免把同一薪资重复计算。
五、高频题型 3:每组 TopN
题目:查询每个部门工资最高的 3 名员工。
如果数据库支持窗口函数:
select department_id, name, salary
from (
select
department_id,
name,
salary,
row_number() over(partition by department_id order by salary desc) as rn
from employee
) t
where rn <= 3;
如果要考虑并列,可以用 dense_rank()。
测试场景:排行榜、销量榜、成绩榜、消费金额 TopN 都是类似问题。
六、高频题型 4:按日期统计订单量
表:t_order(order_no, status, create_time)。
select date(create_time) as order_date, count(*) as order_count
from t_order
where create_time >= '2026-04-01 00:00:00'
and create_time < '2026-05-01 00:00:00'
group by date(create_time)
order by order_date;
如果只统计已支付订单:
select date(pay_time) as pay_date, count(*) as pay_count
from t_order
where status = 'PAID'
and pay_time >= '2026-04-01 00:00:00'
and pay_time < '2026-05-01 00:00:00'
group by date(pay_time)
order by pay_date;
测试时要关注:时间边界、状态条件、无数据日期是否补 0。
七、高频题型 5:多表关联统计
题目:统计每个用户的订单数量和消费金额。
表:t_user(id, name),t_order(order_no, user_id, status, total_amount)。
select u.id, u.name, count(o.order_no) as order_count, sum(o.total_amount) as total_amount
from t_user u
left join t_order o on u.id = o.user_id and o.status = 'PAID'
group by u.id, u.name;
这里使用 left join,是为了保留没有订单的用户。如果用 inner join,没有订单的用户会消失。
八、高频题型 6:查询从未下单的用户
select u.id, u.name
from t_user u
left join t_order o on u.id = o.user_id
where o.order_no is null;
这个题本质是 left join 找缺失关联数据。测试中经常用来找有主数据但缺少关联数据的问题。
九、高频题型 7:连续登录用户
表:login_log(user_id, login_date),查询连续 3 天登录用户。
如果支持窗口函数,可以用日期减排名的思路:
select user_id
from (
select
user_id,
login_date,
date_sub(login_date, interval row_number() over(partition by user_id order by login_date) day) as flag_date
from (
select distinct user_id, login_date
from login_log
) t1
) t2
group by user_id, flag_date
having count(*) >= 3;
这类题不要求每个测试都背得滚瓜烂熟,但要理解核心:先去重,再按用户排序,再判断日期是否连续。
十、高频题型 8:查询每个商品销量
表:t_order(status),t_order_item(order_no, product_id, quantity)。
select i.product_id, sum(i.quantity) as sale_count
from t_order o
join t_order_item i on o.order_no = i.order_no
where o.status = 'PAID'
group by i.product_id
order by sale_count desc;
测试场景:商品销量榜、后台商品统计、运营报表。
十一、高频题型 9:查询金额异常订单
题目:订单主表金额不等于明细金额之和。
select o.order_no, o.total_amount, sum(i.price * i.quantity) as item_amount
from t_order o
join t_order_item i on o.order_no = i.order_no
group by o.order_no, o.total_amount
having o.total_amount <> sum(i.price * i.quantity);
这类 SQL 非常有测试价值,可以直接用于发现数据一致性问题。
十二、高频题型 10:分页查询
第一页 10 条:
select order_no, create_time
from t_order
order by create_time desc
limit 0, 10;
第二页 10 条:
select order_no, create_time
from t_order
order by create_time desc
limit 10, 10;
面试可能追问深分页慢的问题。可以回答:数据量大时 offset 很大可能慢,可以考虑限制页数、增加查询条件、使用游标分页等。
十三、面试现场怎么表达
手撕 SQL 时不要沉默写。建议按这个节奏:
- 先复述需求;
- 确认表结构和输出字段;
- 判断是否需要 join;
- 判断是否需要 group by;
- 判断是否需要排序、去重、窗口函数;
- 写 SQL;
- 解释边界。
比如题目是“统计每个用户支付成功订单金额”,你可以说:
我先确认订单表里有 user_id、status、total_amount 字段。因为是每个用户,所以要 group by user_id;因为只统计支付成功,所以 where status='PAID';金额用 sum(total_amount)。如果要展示用户名,再关联用户表。
这样即使 SQL 小地方写错,面试官也能看到你的思路。
十四、面试回答模板
如果面试官问“你怎么准备手撕 SQL”,可以这样答:
我会按题型准备,而不是死背单个答案。统计类题重点是 group by、count、sum;关联类题重点是 join 和 left join;去重类题用 distinct 或 group by;排名类题可以用窗口函数;连续登录类题要先去重再判断日期连续。面试现场我会先确认表结构、输出字段和过滤条件,再判断是否需要关联、分组、排序和去重。比如订单金额校验,我会关联订单主表和明细表,用 having 找出主表金额和明细合计不一致的数据。
十五、练习清单
- 查询重复手机号;
- 查询第二高薪资;
- 查询每组 Top3;
- 按日期统计订单数;
- 统计每个用户消费金额;
- 查询从未下单的用户;
- 查询连续 3 天登录用户;
- 统计每个商品销量;
- 查询金额异常订单;
- 分页查询订单列表。
手撕 SQL 的核心是题型思维。你只要把常见题型拆清楚,再结合测试业务场景讲出来,面试就不会只是“背 SQL”,而是展示你解决数据问题的能力。
配套刷题:

