☰
数据分析面试SQL高频题型与答题模板全解析
2026/10/3 7:46:59 网站建设 项目流程

简介:面向数据分析岗位面试的SQL编程题汇总文档,专为备战数据分析师面试的求职者及希望巩固SQL基础的分析从业者设计。文档选取真实面试题目,覆盖建表与插入数据、排序连接、分组聚合、日期格式化、留存率计算、行转列等高频考点,通过具体业务场景串联常用函数与查询逻辑,帮助读者掌握从数据加载到指标计算的完整分析思路。

资源为单个docx文档,压缩包大小564KB,内容结构清晰,题目、解答思路与知识点总结可对照学习。目前已有1599人学习下载,适合作为面试前查漏补缺的复习材料。

文档不仅给出了可运行的SQL语句,还特别提示了ONLY_FULL_GROUP_BY等常见报错的解决办法,并对比了MySQL与Hive、Spark等分析工具的适用场景。针对活跃度统计、次日/三日/七日留存率计算、行转列等典型题目提供了完整SQL示例与注释,可直接迁移到实际工作中,提升数据处理与面试应答能力。

1. 数据分析面试题里的SQL到底在考什么:不是语法,是取数逻辑

面试官把一张订单表推到你面前,问“每个品类的复购率怎么算”。很多人第一反应是写 group by,写到一半卡住:复购是算同一个用户第二次买,还是第二次买同一个品类?数据分析面试题-SQL面试题汇总.docx 这类题库,刷的人多,刷明白的人少,原因是大多数人把 SQL 面试当成语法考试,而面试官考的是你能不能先定义指标、再拆表关系、最后写取数语句。写业务系统的 SQL 和这个方向完全不同:后端在意索引和事务,数据岗在意口径和粒度。我面过不少候选人,能往下走的不是背题多,而是有一套判断题型、套模板、纠错的流程。下面按这套流程,把高频考点、可抄模板、翻车排查和加分习惯讲清楚。

2. 拆开SQL面试题汇总:四类高频题型的识别方法,别在冷门语法上耗时间

拿到一份 SQL 面试题汇总,不要按顺序从头刷到尾。数据分析面试的题库虽然题目千变万化,但考点高度集中。我刷了几百道题之后发现,高频题型无非就是窗口函数、留存漏斗、去重口径、长题干取数这四类。先学会从题干里识别考点,再决定复习顺序,比盲目刷题效率高得多。

2.1 窗口函数题:三种排名写法的边界条件是第一道分水岭

窗口函数在数据分析面试题里出现频率最高,尤其集中在“每组TopN”和“逐行对比”这两类。题干里出现“每个部门薪资前3”“每个用户最近一笔订单”“上一单和下一单间隔多久”,基本可以判定是窗口函数题。窗口函数的核心不是 over 怎么写,而是分区和排序的语义。

先记住最常见的三个排名函数的区别。同样一批成绩,row_number 给出行号 1、2、3、4,不重不漏;rank 遇到同分跳过名次,两个并列第二之后直接是第四;dense_rank 不跳过,并列第二之后还是第三。写“取每个班级前两名”时,如果两个人并列第二,三种写法结果完全不同。

函数同分处理典型场景
row_number强行走序号,不并列明细取前N条、数据行号
rank同分同名,名次有间隔允许并列且跳名次的排名
dense_rank同分同名,名次连续竞赛排名,名次不跳号

我建议把下面这句 SQL 在本地跑一遍,手动插入几条同分数据,三个函数的差异一眼就记住了:

select student_id, score, row_number() over (order by score desc) as rn, rank() over (order by score desc) as rk, dense_rank() over (order by score desc) as dr from exam_score where class_id = 1;

这里 order by score desc 决定排名方向,没有 partition by 的时候整张表作为一个窗口参与排名,适合全局排序。要按班级排名,加上 partition by class_id 就行。还有个高频追问是“窗口函数和 group by 有什么区别”:group by 会把多行压成一组,窗口函数不改变行数。把这个答出来,面试官基本认可你理解了窗口函数的语义。复习这一步时不用背题,建一张表自己把三种排名各跑一遍,比看十篇总结都管用。

2.2 留存与漏斗题:自连接和时间差函数是题眼

“计算某日活跃用户的次日留存”“统计注册用户7日转化率”,这类题干短,但写错的人特别多。题眼在于把“留存”翻译成一个集合逻辑:今天活跃的用户集合,和N天后活跃的用户集合,取交集再除以上一层基数。落到 SQL 上就是自连接——同一张用户日志表起两个别名,按 user_id 关联,用日期差筛选偏移窗口。

我通常先在草稿纸上画两个圈,左边是“基准日活跃用户”,右边是“N日后活跃用户”,交集是留存用户。对应到 SQL,用 left join 保留所有基准日用户,用连接条件里的 datediff 限定偏移天数,没有回来的用户自然落在 null 上。

这类题最常见的坑是日期维度没去重。用户日志表很可能同一个用户一天登录多次,直接 join 会让行数翻倍,留存率算出来超过 100%,一看就不合理。所以两个子查询里都要先对 user_id 和 dt 做 distinct。我面试时会先看一眼表结构再决定写几层子查询——给的是行为流水表,先缩到用户日粒度;给的是已经聚合好的每日活跃表,就少处理一层。另外,漏斗题的本质也是同样的逻辑:订单到支付到完成的每一层,都先取 distinct user_id 形成用户集合,再逐层关联,不要一上来就 join 订单明细,一个用户多笔订单会把人数放大好几倍。

2.3 去重与指标口径题:count distinct 背后藏着业务口径

“订单表里有 user_id、order_id、商品、金额,统计有成交的用户数。”这种题表面在考 count(distinct user_id),实际在考业务口径:一个用户下单多笔算几个用户?退款订单算不算成交?数据分析师日常被诟病指标对不上,多数不是 SQL 写错,而是口径没对齐,面试官就是靠这种题看你这方面的敏感度。

先分清语法层面的差别。count(distinct user_id) 是精确去重,结果就是唯一用户数。count(distinct user_id, dt) 是按“用户+日期”组合去重,含义完全不一样。很多人以为 distinct 只修饰 user_id,其实 distinct 后面跟的一组字段是整体去重。还有一个高频点:count(*) 统计所有行,count(user_id) 忽略 user_id 为 null 的行。如果宽表里 user_id 是从别的表关联出来的,部分行匹配不上就是 null,直接 count(user_id) 结果偏小。

我答题前习惯先口述三句话:统计的唯一键是什么、空值怎么处理、同一天多条记录要不要合并。三句话说清楚再动手,正确率会高很多。比如“有成交的用户数”,我会先问“一个用户成交多笔算一个还是多笔多算”,再决定用 count(distinct user_id) 还是 count(order_id)。数据分析岗位的面试官不怕你多问一句,只怕你闷头写完一个错误口径。

2.4 长题干取数题:别急着写,先画一张表关系草图

第四类是最筛人的,题干特别长,像“用户表、订单表、退款表,要分析生鲜品类近30天复购用户中使用过优惠券的占比”。这类题不单考一个函数,而是考你把业务问题拆成几步的能力。我的经验是先别碰键盘,在草稿纸上做三件事:列出题干出现的所有实体对应到表,标出表之间的连接字段,确定最终输出粒度。

拆完再写,思路会清晰许多。比如“找出连续三个月升级到VIP的用户”:实体是用户表和会员等级变更表,连接键是 user_id,过滤条件是等级变更记录里类型为升级,指标是每个用户的升级月份序列是否连续,输出粒度是用户维度。剩下的就是取出去重后的 user_id 加月份,再用连续序列分组逻辑套一遍。如果一开始就把所有表 join 在一起,行数膨胀之后再去重,浪费时间和脑力。

长题干题容易犯的另一个错是顺序。正确做法是先分别把各表缩到目标粒度,再连接聚合。比如先按 user_id 聚合出复购用户集合,再关联优惠券记录,最后计算占比。面试官想看到的是你拿到长描述不慌,能拆步骤、能说清每一步的输入输出。我建议把做过的长题干题按“数据表、连接键、指标、输出粒度”四项记成模板笔记,新题往里面套,比硬背题目有效得多。

3. 可直接抄的SQL答题模板:TopN、留存、连续登录三类题的标准写法

识别完题型,下一步就是落到代码。我总结了三个出现率极高、可以套用的模板:分组取 TopN、留存率计算、连续 N 天判断。每套模板都带注释和参数说明,面试时照着这个骨架改条件即可。

3.1 分组Top-N模板:窗口函数加外层过滤,注意排序键和并列处理

先看最经典的“每个部门薪资最高的3个员工”:

select department_id, employee_id, salary from ( select department_id, employee_id, salary, row_number() over ( partition by department_id order by salary desc, employee_id asc ) as rn from employee ) t where t.rn <= 3;

逻辑说明:内层查询按部门分区,对每个部门内部的员工按薪资降序生成行号;外层 where 条件把行号小于等于 3 的行留下。为什么不直接在内层写 where rn <= 3?SQL 的执行顺序里 where 在 select 之前,窗口函数生成的字段在同一层无法被 where 直接引用,所以必须包一层子查询。这个点我在面试中见过好几个人踩,当场报错很影响心态。

参数说明:partition by department_id 表示每个部门一个独立窗口,去掉就是全局排名;order by salary desc 是降序排列,要取最低的把 desc 换成 asc;order by salary desc, employee_id asc 里的 employee_id 是兜底排序,薪资相同时保证结果稳定,也方便面试官预判输出。如果要求并列保留,就把 row_number 换成 rank() 或 dense_rank(),同时要想清楚“前3名”是3个人头还是前3个名次——用 rank() 时,两个并列第二会占两个名次,返回4行。答题前先反问“并列怎么算”,比写完全套才发现语义不对强得多。

3.2 留存率计算模板:自连接加日期差,先做用户去重

再看“计算 2024 年 1 月每日活跃用户在次日、第7日的留存率”:

select a.dt as base_date, datediff(b.dt, a.dt) as diff_day, count(distinct a.user_id) as base_users, count(distinct b.user_id) as retained_users, round(count(distinct b.user_id) / count(distinct a.user_id), 4) as retention_rate from ( select distinct user_id, dt from user_login_log where dt between '2024-01-01' and '2024-01-31' ) a left join ( select distinct user_id, dt from user_login_log ) b on a.user_id = b.user_id and datediff(b.dt, a.dt) in (1, 7) group by a.dt, datediff(b.dt, a.dt) order by a.dt, diff_day;

逻辑说明:子查询 a 取出基准月份内的活跃用户和日期,子查询 b 是完整日志去重后的用户日期集合;连接条件是用户相等且日期差等于 1 或 7。这样同一个基准日的活跃用户,如果在次日和第7日也活跃,就会分别落到 diff_day=1 和 diff_day=7 两组里。count(distinct a.user_id) 是基准日活跃人数,count(distinct b.user_id) 是留存用户数,两者相除得到留存率。

参数说明:datediff(b.dt, a.dt) 计算日期差,不同数据库写法略有差异,SQL Server 里是 datediff(day, a.dt, b.dt),先确认环境再写。留存窗口放在 join 的 on 里而不是 where 里,放在 where 会过滤掉未留存的用户,基准基数直接缺失。两个子查询都做 distinct 是必须的,日志表有重复记录时不去重,留存人数会被放大甚至超过100%。数据量大的时候,先把基准日期区间过滤出来再自连接,避免全表笛卡尔积,这也是慢 SQL 优化意识的体现。

3.3 连续N天登录模板:日期减行号生成分组键

“找出连续3天都有登录的用户”是这类题的典型代表:

select user_id, count(*) as cont_days from ( select user_id, login_date, date_sub(login_date, interval row_number() over ( partition by user_id order by login_date ) day) as group_flag from ( select distinct user_id, login_date from user_login_log ) t ) g group by user_id, group_flag having count(*) >= 3;

逻辑说明:连续日期的特点是相邻日期差固定为1,窗口函数生成的行号也是逐行加1。日期减行号之后,连续日期的 group_flag 相同,中间断开的日期会生成另一个 group_flag。最后按 user_id 和 group_flag 分组,统计连续段的长度,having 过滤出长度大于等于3的段。这个技巧被叫作连续日期分组法,本质是利用两个同步递增序列的差值做分组键。

参数说明:date_sub 是 MySQL 的写法,PostgreSQL 里是 login_date - row_number() 天,写成 login_date - rn * interval '1 day',SQL Server 用 dateadd(day, -rn, login_date)。面试时换了数据库,先确认日期函数再写。row_number() 必须按 login_date 排序,否则差值序列不成立。如果题目要的是“最长连续登录天数”,把 count(*) 改成在外层再取 max 即可;如果只要“出现过连续3天”,当前写法已经满足。中途把 group_flag 那段 select 出来看一眼,你会立刻理解这个技巧的效果。

4. SQL面试避坑手册:从报错到追问的5个高频翻车点

模板会了,还得知道哪里容易翻车。以下五条是我刷题和面试中反复见到的坑,每条按“现象、原因、解决”拆开,面试前过一遍能避开大半问题。

4.1 排名函数选错:同分处理被追问后支支吾吾

现象:题目是“取每个部门工资前三的员工”,代码写的是 row_number(),面试官追问“有两个人工资一样怎么办”,回答“那就按员工ID排”,面试官再问“那另一个并列的人去哪了”,人愣住。

原因:对 row_number 和 rank 的区别停留在背诵层面,没有真正在数据上验证过。两者没有绝对对错,错误的是没搞清业务要的是行号还是名次,也没主动确认并列规则。

解决:看到“取前N”先反问一句“并列怎么算”。明细场景每行对应一条记录,用 row_number 没问题;并列都要保留,换成 rank 或 dense_rank。答题时把“我选 rank 是因为允许并列保留”这句话说出口,比闷头写完有用得多。复习时建一张成绩表,插入两条同分数据,把三种函数各跑一遍,印象会非常深。

4.2 group by 与 select 字段不匹配:本地能跑,面试环境直接报错

现象:本地 MySQL 写 select user_id, order_time, count(*) from orders group by user_id,跑得好好的;面试的在线 SQL 环境换成 PostgreSQL,直接报错“column must appear in the GROUP BY clause or be used in an aggregate function”。

原因:MySQL 默认关闭 ONLY_FULL_GROUP_BY 时,允许 select 非聚合且非分组的字段,结果取哪一行是不确定的。换到严格模式或别的数据库,这种写法立刻暴露。

解决:从第一天就按严格模式写。非聚合字段要么放进 group by,要么用聚合函数包一层,比如 max(order_time)。如果确实要拿到分组内某一行数据,用窗口函数加外层过滤,不要依赖宽松 SQL 模式的隐性行为。日常练习时把数据库的 sql_mode 打开严格模式,能省去大量面试现场的尴尬。

4.3 日期字段当字符串比较:跨月跨年数据对不上

现象:“统计最近7天下单用户”,直接写 where order_date >= '2024-12-28',下个月再跑同样语句范围全错;或者存的是 datetime,和字符串比较导致索引失效,结果边界还多一天少一天。

原因:把日期比较当字符串比较。YYYY-MM-DD 格式的字符串字典序和日期序恰好一致,容易让人产生“这么写没问题”的错觉,但跨月、跨年、带时分秒的场景立刻出错。

解决:用日期函数算边界。写成 where order_date >= date_sub(current_date, interval 6 day),或者 where datediff(current_date, order_date) between 0 and 6。注意“最近7天”包含不包含今天,先和面试官对齐口径,再用函数表达。这个细节在数据分析日常中很常见,也是最能体现严谨程度的地方之一。

4.4 count(*) 与 count(列) 混淆:空值导致统计结果偏小

现象:统计有效订单数,写 select count(order_amount) from orders,结果比预期少,面试官问数据有没有问题,答不上来。

原因:count(order_amount) 会忽略该字段为 null 的行。如果订单金额字段允许为空,或者是从其他表关联出来的列,部分行匹配失败就是 null,count 直接把整行丢掉。

解决:先确认字段是否会为 null。要数行数用 count(*),要数非空值用 count(字段)。如果业务要求计算“成交订单数”且金额为空不应算数,那 count(order_amount) 反而是对的。答题时主动说“我注意到该字段可能为空,所以用 count 过滤掉空值”,这句话比默默写代码更容易拿分。

4.5 join 类型没确认:该左连接写成了内连接,主表数据少一截

现象:题目是“统计每个用户的订单金额,没有订单的用户也要保留”,写的是 join orders 而不是 left join,结果没买过东西的用户全没了,总数和用户表对不上。

原因:join 默认是 inner join,两表都匹配才返回,未匹配的用户被丢弃。对“保留左表所有行”的需求没有形成条件反射。

解决:先判断主表和输出粒度。题干出现“每个用户都保留”“所有品类都显示”,基本确定主表在左,写 left join。右侧表字段做统计时注意 count(orders.order_no) 会忽略未匹配的 null。面试时主动确认一句“这是以用户表为主表,订单表做明细关联,对吧”,既展示沟通习惯,也避免写完才发现语义错。

5. 让SQL答案在面试中加分的三个习惯:口述逻辑、留注释、验边界值

很多候选人不是不会写 SQL,而是写完无法让面试官相信他真懂。我观察到的差距不在代码本身,而在三个执行细节上。

第一个习惯是写之前把口径说清楚。不要拿到题就低头敲键盘,先花十秒说:“我按用户维度去重,统计的是有效订单,连接关系是一对多。”这句话决定了后面所有代码的方向。面试官听到你主动说口径,通常不会催你快点写,反而会顺着你的思路往下问,节奏会舒服很多。第二个习惯是给 SQL 关键步骤写注释。不用每行都写,在子查询开头、窗口函数那层、外层过滤三个位置各写一行即可,比如:

-- 子查询1:按用户取最早下单时间,确定基准点 select user_id, min(order_time) as first_order_time from orders group by user_id;

注释写下来,你的讲述就是照着注释走一遍,逻辑主线不会乱。我平时刷题也强制自己这么做,最直接的好处是答案回看时不卡壳。第三个习惯是写完检查三样东西:重复值、空值、去重粒度。count(distinct user_id) 该不该加 distinct?日期字段有没有可能为 null?输出粒度是每用户一行还是每用户每天一行?三个问题各花十秒,能把 SQL 面试的“玄学挂”概率降下一半。

我到现在都保留一个习惯:任何 SQL 写完,先跑一个小数据量的子集,看结果行数是否符合预期,再往全量上套。面试里没有跑数条件,就口头过一遍“这个 join 之后行数会不会变多”。数据分析面试的 SQL 题,最终考的是一个数据工作者面对不确定数据时的处理方式,而不只是语句对不对。希望这些经验能帮你把这份 SQL 面试题汇总消化成自己的解题框架,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询