You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL按月计算指定日期范围员工平均薪资方案(兼容5.6/8+)

解决方案:按月统计指定日期范围内在职员工的平均薪资

问题背景

我们有一个employees表,记录了员工的入职、离职日期和薪资。需要针对给定的日期范围,按月统计每个月在职员工的平均薪资,且要求纯MySQL实现,同时兼容MySQL 5.6和MySQL 8+版本,避免循环查询每个月的低效方式。

首先确认表结构和测试数据:

CREATE TABLE `employees` (
 `id` int(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
 `name` varchar(50) NOT NULL,
 `start_date` date NOT NULL,
 `end_date` date NOT NULL,
 `salary` decimal(10,2) DEFAULT NULL
);

INSERT INTO `employees` (`id`, `name`, `start_date`, `end_date`, `salary`) VALUES
(1, 'Mark', '2017-05-01', '2020-01-31', '2000.00'),
(2, 'Tania', '2018-02-01', '2019-08-31', '5000.00'),
(3, 'Leo', '2018-02-01', '2018-09-30', '3000.00'),
(4, 'Elsa', '2018-12-01', '2020-05-31', '4000.00');

核心思路

  • 生成目标日期范围内的所有月份:这是实现高效统计的关键,我们需要先构建出统计周期内的每个月份,而非依赖现有数据中的月份记录。
  • 匹配每个月份的在职员工:判断员工的在职周期与当前月份是否有重叠(即员工的start_date ≤ 当月最后一天,且end_date ≥ 当月第一天)。
  • 计算月度平均薪资:对每个月份的在职员工薪资求平均值,保留两位小数以保证结果精度。

MySQL 8+ 版本方案

MySQL 8+支持递归CTE(公共表表达式),可以轻松生成连续的月份序列,代码简洁易维护:

WITH RECURSIVE date_range AS (
    -- 初始化起始月份:将输入的开始日期转为当月第一天
    SELECT 
        DATE_FORMAT('2018-08-01', '%Y-%m-01') AS month_start,
        LAST_DAY('2018-08-01') AS month_end
    UNION ALL
    -- 递归生成后续月份,直到超过目标结束日期
    SELECT 
        DATE_ADD(month_start, INTERVAL 1 MONTH),
        LAST_DAY(DATE_ADD(month_start, INTERVAL 1 MONTH))
    FROM date_range
    WHERE month_start < DATE_FORMAT('2019-01-31', '%Y-%m-01')
)
SELECT
    YEAR(dr.month_start) AS `year`,
    LPAD(MONTH(dr.month_start), 2, '0') AS `month`,
    ROUND(AVG(e.salary), 2) AS avg_salary
FROM date_range dr
LEFT JOIN employees e 
    ON e.start_date <= dr.month_end 
    AND e.end_date >= dr.month_start
GROUP BY dr.month_start
ORDER BY dr.month_start;

关键说明

  • 递归CTE date_range会自动生成指定范围内的所有月份的起始和结束日期。
  • JOIN条件确保只统计在当前月份内有在职记录的员工(哪怕只在职一天也算)。
  • LPAD函数保证月份显示为两位数字(如08而非8),与预期结果格式一致。

MySQL 5.6 版本方案

MySQL 5.6不支持CTE,我们可以通过数字辅助表来生成连续月份序列,有两种实现方式:

方法1:临时数字表方案

-- 临时创建一个包含连续数字的表(这里生成0到100的数字,足够覆盖大部分场景)
CREATE TEMPORARY TABLE nums (n INT);
INSERT INTO nums VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
INSERT INTO nums SELECT n+11 FROM nums; -- 重复执行可扩展数字范围

-- 生成目标月份并统计平均薪资
SELECT
    YEAR(month_start) AS `year`,
    LPAD(MONTH(month_start), 2, '0') AS `month`,
    ROUND(AVG(e.salary), 2) AS avg_salary
FROM (
    SELECT
        DATE_ADD(
            DATE_FORMAT('2018-08-01', '%Y-%m-01'),
            INTERVAL n MONTH
        ) AS month_start,
        LAST_DAY(
            DATE_ADD(
                DATE_FORMAT('2018-08-01', '%Y-%m-01'),
                INTERVAL n MONTH
            )
        ) AS month_end
    FROM nums
    WHERE DATE_ADD(
            DATE_FORMAT('2018-08-01', '%Y-%m-01'),
            INTERVAL n MONTH
        ) <= DATE_FORMAT('2019-01-31', '%Y-%m-01')
) dr
LEFT JOIN employees e 
    ON e.start_date <= dr.month_end 
    AND e.end_date >= dr.month_start
GROUP BY dr.month_start
ORDER BY dr.month_start;

-- 可选:统计完成后删除临时表
DROP TEMPORARY TABLE nums;

方法2:无临时表方案(利用系统表生成数字)

如果不想创建临时表,可以借助系统表生成连续数字:

SELECT
    YEAR(month_start) AS `year`,
    LPAD(MONTH(month_start), 2, '0') AS `month`,
    ROUND(AVG(e.salary), 2) AS avg_salary
FROM (
    SELECT
        DATE_ADD(
            DATE_FORMAT('2018-08-01', '%Y-%m-01'),
            INTERVAL (a.n + b.n * 10) MONTH
        ) AS month_start,
        LAST_DAY(
            DATE_ADD(
                DATE_FORMAT('2018-08-01', '%Y-%m-01'),
                INTERVAL (a.n + b.n * 10) MONTH
            )
        ) AS month_end
    FROM (
        SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
        UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
    ) a
    CROSS JOIN (
        SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
        UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
    ) b
    WHERE DATE_ADD(
            DATE_FORMAT('2018-08-01', '%Y-%m-01'),
            INTERVAL (a.n + b.n * 10) MONTH
        ) <= DATE_FORMAT('2019-01-31', '%Y-%m-01')
) dr
LEFT JOIN employees e 
    ON e.start_date <= dr.month_end 
    AND e.end_date >= dr.month_start
GROUP BY dr.month_start
ORDER BY dr.month_start;

测试验证

当输入日期范围为2018-08-01至2019-01-31时,两种方案都会返回预期结果:

+------+-------+------------+
| year | month | avg_salary |
+------+-------+------------+
| 2018 | 08    | 3333.33    |
| 2018 | 09    | 3333.33    |
| 2018 | 10    | 3500.00    |
| 2018 | 11    | 3500.00    |
| 2018 | 12    | 3666.67    |
| 2019 | 01    | 3666.67    |
+------+-------+------------+

内容的提问来源于stack exchange,提问作者Dan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:18:59