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

基于员工startdate按月统计工时的SQL迭代查询需求

员工月度工时统计方案(含入职后月度区间生成)

需求拆解

  • 基于Table1的员工入职日期,生成从入职月到当前月的月度区间:每个区间是「当月入职日」到「下月入职日的前一天」(比如入职日2022-02-15,第一个区间就是2022-02-15至2022-03-14)
  • 关联Table2的考勤数据,统计每个员工在对应区间内的总工时,无考勤数据时返回NULL

实现步骤

1. 生成员工月度区间表

用递归CTE(Common Table Expression)来生成每位员工的所有月度区间,主流数据库(SQL Server、MySQL 8.0+、PostgreSQL等)都支持这种写法。

SQL Server 版本

WITH EmployeeMonths AS (
    -- 初始行:员工入职第一个月的区间
    SELECT 
        emp AS Emp#,
        startdate AS startdate1,
        DATEADD(DAY, -1, DATEADD(MONTH, 1, startdate)) AS enddate,
        -- 用年月数值标记,方便终止递归
        DATEPART(YEAR, startdate) * 12 + DATEPART(MONTH, startdate) AS month_num
    FROM Table1
    UNION ALL
    -- 递归生成后续每个月的区间
    SELECT 
        Emp#,
        DATEADD(MONTH, 1, startdate1) AS startdate1,
        DATEADD(DAY, -1, DATEADD(MONTH, 2, startdate1)) AS enddate,
        month_num + 1
    FROM EmployeeMonths
    -- 终止条件:不超过当前年月
    WHERE month_num < DATEPART(YEAR, GETDATE()) * 12 + DATEPART(MONTH, GETDATE())
)
SELECT * FROM EmployeeMonths ORDER BY Emp#, startdate1;

MySQL 8.0+ 版本

WITH RECURSIVE EmployeeMonths AS (
    SELECT 
        emp AS Emp#,
        startdate AS startdate1,
        DATE_SUB(DATE_ADD(startdate, INTERVAL 1 MONTH), INTERVAL 1 DAY) AS enddate,
        YEAR(startdate)*12 + MONTH(startdate) AS month_num
    FROM Table1
    UNION ALL
    SELECT 
        Emp#,
        DATE_ADD(startdate1, INTERVAL 1 MONTH) AS startdate1,
        DATE_SUB(DATE_ADD(startdate1, INTERVAL 2 MONTH), INTERVAL 1 DAY) AS enddate,
        month_num + 1
    FROM EmployeeMonths
    WHERE month_num < YEAR(CURDATE())*12 + MONTH(CURDATE())
)
SELECT * FROM EmployeeMonths ORDER BY Emp#, startdate1;

2. 关联考勤表统计总工时

把上面生成的区间表和Table2做左连接,分组统计每个区间的总工时:

SQL Server 完整查询

WITH EmployeeMonths AS (
    SELECT 
        emp AS Emp#,
        startdate AS startdate1,
        DATEADD(DAY, -1, DATEADD(MONTH, 1, startdate)) AS enddate,
        DATEPART(YEAR, startdate) * 12 + DATEPART(MONTH, startdate) AS month_num
    FROM Table1
    UNION ALL
    SELECT 
        Emp#,
        DATEADD(MONTH, 1, startdate1) AS startdate1,
        DATEADD(DAY, -1, DATEADD(MONTH, 2, startdate1)) AS enddate,
        month_num + 1
    FROM EmployeeMonths
    WHERE month_num < DATEPART(YEAR, GETDATE()) * 12 + DATEPART(MONTH, GETDATE())
)
SELECT 
    em.Emp#,
    em.startdate1,
    em.enddate,
    SUM(t2.hrs) AS total_hrs
FROM EmployeeMonths em
LEFT JOIN Table2 t2 
    ON em.Emp# = t2.Emp#
    AND t2.[attend date] BETWEEN em.startdate1 AND em.enddate
GROUP BY em.Emp#, em.startdate1, em.enddate
ORDER BY em.Emp#, em.startdate1;

MySQL 完整查询

WITH RECURSIVE EmployeeMonths AS (
    SELECT 
        emp AS Emp#,
        startdate AS startdate1,
        DATE_SUB(DATE_ADD(startdate, INTERVAL 1 MONTH), INTERVAL 1 DAY) AS enddate,
        YEAR(startdate)*12 + MONTH(startdate) AS month_num
    FROM Table1
    UNION ALL
    SELECT 
        Emp#,
        DATE_ADD(startdate1, INTERVAL 1 MONTH) AS startdate1,
        DATE_SUB(DATE_ADD(startdate1, INTERVAL 2 MONTH), INTERVAL 1 DAY) AS enddate,
        month_num + 1
    FROM EmployeeMonths
    WHERE month_num < YEAR(CURDATE())*12 + MONTH(CURDATE())
)
SELECT 
    em.Emp#,
    em.startdate1,
    em.enddate,
    SUM(t2.hrs) AS total_hrs
FROM EmployeeMonths em
LEFT JOIN Table2 t2 
    ON em.Emp# = t2.Emp#
    AND t2.`attend date` BETWEEN em.startdate1 AND em.enddate
GROUP BY em.Emp#, em.startdate1, em.enddate
ORDER BY em.Emp#, em.startdate1;

关键说明

  • 递归CTE会自动遍历每个员工的入职月到当前月,生成符合要求的月度区间,不用手动写迭代逻辑
  • 左连接保证了即使某个区间没有考勤数据,也会保留该区间的记录,SUM(t2.hrs)会返回NULL
  • 不同数据库的日期函数略有差异,上面分别适配了SQL Server和MySQL的写法,其他数据库(比如PostgreSQL)只需要调整日期函数即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:41:05