字节跳动大数据面试SQL-每个部门工资前3高的员工
一、题目背景
这道题是 LeetCode 185 的原题变体,字节跳动、阿里、腾讯的面试中频繁出现。它考察窗口函数中最常用的 ROW_NUMBER / RANK / DENSE_RANK 三者之间的区别——这是面试官最喜欢追问的点。
业务场景:HR 系统需要出"各部门薪资 Top 3 报表",用于薪酬对标分析。注意:如果第3名有并列,用 DENSE_RANK 会包含所有并列者。
二、题目
有两张表:员工表 t13_zj_employee 和薪资表 t13_zj_salary。请找出每个部门工资前3高的员工。
t13_zj_employee 表
+----+--------+----------+
| id | name | dept_id |
+----+--------+----------+
| 1 | 张三 | D001 |
| 2 | 李四 | D001 |
| 3 | 王五 | D001 |
| 4 | 赵六 | D001 |
| 5 | 孙七 | D002 |
| 6 | 周八 | D002 |
| 7 | 吴九 | D002 |
+----+--------+----------+
t13_zj_salary 表
+----+--------+
| id | salary |
+----+--------+
| 1 | 50000 |
| 2 | 45000 |
| 3 | 45000 |
| 4 | 30000 |
| 5 | 60000 |
| 6 | 55000 |
| 7 | 40000 |
+----+--------+
期望输出:每个部门薪资前3高的员工姓名、部门和薪资。
三、思路分析
- JOIN 两表关联员工和薪资
- 用窗口函数
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC)排名 - WHERE rn 小于等于 3 取前3
ROW_NUMBER vs RANK vs DENSE_RANK:
ROW_NUMBER:1,2,3,4... 即使值相同也不并列RANK:1,1,3,4... 值相同并列,后续跳过DENSE_RANK:1,1,2,3... 值相同并列,后续不跳过
| 维度 | 评分 |
|---|---|
| 题目难度 | ⭐️⭐️ |
| 题目清晰度 | ⭐️⭐️⭐️⭐️⭐️ |
| 业务常见度 | ⭐️⭐️⭐️⭐️⭐️ |
四、逐步推导
步骤1:JOIN 并排名
SELECT
e.name,
e.dept_id,
s.salary,
DENSE_RANK() OVER (PARTITION BY e.dept_id ORDER BY s.salary DESC) AS rn
FROM t13_zj_employee e
JOIN t13_zj_salary s ON e.id = s.id;
+-------+----------+---------+-----+
| name | dept_id | salary | rn |
+-------+----------+---------+-----+
| 张三 | D001 | 50000 | 1 |
| 李四 | D001 | 45000 | 2 |
| 王五 | D001 | 45000 | 2 |
| 赵六 | D001 | 30000 | 3 |
| 孙七 | D002 | 60000 | 1 |
| 周八 | D002 | 55000 | 2 |
| 吴九 | D002 | 40000 | 3 |
+-------+----------+---------+-----+
7 rows selected (8.815 seconds)(https://www.dwsql.com)
D001:张三(1)、李四和王五并列(2)、赵六(3)。前3包含4人——这正是 DENSE_RANK 的效果。
步骤2:筛选前3
SELECT name, dept_id, salary
FROM (
SELECT
e.name, e.dept_id, s.salary,
DENSE_RANK() OVER (PARTITION BY e.dept_id ORDER BY s.salary DESC) AS rn
FROM t13_zj_employee e
JOIN t13_zj_salary s ON e.id = s.id
) t
WHERE rn <= 3;
最终结果:
+-------+----------+---------+
| name | dept_id | salary |
+-------+----------+---------+
| 张三 | D001 | 50000 |
| 李四 | D001 | 45000 |
| 王五 | D001 | 45000 |
| 赵六 | D001 | 30000 |
| 孙七 | D002 | 60000 |
| 周八 | D002 | 55000 |
| 吴九 | D002 | 40000 |
+-------+----------+---------+
7 rows selected (0.655 seconds)(https://www.dwsql.com)
D001 有 4 人(因为李四和王五并列第二,赵六排第三也被包含)。如果面试官说"只要严格3个人",把 DENSE_RANK 换成 ROW_NUMBER 即可。
五、常见坑点
坑1:RANK vs DENSE_RANK vs ROW_NUMBER
面试官大概率会追问三者区别。简洁答案:ROW_NUMBER 不并列,RANK 并列跳号,DENSE_RANK 并列不跳号。
坑2:排序方向
窗口函数默认 ORDER BY 是升序。取"前N高"必须显式写 DESC。忘记写 DESC 会取到工资最低的N个人。
坑3:WHERE vs QUALIFY
Spark SQL 不支持 QUALIFY(某些数据库如 Snowflake 支持)。必须用子查询 + WHERE。
六、举一反三
- 每组前N%:
NTILE(10) OVER (PARTITION BY dept_id ORDER BY salary DESC)→ 取 NTILE=1 的组 - 累计薪资占比:
SUM(salary) OVER (PARTITION BY dept_id ORDER BY salary DESC) / SUM(salary) OVER (PARTITION BY dept_id)计算累计占比
七、知识点总结
| 考点 | 说明 |
|---|---|
| PARTITION BY | 按部门分区,排名在区内独立计算 |
| ORDER BY DESC | 降序排列,最高薪排第1 |
| DENSE_RANK | 并列不跳号,前3可能多于3人 |
| 子查询 + WHERE | Spark SQL 筛选排名,替代 QUALIFY |
八、建表语句和数据插入
点击展开 DDL & DML
CREATE TABLE IF NOT EXISTS t13_zj_employee (
id INT, name STRING, dept_id STRING
);
CREATE TABLE IF NOT EXISTS t13_zj_salary (
id INT, salary BIGINT
);
INSERT INTO t13_zj_employee VALUES
(1,'张三','D001'),(2,'李四','D001'),(3,'王五','D001'),(4,'赵六','D001'),
(5,'孙七','D002'),(6,'周八','D002'),(7,'吴九','D002');
INSERT INTO t13_zj_salary VALUES
(1,50000),(2,45000),(3,45000),(4,30000),(5,60000),(6,55000),(7,40000);
「数据仓库技术」文章同步更新,不错过每一篇干货

备注「数据仓库技术」加入社群,每日一道大厂SQL真题
