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

在TSQL中拆分日期范围为多个员工专属有效时间段

TSQL处理同一CaseId下的重叠日期范围,拆分无重叠员工时间段

需求说明

需要在TSQL中处理同一caseId下的重叠日期范围,为每个员工划分出无重叠的有效时间段。优先采用SQL视图实现,存储过程也可接受。此前尝试用CTE实现时,无法生成完整结果(比如测试数据中caseId=1的第三行),现寻求可行解决方案。

输入测试数据

drop table if exists #ranges
create table #ranges (caseId int not null, startDate date  not null, endDate date not null, Employee VARCHAR(50))

insert #ranges select 1, '2023-01-01', '2023-12-31','ABc'
insert #ranges select 1, '2023-01-15', '2023-05-01', 'CBa'

insert #ranges select 2, '2023-01-01', '2023-05-01', 'ABc'
insert #ranges select 2, '2023-01-15', '2023-03-01', 'DEf'
insert #ranges select 2, '2023-02-01', '2023-12-31', 'GHi'

期望输出结果

drop table if exists #result
create table #result (caseId int not null, startDate date  not null, endDate date not null, Employee VARCHAR(50))
insert #result select 1, '2023-01-01', '2023-01-14','ABc'
insert #result select 1, '2023-01-15', '2023-05-01', 'CBa'
insert #result select 1, '2023-05-02', '2023-12-31','ABc'

insert #result select 2, '2023-01-01', '2023-01-14','ABc'
insert #result select 2, '2023-01-15', '2023-03-01', 'DEf'
insert #result select 2, '2023-03-02', '2023-05-01','ABc'
insert #result select 2, '2023-05-02', '2023-12-31','GHi'

解决方案

以下是基于CTE的实现方案,核心思路是:先定位每个员工原始时间段中被其他员工覆盖的重叠区间,再将原始时间段拆分为未被覆盖的有效子区间:

WITH Overlaps AS (
    -- 找出当前员工时间段与其他员工的重叠部分
    SELECT 
        r1.caseId,
        r1.Employee,
        CASE WHEN r1.startDate <= r2.startDate THEN r2.startDate ELSE r1.startDate END AS OverlapStart,
        CASE WHEN r1.endDate >= r2.endDate THEN r2.endDate ELSE r1.endDate END AS OverlapEnd
    FROM #ranges r1
    JOIN #ranges r2 
        ON r1.caseId = r2.caseId 
        AND r1.Employee != r2.Employee
        AND r1.startDate <= r2.endDate 
        AND r1.endDate >= r2.startDate
),
MergedOverlaps AS (
    -- 合并重叠的排除区间,避免重复拆分
    SELECT 
        caseId,
        Employee,
        OverlapStart,
        OverlapEnd
    FROM Overlaps o1
    WHERE NOT EXISTS (
        SELECT 1 
        FROM Overlaps o2
        WHERE o2.caseId = o1.caseId 
          AND o2.Employee = o1.Employee
          AND o2.OverlapStart <= o1.OverlapStart 
          AND o2.OverlapEnd >= o1.OverlapEnd
          AND (o2.OverlapStart < o1.OverlapStart OR o2.OverlapEnd > o1.OverlapEnd)
    )
    UNION ALL
    SELECT 
        mo.caseId,
        mo.Employee,
        mo.OverlapStart,
        o.OverlapEnd
    FROM MergedOverlaps mo
    JOIN Overlaps o 
        ON mo.caseId = o.caseId 
        AND mo.Employee = o.Employee
        AND o.OverlapStart <= DATEADD(DAY, 1, mo.OverlapEnd) 
        AND o.OverlapEnd > mo.OverlapEnd
        AND NOT EXISTS (
            SELECT 1 
            FROM MergedOverlaps mo2
            WHERE mo2.caseId = mo.caseId 
              AND mo2.Employee = mo.Employee
              AND mo2.OverlapStart <= mo.OverlapStart 
              AND mo2.OverlapEnd >= o.OverlapEnd
        )
),
DistinctMergedOverlaps AS (
    -- 去重合并后的排除区间
    SELECT DISTINCT caseId, Employee, OverlapStart, OverlapEnd 
    FROM MergedOverlaps
),
SplitPoints AS (
    -- 生成所有需要拆分的日期点:原始区间的起止、重叠区间的起止
    SELECT caseId, Employee, startDate AS Point FROM #ranges
    UNION
    SELECT caseId, Employee, DATEADD(DAY, 1, endDate) AS Point FROM #ranges
    UNION
    SELECT caseId, Employee, OverlapStart AS Point FROM DistinctMergedOverlaps
    UNION
    SELECT caseId, Employee, DATEADD(DAY, 1, OverlapEnd) AS Point FROM DistinctMergedOverlaps
),
SplitRanges AS (
    -- 基于拆分点生成连续的子时间段
    SELECT 
        caseId,
        Employee,
        Point AS StartDate,
        LEAD(Point) OVER (PARTITION BY caseId, Employee ORDER BY Point) AS NextPoint
    FROM SplitPoints
),
ValidRanges AS (
    -- 筛选出属于原始区间且未被覆盖的有效子时间段
    SELECT 
        sr.caseId,
        sr.StartDate,
        DATEADD(DAY, -1, sr.NextPoint) AS EndDate,
        sr.Employee
    FROM SplitRanges sr
    JOIN #ranges r 
        ON sr.caseId = r.caseId 
        AND sr.Employee = r.Employee
        AND sr.StartDate >= r.startDate 
        AND DATEADD(DAY, -1, sr.NextPoint) <= r.endDate
    WHERE sr.NextPoint IS NOT NULL
    AND NOT EXISTS (
        SELECT 1 
        FROM DistinctMergedOverlaps mo
        WHERE mo.caseId = sr.caseId 
          AND mo.Employee = sr.Employee
          AND mo.OverlapStart <= sr.StartDate 
          AND mo.OverlapEnd >= DATEADD(DAY, -1, sr.NextPoint)
    )
)
-- 输出最终结果
SELECT * FROM ValidRanges ORDER BY caseId, StartDate;

如果需要封装为视图,只需将临时表#ranges替换为正式表(比如dbo.ranges),将上述逻辑整合到视图中即可:

CREATE VIEW vw_EmployeeValidRanges AS
WITH Overlaps AS (
    SELECT 
        r1.caseId,
        r1.Employee,
        CASE WHEN r1.startDate <= r2.startDate THEN r2.startDate ELSE r1.startDate END AS OverlapStart,
        CASE WHEN r1.endDate >= r2.endDate THEN r2.endDate ELSE r1.endDate END AS OverlapEnd
    FROM dbo.ranges r1
    JOIN dbo.ranges r2 
        ON r1.caseId = r2.caseId 
        AND r1.Employee != r2.Employee
        AND r1.startDate <= r2.endDate 
        AND r1.endDate >= r2.startDate
),
MergedOverlaps AS (
    SELECT 
        caseId,
        Employee,
        OverlapStart,
        OverlapEnd
    FROM Overlaps o1
    WHERE NOT EXISTS (
        SELECT 1 
        FROM Overlaps o2
        WHERE o2.caseId = o1.caseId 
          AND o2.Employee = o1.Employee
          AND o2.OverlapStart <= o1.OverlapStart 
          AND o2.OverlapEnd >= o1.OverlapEnd
          AND (o2.OverlapStart < o1.OverlapStart OR o2.OverlapEnd > o1.OverlapEnd)
    )
    UNION ALL
    SELECT 
        mo.caseId,
        mo.Employee,
        mo.OverlapStart,
        o.OverlapEnd
    FROM MergedOverlaps mo
    JOIN Overlaps o 
        ON mo.caseId = o.caseId 
        AND mo.Employee = o.Employee
        AND o.OverlapStart <= DATEADD(DAY, 1, mo.OverlapEnd) 
        AND o.OverlapEnd > mo.OverlapEnd
        AND NOT EXISTS (
            SELECT 1 
            FROM MergedOverlaps mo2
            WHERE mo2.caseId = mo.caseId 
              AND mo2.Employee = mo.Employee
              AND mo2.OverlapStart <= mo.OverlapStart 
              AND mo2.OverlapEnd >= o.OverlapEnd
        )
),
DistinctMergedOverlaps AS (
    SELECT DISTINCT caseId, Employee, OverlapStart, OverlapEnd 
    FROM MergedOverlaps
),
SplitPoints AS (
    SELECT caseId, Employee, startDate AS Point FROM dbo.ranges
    UNION
    SELECT caseId, Employee, DATEADD(DAY, 1, endDate) AS Point FROM dbo.ranges
    UNION
    SELECT caseId, Employee, OverlapStart AS Point FROM DistinctMergedOverlaps
    UNION
    SELECT caseId, Employee, DATEADD(DAY, 1, OverlapEnd) AS Point FROM DistinctMergedOverlaps
),
SplitRanges AS (
    SELECT 
        caseId,
        Employee,
        Point AS StartDate,
        LEAD(Point) OVER (PARTITION BY caseId, Employee ORDER BY Point) AS NextPoint
    FROM SplitPoints
),
ValidRanges AS (
    SELECT 
        sr.caseId,
        sr.StartDate,
        DATEADD(DAY, -1, sr.NextPoint) AS EndDate,
        sr.Employee
    FROM SplitRanges sr
    JOIN dbo.ranges r 
        ON sr.caseId = r.caseId 
        AND sr.Employee = r.Employee
        AND sr.StartDate >= r.startDate 
        AND DATEADD(DAY, -1, sr.NextPoint) <= r.endDate
    WHERE sr.NextPoint IS NOT NULL
    AND NOT EXISTS (
        SELECT 1 
        FROM DistinctMergedOverlaps mo
        WHERE mo.caseId = sr.caseId 
          AND mo.Employee = sr.Employee
          AND mo.OverlapStart <= sr.StartDate 
          AND mo.OverlapEnd >= DATEADD(DAY, -1, sr.NextPoint)
    )
)
SELECT * FROM ValidRanges;

内容的提问来源于stack exchange,提问作者Dani C.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 16:38:07