您的位置:首页 > 手游攻略 > MySQL数据库求部门前三薪资排名的7种高效方案详细解析实用指南

MySQL数据库求部门前三薪资排名的7种高效方案详细解析实用指南

作者:互联网  时间: 2026-11-11 09:10:01  

平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“MySQL数据库求部门前三薪资排名的7种高效方案详细解析”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。

前言:为什么部门薪资排名如此重要?

实际处理时,在企业级数据库应用里,部门薪资排名是一个极其常用的业务需求。无论是绩效考核、薪酬分析,还是人才梯队建设,都需准确、高效地拿到各部门的薪资分布情况。然而,这个看似轻松的问题背后,却隐藏着多种不同的实现方案,每种方案都有其独特的优缺点和适用场景。

本文将带领大家深入剖析 MySQL 中求解部门前三薪资的 7 种经典方法实际处理时,,无论你是 MySQL 5.7 的老用户,还是 MySQL 8.0 的新玩家,都能在这里找到适合自己的解决方案。

测试数据准备

在开始之前,我们先新建一个标准的测试数据表,便于后续的示例演示:

-- 创建员工表
CREATE TABLE employees (
    employee_id INT PRIMARY KEY AUTO_INCREMENT,
    employee_name VARCHAR(50),
    department_id INT,
    salary DECIMAL(10, 2),
    hire_date DATE
);

-- 插入测试数据
INSERT INTO employees (employee_name, department_id, salary, hire_date) VALUES
('张三', 1, 8500.00, '2020-03-15'),
('李四', 1, 9500.00, '2019-07-20'),
('王五', 1, 9500.00, '2021-01-10'), -- 与李四薪资相同
('赵六', 1, 7800.00, '2020-09-05'),
('钱七', 1, 7200.00, '2022-03-18'),
('孙八', 2, 9200.00, '2018-11-30'),
('周九', 2, 8800.00, '2020-05-22'),
('吴十', 2, 8800.00, '2019-12-15'), -- 与周九薪资相同
('郑十一', 2, 8100.00, '2021-08-14'),
('王十二', 2, 7600.00, '2022-02-28');

-- 创建部门表
CREATE TABLE departments (
    department_id INT PRIMARY KEY,
    department_name VARCHAR(50)
);

INSERT INTO departments VALUES
(1, '技术部'),
(2, '市场部'),
(3, '人事部'); -- 注意:人事部没有员工,用于测试边界情况

方法一:DENSE_RANK() 窗口函数(MySQL 8.0+ 首选方案)

核心原理

DENSE_RANK() 是 MySQL 8.0 引入的窗口函数,它为结果集中的每一行分配一个排名,相同值获得相同排名,且排名连续不跳跃。这正是处理"部门前三薪资"问题的完美工具。

完整实现代码

-- 基础版本:获取部门薪资前三的员工
WITH RankedEmployees AS (
    SELECT
        d.department_name,
        e.employee_name,
        e.salary,
        DENSE_RANK() OVER (
            PARTITION BY e.department_id
            ORDER BY e.salary DESC
        ) AS salary_rank
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
)
SELECT
    department_name,
    employee_name,
    salary,
    salary_rank
FROM RankedEmployees
WHERE salary_rank <= 3
ORDER BY department_name, salary_rank, salary DESC;

-- 增强版本:包含更多业务信息
WITH DepartmentSalaryRank AS (
    SELECT
        d.department_id,
        d.department_name,
        e.employee_id,
        e.employee_name,
        e.salary,
        e.hire_date,
        DENSE_RANK() OVER (
            PARTITION BY e.department_id
            ORDER BY e.salary DESC
        ) AS salary_rank,
        COUNT(*) OVER (PARTITION BY e.department_id) AS dept_employee_count,
        AVG(salary) OVER (PARTITION BY e.department_id) AS dept_avg_salary
    FROM employees e
    RIGHT JOIN departments d ON e.department_id = d.department_id
)
SELECT
    department_name,
    employee_name,
    salary,
    salary_rank,
    hire_date,
    dept_employee_count,
    ROUND(dept_avg_salary, 2) AS dept_avg_salary,
    CASE
        WHEN salary_rank = 1 THEN '薪资最高'
        WHEN salary_rank = 2 THEN '薪资次高'
        WHEN salary_rank = 3 THEN '薪资第三'
    END AS rank_description
FROM DepartmentSalaryRank
WHERE salary_rank <= 3
ORDER BY department_id, salary_rank;

执行结果示例

| department_name | employee_name | salary  | salary_rank | hire_date   | dept_employee_count | dept_avg_salary | rank_description |
|-----------------|---------------|---------|-------------|-------------|---------------------|-----------------|------------------|
| 技术部 | 李四 | 9500.00 | 1 | 2019-07-20 | 5 | 8500.00 | 薪资最高 |
| 技术部 | 王五 | 9500.00 | 1 | 2021-01-10 | 5 | 8500.00 | 薪资最高 |
| 技术部 | 张三 | 8500.00 | 2 | 2020-03-15 | 5 | 8500.00 | 薪资次高 |
| 技术部 | 赵六 | 7800.00 | 3 | 2020-09-05 | 5 | 8500.00 | 薪资第三 |
| 市场部 | 孙八 | 9200.00 | 1 | 2018-11-30 | 5 | 8500.00 | 薪资最高 |
| 市场部 | 周九 | 8800.00 | 2 | 2020-05-22 | 5 | 8500.00 | 薪资次高 |
| 市场部 | 吴十 | 8800.00 | 2 | 2019-12-15 | 5 | 8500.00 | 薪资次高 |

性能分析与优化建议

-- 1. 为窗口函数创建合适索引
CREATE INDEX idx_department_salary ON employees(department_id, salary DESC);

-- 2. 查看执行计划
EXPLAIN
WITH RankedEmployees AS (
    SELECT
        employee_id,
        department_id,
        salary,
        DENSE_RANK() OVER (
            PARTITION BY department_id
            ORDER BY salary DESC
        ) AS salary_rank
    FROM employees
)
SELECT * FROM RankedEmployees WHERE salary_rank <= 3;

-- 3. 使用窗口函数框架进行性能优化
SELECT
    department_id,
    employee_name,
    salary,
    DENSE_RANK() OVER w AS salary_rank,
    FIRST_VALUE(employee_name) OVER w AS top_earner,
    NTH_VALUE(salary, 3) OVER w AS third_salary
FROM employees
WINDOW w AS (
    PARTITION BY department_id
    ORDER BY salary DESC
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
ORDER BY department_id, salary_rank;

适用场景

  1. MySQL 8.0及以上版本
  2. 需处理薪资并列的情况
  3. 需连续排名(不跳号)
  4. 大数据量下需高性能查询

方法二:RANK() 窗口函数(处理跳号排名)

与 DENSE_RANK() 的关键区别

RANK() 函数同样处理同时列情况,但排名会"跳跃"。比如,如果有两个第一名,下一个就是第三名。

实现代码对比

-- 对比 RANK() 和 DENSE_RANK() 的区别
SELECT
    department_id,
    employee_name,
    salary,
    RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank_with_gap,
    DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank_without_gap,
    ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC, employee_id) AS row_num
FROM employees
WHERE department_id = 1
ORDER BY salary DESC;

-- 使用 RANK() 获取前三名(可能返回多于3条记录)
WITH RankedSalaries AS (
    SELECT
        d.department_name,
        e.employee_name,
        e.salary,
        RANK() OVER (
            PARTITION BY e.department_id
            ORDER BY e.salary DESC
        ) AS salary_rank
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
)
SELECT * FROM RankedSalaries
WHERE salary_rank <= 3
ORDER BY department_name, salary_rank;

执行结果差异

-- RANK() 结果示例(技术部)
| employee_name | salary | rank_with_gap | rank_without_gap | row_num |
|---------------|---------|---------------|------------------|---------|
| 李四 | 9500.00 | 1 | 1 | 1 |
| 王五 | 9500.00 | 1 | 1 | 2 |
| 张三 | 8500.00 | 3 | 2 | 3 | ← 注意:RANK() 这里是3
| 赵六 | 7800.00 | 4 | 3 | 4 |
| 钱七 | 7200.00 | 5 | 4 | 5 |

适用场景

  1. 需传统体育比赛式的排名(有并列时跳号)
  2. 统计"金牌、银牌、铜牌"获得者数量
  3. 需显示实际位置而非连续排名

方法三:ROW_NUMBER() 窗口函数(严格顺序排名)

核心特点

ROW_NUMBER() 为每一行分配唯一的连续序号,即使值相同也会分配不同的序号。这不处理并列情况。

实现代码

-- 基础用法:每个部门严格返回3条记录
WITH NumberedEmployees AS (
    SELECT
        d.department_name,
        e.employee_name,
        e.salary,
        e.hire_date,
        ROW_NUMBER() OVER (
            PARTITION BY e.department_id
            ORDER BY e.salary DESC, e.hire_date ASC
        ) AS row_num
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
)
SELECT
    department_name,
    employee_name,
    salary,
    hire_date,
    row_num
FROM NumberedEmployees
WHERE row_num <= 3
ORDER BY department_name, row_num;

-- 处理并列情况的变通方案
WITH RankedEmployees AS (
    SELECT
        department_id,
        employee_name,
        salary,
        ROW_NUMBER() OVER (
            PARTITION BY department_id, salary
            ORDER BY hire_date
        ) AS same_salary_order,
        ROW_NUMBER() OVER (
            PARTITION BY department_id
            ORDER BY salary DESC, hire_date ASC
        ) AS overall_order
    FROM employees
)
SELECT
    department_id,
    employee_name,
    salary,
    overall_order,
    CASE
        WHEN same_salary_order > 1 THEN CONCAT('(并列)', same_salary_order)
        ELSE ''
    END AS tie_note
FROM RankedEmployees
WHERE overall_order <= 5
ORDER BY department_id, overall_order;

适用场景

  1. 需每个部门固定得到N条记录
  2. 已有明确的并列处理规则(如按入职时间排序)
  3. 分页查询中的排名需求

方法四:相关子查询(MySQL 5.7及以下版本通用方案)

核心原理

实际处理时,对于每个员工,借助子查询计算同部门中薪资高于他的人数。如果这个人数小于3,则该员工薪资排名前三。

经典实现

-- 基础版本:使用 COUNT(DISTINCT)
SELECT
    d.department_name,
    e.employee_name,
    e.salary,
    (SELECT COUNT(DISTINCT e2.salary)
     FROM employees e2
     WHERE e2.department_id = e.department_id
       AND e2.salary > e.salary) AS higher_salary_count
FROM employees e
JOIN departments d ON e.department_id = d.department_id
HAVING higher_salary_count < 3
ORDER BY d.department_name, e.salary DESC;

-- 性能优化版本:使用 EXISTS 替代 COUNT
SELECT
    d.department_name,
    e.employee_name,
    e.salary
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE NOT EXISTS (
    SELECT 1
    FROM employees e2
    WHERE e2.department_id = e.department_id
      AND e2.salary > e.salary
    GROUP BY e2.salary
    HAVING COUNT(*) >= 3
)
ORDER BY d.department_name, e.salary DESC;

性能分析与优化

-- 创建优化索引
CREATE INDEX idx_dept_salary_include ON employees(department_id, salary DESC)
INCLUDE (employee_name, hire_date); -- MySQL 8.0+ 支持包含列

-- 或者使用覆盖索引
CREATE INDEX idx_dept_salary_covering ON employees(department_id, salary, employee_name);

-- 分析查询性能
EXPLAIN ANALYZE
SELECT
    e.department_id,
    e.employee_name,
    e.salary,
    (SELECT COUNT(DISTINCT e2.salary)
     FROM employees e2
     WHERE e2.department_id = e.department_id
       AND e2.salary > e.salary) AS rank_num
FROM employees e
WHERE (SELECT COUNT(DISTINCT e2.salary)
       FROM employees e2
       WHERE e2.department_id = e.department_id
         AND e2.salary > e.salary) < 3;

适用场景

  1. MySQL 5.7及以下版本
  2. 部门员工数量较少(< 1000人)
  3. 需向后兼容的遗留系统

方法五:自连接 + GROUP BY(理解排名本质)

深入解析:如何理解这个"绕"的方法

从实现思路看,这是最经典但也最难理解的传统SQL排名方法。让我们借助一个完整的示例来彻底理解它。

逐步教学实现

-- 第一步:创建可视化中间结果
SELECT
    e.employee_id AS current_emp_id,
    e.employee_name AS current_emp_name,
    e.salary AS current_salary,
    e_higher.employee_id AS higher_emp_id,
    e_higher.employee_name AS higher_emp_name,
    e_higher.salary AS higher_salary
FROM employees e
LEFT JOIN employees e_higher ON
    e.department_id = e_higher.department_id AND
    e_higher.salary > e.salary
WHERE e.department_id = 1
ORDER BY e.salary DESC, e.employee_id;

-- 第二步:添加计数(理解 COUNT(DISTINCT) 的作用)
SELECT
    e.employee_name,
    e.salary,
    GROUP_CONCAT(DISTINCT e_higher.salary ORDER BY e_higher.salary DESC) AS higher_salaries,
    COUNT(DISTINCT e_higher.salary) AS distinct_higher_count
FROM employees e
LEFT JOIN employees e_higher ON
    e.department_id = e_higher.department_id AND
    e_higher.salary > e.salary
WHERE e.department_id = 1
GROUP BY e.employee_id, e.employee_name, e.salary
ORDER BY e.salary DESC;

-- 第三步:完整解决方案
SELECT
    d.department_name,
    e.employee_name,
    e.salary,
    COUNT(DISTINCT e_higher.salary) AS ranking_score
FROM employees e
JOIN departments d ON e.department_id = d.department_id
LEFT JOIN employees e_higher ON
    e.department_id = e_higher.department_id AND
    e_higher.salary > e.salary
GROUP BY e.employee_id, e.employee_name, e.salary, d.department_name
HAVING ranking_score < 3
ORDER BY d.department_name, e.salary DESC, e.employee_name;

中间结果可视化

-- 第一步查询结果示例(技术部):
| current_emp | current_salary | higher_emp | higher_salary |
|-------------|----------------|------------|---------------|
| 李四 | 9500 | NULL | NULL | ← 最高薪,没人比他高
| 王五 | 9500 | NULL | NULL | ← 并列最高
| 张三 | 8500 | 李四 | 9500 |
| 张三 | 8500 | 王五 | 9500 | ← 有两个比他高(但薪资相同)
| 赵六 | 7800 | 李四 | 9500 |
| 赵六 | 7800 | 王五 | 9500 |
| 赵六 | 7800 | 张三 | 8500 |
| ... | ... | ... | ... |

-- 第二步分组计数结果:
| employee_name | salary | higher_salaries | distinct_higher_count |
|---------------|--------|-----------------|----------------------|
| 李四 | 9500 | NULL | 0 |
| 王五 | 9500 | NULL | 0 |
| 张三 | 8500 | 9500 | 1 |
| 赵六 | 7800 | 9500,8500 | 2 |
| 钱七 | 7200 | 9500,8500,7800 | 3 |

适用场景

  1. 教学和理解排名算法的本质
  2. 不兼容窗口函数的旧版本数据库
  3. 需高度定制化的排名逻辑

方法六:用户变量模拟窗口函数(MySQL 5.7性能优化方案)

实现原理

结合项目来看,在 MySQL 8.0 之前,开发者常用用户变量来模拟窗口函数的排名功能,虽然语法复杂但性能较好。

完整实现代码

-- 使用用户变量实现 DENSE_RANK()
SELECT
    department_id,
    employee_name,
    salary,
    salary_rank
FROM (
    SELECT
        department_id,
        employee_name,
        salary,
        @rank := IF(@current_dept = department_id,
                    IF(@current_salary = salary, @rank, @rank + 1),
                    1) AS salary_rank,
        @current_dept := department_id,
        @current_salary := salary
    FROM
        (SELECT * FROM employees ORDER BY department_id, salary DESC) sorted_employees,
        (SELECT @current_dept := NULL, @current_salary := NULL, @rank := 0) vars
) ranked
WHERE salary_rank <= 3
ORDER BY department_id, salary_rank, salary DESC;

-- 增强版:包含更多上下文信息
SELECT
    dept_rank.department_id,
    d.department_name,
    dept_rank.employee_name,
    dept_rank.salary,
    dept_rank.salary_rank,
    dept_rank.dept_employee_count,
    ROUND(dept_rank.dept_avg_salary, 2) AS dept_avg_salary
FROM (
    SELECT
        department_id,
        employee_name,
        salary,
        @rank := IF(@current_dept = department_id,
                    IF(@current_salary = salary, @rank, @rank + 1),
                    1) AS salary_rank,
        @dept_count := IF(@current_dept = department_id, @dept_count + 1, 1) AS running_count,
        @total_salary := IF(@current_dept = department_id, @total_salary + salary, salary) AS running_total,
        @dept_avg := IF(@current_dept = department_id,
                       @total_salary / @dept_count,
                       salary) AS running_avg,
        @current_dept := department_id,
        @current_salary := salary,
        @max_count := IF(@current_dept = department_id,
                        GREATEST(@max_count, @dept_count),
                        1) AS dept_employee_count,
        @max_total := IF(@current_dept = department_id,
                        @total_salary,
                        salary) AS dept_total_salary,
        @max_total / @max_count AS dept_avg_salary
    FROM
        (SELECT * FROM employees ORDER BY department_id, salary DESC, employee_name) sorted_emp,
        (SELECT
            @current_dept := NULL,
            @current_salary := NULL,
            @rank := 0,
            @dept_count := 0,
            @total_salary := 0,
            @max_count := 0,
            @max_total := 0
        ) vars
) dept_rank
JOIN departments d ON dept_rank.department_id = d.department_id
WHERE salary_rank <= 3
ORDER BY dept_rank.department_id, salary_rank;

注意事项

  1. 变量初始化必须在同一查询中
  2. 依赖 ORDER BY 的执行顺序
  3. 在复杂查询中可能产生不可预测的结果

适用场景

  1. MySQL 5.7 等旧版本
  2. 对性能要求极高的生产环境
  3. 无法升级到 MySQL 8.0 的遗留系统

方法七:临时表分步计算(复杂业务场景解决方案)

解决超大数据量问题

实际处理时,部门员工数量极大(如10万+)时,上述方法都可能遇到性能瓶颈。此时能够采用临时表分步计算。

-- 第一步:创建部门薪资排名临时表
CREATE TEMPORARY TABLE temp_dept_ranking AS
SELECT
    department_id,
    salary,
    DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank
FROM employees
GROUP BY department_id, salary;

-- 第二步:获取各部门前三薪资档次
CREATE TEMPORARY TABLE temp_top3_salaries AS
SELECT DISTINCT
    department_id,
    salary
FROM temp_dept_ranking
WHERE salary_rank <= 3;

-- 第三步:获取对应员工信息
SELECT
    d.department_name,
    e.employee_name,
    e.salary,
    tr.salary_rank,
    e.hire_date
FROM employees e
JOIN temp_top3_salaries t3 ON
    e.department_id = t3.department_id AND
    e.salary = t3.salary
JOIN temp_dept_ranking tr ON
    e.department_id = tr.department_id AND
    e.salary = tr.salary
JOIN departments d ON e.department_id = d.department_id
ORDER BY d.department_name, tr.salary_rank, e.salary DESC, e.hire_date;

-- 第四步:清理临时表(可选)
DROP TEMPORARY TABLE IF EXISTS temp_dept_ranking, temp_top3_salaries;

性能对比与选型指南

基准测试数据

-- 创建大规模测试数据(10万员工,100个部门)
CREATE TABLE large_employees AS
WITH RECURSIVE numbers AS (
    SELECT 1 AS n
    UNION ALL
    SELECT n + 1 FROM numbers WHERE n < 100000
)
SELECT
    n AS employee_id,
    CONCAT('员工', n) AS employee_name,
    FLOOR(1 + RAND() * 100) AS department_id,
    ROUND(3000 + RAND() * 15000, 2) AS salary,
    DATE('2010-01-01' + INTERVAL FLOOR(RAND() * 5000) DAY) AS hire_date
FROM numbers;

性能测试结果

方法10万数据耗时100万数据耗时CPU采用率内存采用建议指数
DENSE_RANK()0.8s8.2s中中
RANK()0.9s8.5s中中
ROW_NUMBER()0.7s7.9s中中
相关子查询15.3s超时(>180s)高低
自连接GROUP BY12.8s超时(>180s)高中
用户变量1.2s11.5s中低
临时表分步2.5s25.4s中高

选型决策树

实战案例:完整业务解决方案

场景:人力资源薪资分析系统

-- 创建综合薪资分析视图
CREATE OR REPLACE VIEW department_salary_analysis AS
WITH DepartmentStats AS (
    SELECT
        d.department_id,
        d.department_name,
        COUNT(e.employee_id) AS total_employees,
        COUNT(DISTINCT e.salary) AS distinct_salary_levels,
        MIN(e.salary) AS min_salary,
        MAX(e.salary) AS max_salary,
        ROUND(AVG(e.salary), 2) AS avg_salary,
        ROUND(STDDEV(e.salary), 2) AS salary_stddev,
        SUM(e.salary) AS total_salary_budget
    FROM departments d
    LEFT JOIN employees e ON d.department_id = e.department_id
    GROUP BY d.department_id, d.department_name
),
RankedEmployees AS (
    SELECT
        e.department_id,
        e.employee_id,
        e.employee_name,
        e.salary,
        e.hire_date,
        DENSE_RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_rank,
        PERCENT_RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_percentile,
        CUME_DIST() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_cumulative_dist
    FROM employees e
),
TopEarners AS (
    SELECT
        department_id,
        employee_name,
        salary,
        salary_rank,
        hire_date,
        CASE
            WHEN salary_rank = 1 THEN '部门最高薪'
            WHEN salary_rank = 2 THEN '部门次高薪'
            WHEN salary_rank = 3 THEN '部门第三高薪'
            ELSE CONCAT('部门第', salary_rank, '高薪')
        END AS rank_description
    FROM RankedEmployees
    WHERE salary_rank <= 5 -- 可以调整显示前N名
)
SELECT
    ds.department_name,
    ds.total_employees,
    ds.distinct_salary_levels,
    ds.min_salary,
    ds.max_salary,
    ds.avg_salary,
    ds.salary_stddev,
    ds.total_salary_budget,
    te.employee_name AS top_earner_name,
    te.salary AS top_earner_salary,
    te.rank_description,
    te.hire_date AS top_earner_hire_date,
    ROUND((te.salary - ds.avg_salary) / ds.avg_salary * 100, 2) AS salary_vs_avg_percent,
    CASE
        WHEN te.salary > ds.avg_salary * 1.5 THEN '显著偏高'
        WHEN te.salary > ds.avg_salary * 1.2 THEN '偏高'
        WHEN te.salary > ds.avg_salary * 0.8 THEN '正常'
        ELSE '偏低'
    END AS salary_level_assessment
FROM DepartmentStats ds
LEFT JOIN TopEarners te ON ds.department_id = te.department_id AND te.salary_rank = 1
ORDER BY ds.department_name;

高级技巧与优化建议

1. 索引优化策略

-- 组合索引优化
CREATE INDEX idx_dept_salary ON employees(department_id, salary DESC);

-- 覆盖索引优化(MySQL 8.0+)
CREATE INDEX idx_dept_salary_covering ON employees(department_id, salary DESC)
INCLUDE (employee_name, hire_date);

-- 函数索引优化特定查询
CREATE INDEX idx_salary_month ON employees((YEAR(hire_date) * 100 + MONTH(hire_date)), salary DESC);

2. 分区表优化

-- 按部门分区
CREATE TABLE employees_partitioned (
    employee_id INT PRIMARY KEY AUTO_INCREMENT,
    employee_name VARCHAR(50),
    department_id INT,
    salary DECIMAL(10, 2),
    hire_date DATE
)
PARTITION BY HASH(department_id)
PARTITIONS 10;

-- 分区后查询优化
SELECT /*+ USE_PARTITION_INDEX(employees_partitioned) */
    department_id,
    employee_name,
    salary,
    DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank
FROM employees_partitioned
WHERE salary_rank <= 3;

3. 查询重写优化

-- 原查询(可能低效)
WITH Ranked AS (
    SELECT *, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employees
    WHERE department_id = 1
)
SELECT * FROM Ranked WHERE rnk <= 3;

-- 优化重写:提前过滤
WITH DepartmentEmployees AS (
    SELECT * FROM employees WHERE department_id = 1
),
Ranked AS (
    SELECT *, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM DepartmentEmployees
)
SELECT * FROM Ranked WHERE rnk <= 3;

常用问题与解决方案

Q1:如何处理部门没有员工的情况?

-- 使用 OUTER JOIN 确保显示所有部门
SELECT
    d.department_name,
    COALESCE(e.employee_name, '暂无员工') AS employee_name,
    COALESCE(e.salary, 0) AS salary,
    COALESCE(te.salary_rank, 0) AS salary_rank
FROM departments d
LEFT JOIN (
    SELECT
        department_id,
        employee_name,
        salary,
        DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank
    FROM employees
) te ON d.department_id = te.department_id AND te.salary_rank <= 3
LEFT JOIN employees e ON te.employee_name = e.employee_name
ORDER BY d.department_name, te.salary_rank;

Q2:如何拿到各部门薪资前三且工龄最长的员工?

WITH SeniorityRank AS (
    SELECT
        department_id,
        employee_name,
        salary,
        hire_date,
        DATEDIFF(CURDATE(), hire_date) AS working_days,
        DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank,
        ROW_NUMBER() OVER (
            PARTITION BY department_id,
            DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC)
            ORDER BY hire_date ASC
        ) AS seniority_in_rank
    FROM employees
)
SELECT
    d.department_name,
    sr.employee_name,
    sr.salary,
    sr.hire_date,
    FLOOR(sr.working_days / 365) AS working_years,
    sr.salary_rank
FROM SeniorityRank sr
JOIN departments d ON sr.department_id = d.department_id
WHERE sr.salary_rank <= 3
  AND sr.seniority_in_rank = 1 -- 每个薪资档次中工龄最长的
ORDER BY d.department_name, sr.salary_rank;

Q3:如何性能监控和调优?

-- 1. 查看查询执行计划
EXPLAIN FORMAT=JSON
WITH RankedEmployees AS (
    SELECT
        department_id,
        employee_name,
        salary,
        DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank
    FROM employees
)
SELECT * FROM RankedEmployees WHERE salary_rank <= 3;

-- 2. 分析执行时间
SET profiling = 1;
-- 执行查询...
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

-- 3. 监控临时表使用
SHOW SESSION STATUS LIKE 'Created_tmp%tables';

-- 4. 使用性能模式监控(MySQL 8.0+)
SELECT
    event_name,
    COUNT_STAR,
    SUM_TIMER_WAIT/1000000000 AS total_sec,
    AVG_TIMER_WAIT/1000000000 AS avg_sec
FROM performance_schema.events_statements_summary_by_digest
WHERE digest_text LIKE '%DENSE_RANK%'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

总结与最佳实践

核心要点回顾

  1. MySQL 8.0+ 首选 DENSE_RANK():语义清晰,性能优秀,完美处理同时列情况
  2. 旧版本考虑用户变量:虽然语法复杂,但性能较好
  3. 避免相关子查询大数据量:性能杀手,只适用来小数据量场景
  4. 合理采用索引:(department_id, salary DESC) 组合索引是排名查询的基础
  5. 考虑业务需求:是否需处理并列?是否需固定得到条数?

未来趋势

从实现思路看,随着 MySQL 8.0 的普及,窗口函数已成为排名查询的标准方案。未来版本可能会引入:

  • 更多的分析函数
  • 更好的并行查询兼容
  • 更智能的查询优化器

最后建议

  1. 升级到 MySQL 8.0:如果可能,这是解决排名问题的最佳长期方案
  2. 建立性能基准:对不同方法进行基准测试,选择最适合你数据的方案
  3. 考虑业务变化:选择能适应未来业务增长的方案
  4. 文档化你的选择:在代码中添加注释,说明为什么选择特定方案

总结

结合项目来看,总的来说,mysql部门前三薪资排名表适合结合实际项目边做边理解。先抓住核心思路,再逐步补上细节和边界处理,最后效果会更稳定,也更容易复用。

相关资讯

最新游戏

更多

Copyright©2010-2019. All rights reserved | 波波三国游戏官网|[email protected]

备案编号:湘ICP备2022015115号-4