部门工资前三高的所有员工

这篇博客探讨了一个SQL查询问题,涉及从`employee`和`department`表中找出每个部门薪资排名前三的员工。当部门人数不足三人时,查询需包含所有员工。解决方案是通过子查询获取部门第三高薪资,并设置若无第三高则为0,然后筛选出薪资大于等于此值的员工。

前言

力扣上发现的mysql查询题,题目链接

表结构sql 和 基本数据

DROP TABLE IF EXISTS `department`;
CREATE TABLE `department`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of department
-- ----------------------------
INSERT INTO `department` VALUES (1, 'IT');
INSERT INTO `department` VALUES (2, 'Sales');

-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `salary` decimal(10, 2) NULL DEFAULT NULL,
  `departmentId` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of employee
-- ----------------------------
INSERT INTO `employee` VALUES (1, 'Joe', 85000.00, 1);
INSERT INTO `employee` VALUES (2, 'Henry', 80000.00, 2);
INSERT INTO `employee` VALUES (3, 'Sam', 60000.00, 2);
INSERT INTO `employee` VALUES (4, 'Max', 90000.00, 1);
INSERT INTO `employee` VALUES (5, 'Janet', 69000.00, 1);
INSERT INTO `employee` VALUES (6, 'Randy', 85000.00, 1);
INSERT INTO `employee` VALUES (7, 'Will', 70000.00, 1);

SET FOREIGN_KEY_CHECKS = 1;

思路

本来是想用limit直接查出每个部门薪资前3的就行了,结果题目要求薪资相等的并列,这样就行不通了。
想了下可以筛选查出部门第三高的薪资,然后再查出大于等于这个薪资的不就完了。
然后发现又埋了个坑,如果一个部门小于三个人那不就没有第三高(查出薪资结果为null)的吗。想了好久,同然灵机一动加个ifnull=0不就行了吗 ,小于三人,所有人都查出来,薪资大于0,不就所有都出来了吗。

查询sql

SELECT
	a.NAME AS Department,
	e.NAME AS Employee,
	e.salary AS Salary 
FROM
	employee e
	LEFT JOIN (
	SELECT
		id,
		ifnull(/*这里是查询出部门第三高的薪资,如果没有第三高,说明该部门小于三人,把薪资门槛设置成0就行了*/
			( SELECT salary FROM employee WHERE departmentid = d.id GROUP BY departmentid, salary ORDER BY departmentid, salary DESC LIMIT 2, 1 ),
			0 
		) AS min,
		d.NAME 
	FROM
		department d 
	) a ON a.id = e.departmentId 
WHERE
	e.salary >= a.min 
ORDER BY
	e.departmentid,
	e.salary DESC;
<think>嗯,用户现在问的是关于SQL查询的问题,需要根据给定的emp表结构,找出每个部门薪水三高员工。首先,我需要确认emp表的结构,但用户可能没有给出具体的字段,不过通常emp表会有部门编号(dept_id)、员工信息(比如员工ID、姓名)和薪水(salary)这些字段。假设表结构包括dept_id、emp_id、emp_name、salary这几个主要字段。 接下来,用户的问题是要每个部门三高薪水的员工。这时候,我需要考虑如何按部门分组,然后在每个部门内根据薪水排序,选取三名。在SQL中,通常可以使用窗口函数来实现这种需求,尤其是使用ROW_NUMBER()、RANK()或DENSE_RANK()函数。 不过需要注意不同数据库的兼容性,比如MySQL在8.0以上才支持窗口函数,而旧版本可能需要使用变量或其他方法。但用户没有指定数据库类型,这里假设可以使用窗口函数,因为现在大多数现代数据库都支持。 使用窗口函数的话,思路是对每个部门(PARTITION BY dept_id)内的员工按薪水降序排序(ORDER BY salary DESC),然后给每个员工分配一个排名。这里需要考虑是否有并列的情况,比如如果两个员工薪水相同,是否应该算同一个排名。如果用户希望严格三名,不管是否有并列,可能需要用ROW_NUMBER(),但如果有并列的情况,使用RANK()或DENSE_RANK()会更合适。例如,如果部门内有两并列第一,那么RANK()会给出1,1,3,而DENSE_RANK()会给出1,1,2,这时候用户可能需要明确需求,但通常“三高”可能允许并列,所以使用DENSE_RANK()可能更合适,但需要根据具体情况判断。 不过用户的问题可能希望每个部门最多三个员工,不管是否有并列,这时候用ROW_NUMBER()更准确,因为即使有相同薪水,也会给不同的行号,但这样可能排除掉并列的情况。例如,如果第三名有两个相同薪水的员工,用ROW_NUMBER()只会取其中一个,这可能不符合用户需求。因此需要进一步澄清,但用户可能期望的是包括所有三高薪水的员工,包括并列的情况,这时候可能需要使用RANK()或DENSE_RANK(),并在外层查询中筛选排名小于等于3。 所以可能的SQL语句结构是: SELECT dept_id, emp_id, emp_name, salary FROM ( SELECT dept_id, emp_id, emp_name, salary, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank FROM emp ) AS ranked WHERE rank <= 3; 但这里如果用DENSE_RANK(),如果有并列的情况,可能会返回多于三个员工,例如三个排名是1,2,3,但如果有多个员工在第三名,就会都包括进来。而如果用户明确要求每个部门最多三个员工,不管是否有并列,可能需要用ROW_NUMBER(),但这样可能不符合实际需求,因为实际中可能需要包括所有并列的情况。 因此,需要确认用户的需求,但通常这种问题要求的是三高的薪水,包括并列的情况,所以使用DENSE_RANK()可能更合适。或者用户可能需要每个部门最多三个不同的薪水值的三名,这时候RANK()可能会更合适,因为RANK()会跳过相同的排名,例如,如果有两个第一名,下一个是第三名,但这样可能不符合三名的需求。 因此,正确的做法可能需要根据具体情况选择窗口函数,但一般情况下,使用DENSE_RANK()可以确保所有薪水三高员工都被选中,包括并列的情况。因此最终的SQL可能如下: SELECT dept_id, emp_id, emp_name, salary FROM ( SELECT dept_id, emp_id, emp_name, salary, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dr FROM emp ) t WHERE dr <= 3; 不过,用户的问题可能希望每个部门返回不超过三个员工,这时候可能需要用ROW_NUMBER(),但这样会忽略并列的情况。如果用户希望包括并列的情况,比如部门有四个员工,其中第三名有两个相同薪水,那么应该返回这四个员工中的三高,即包括那两个并列第三的员工,这时候用DENSE_RANK() WHERE dr <=3 是正确的。 另外,还需要考虑薪水是否为NULL的情况,但假设表中salary字段不为空,或者在排序时处理NULL值,可能需要使用ORDER BY salary DESC NULLS LAST,但根据表结构可能不需要。 总结,正确的SQL应该是使用窗口函数DENSE_RANK(),按部门分区,按薪水降序排序,然后筛选排名三的记录。因此,写出对应的SQL语句即可。</think>### **分步解析与 SQL 实现** --- #### **1. 数据表结构假设** 假设 `emp` 表包含以下字段: - `dept_id`(部门编号) - `emp_id`(员工编号) - `emp_name`(员工姓名) - `salary`(员工薪水) --- #### **2. 核心需求分析** 要求 **每个部门** 内 **薪水三高** 的员工,需满足以下条件: - **部门分组**:按 `dept_id` 分组。 - **排序规则**:同一部门内按 `salary` 降序排列。 - **并列处理**:允许同一部门存在并列薪水(例如两个员工并列第三名时均需返回)。 --- #### **3. 使用窗口函数实现** 通过 `DENSE_RANK()` 窗口函数处理并列排名,确保符合三高的逻辑要求。 ```sql SELECT dept_id, emp_id, emp_name, salary FROM ( SELECT dept_id, emp_id, emp_name, salary, DENSE_RANK() OVER ( PARTITION BY dept_id ORDER BY salary DESC ) AS salary_rank -- 按部门分组,薪水降序排名 FROM emp ) AS ranked_emp WHERE salary_rank <= 3; -- 筛选排名三的记录 ``` --- #### **4. 关键语法解释** - **`DENSE_RANK()`** 为每行分配唯一排名,**允许并列**(例如两个员工薪水相同则排名相同,下一名次不跳过)。 示例:`[1, 2, 2, 3]`(若使用 `RANK()` 则会生成 `[1, 2, 2, 4]`)。 - **`PARTITION BY dept_id`** 按部门分组计算排名,确保每个部门独立排序。 - **`ORDER BY salary DESC`** 按薪水降序排列,确保高薪员工排名靠。 --- #### **5. 结果示例** 假设某部门数据如下: | dept_id | emp_id | emp_name | salary | |---------|--------|----------|--------| | 101 | 1001 | Alice | 9000 | | 101 | 1002 | Bob | 8500 | | 101 | 1003 | Charlie | 8500 | | 101 | 1004 | David | 8000 | 执行后输出: | dept_id | emp_id | emp_name | salary | salary_rank | |---------|--------|----------|--------|-------------| | 101 | 1001 | Alice | 9000 | 1 | | 101 | 1002 | Bob | 8500 | 2 | | 101 | 1003 | Charlie | 8500 | 2 | | 101 | 1004 | David | 8000 | 3 | --- #### **6. 扩展说明** - **性能优化**:若表数据量大,建议对 `dept_id` 和 `salary` 建立索引。 - **去重场景**:若需严格返回最多三条记录(忽略并列),改用 `ROW_NUMBER()`: ```sql ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS salary_rank ``` 此时结果为 `[1, 2, 3, 4]`,仅筛选 `salary_rank <= 3`。 --- ### **总结** 通过窗口函数 `DENSE_RANK()` 可高效实现 **分组排序取 Top N** 的需求,兼顾逻辑准确性与代码简洁性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值