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

在T-SQL中为冲突事件组分配分组ID的技术问询

我明白你现在卡在了冲突事件分组的问题上——用LAG确实能判断单个前后事件的冲突,但没法把一串连续重叠的事件归为同一组。咱们用T-SQL里的**间隙与孤岛(Gap & Island)**技巧来解决,这正是处理这类重叠时间段分组的标准方案,完全能满足你的需求:给冲突事件分配唯一组ID,单独事件组ID设为0,同时计算每组的统计值。

核心思路

  1. 先按房间、日期、开始时间排序,确保同一房间同一天的事件按时间顺序排列。
  2. 用窗口函数追踪当前事件之前所有事件的最晚结束时间,判断当前事件是否属于新组(如果当前开始时间晚于之前所有事件的最晚结束时间,就是新组)。
  3. 通过累计求和生成临时组ID,把连续冲突的事件归为同一组。
  4. 计算每组的最早开始、最晚结束时间和参会人数总和,最后给无冲突的组(仅单个事件)设置组ID为0。

完整解决方案代码

首先是修正后的测试数据(补全了你没写完的最后一行,还加了一个单独事件用来测试组ID=0的情况):

CREATE TABLE mytable( 
    RoomID INTEGER NOT NULL PRIMARY KEY ,
    RoomNo VARCHAR(6) NOT NULL ,
    DayOfYear INTEGER NOT NULL ,
    StartTime DATETIME NOT NULL ,
    EndTime DATETIME NOT NULL ,
    ActivityID INTEGER NOT NULL ,
    FIELD7 VARCHAR(11) 
); 

INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (308,'P1C1',3,'2018-01-03 09:00:00.000','2018-01-03 10:30:00.000',221456);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (309,'P1C1',3,'2018-01-03 09:00:00.000','2018-01-03 10:30:00.000',222129);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (310,'P1C1',3,'2018-01-03 09:00:00.000','2018-01-03 10:30:00.000',222251);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (311,'P1C1',3,'2018-01-03 09:00:00.000','2018-01-03 10:30:00.000',222389);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (312,'P1C1',3,'2018-01-03 09:00:00.000','2018-01-03 10:30:00.000',222527);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (313,'P1C1',3,'2018-01-03 10:45:00.000','2018-01-03 12:15:00.000',221491);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (314,'P1C1',3,'2018-01-03 10:45:00.000','2018-01-03 12:15:00.000',222160);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (315,'P1C1',3,'2018-01-03 10:45:00.000','2018-01-03 12:15:00.000',222286);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (316,'P1C1',3,'2018-01-03 10:45:00.000','2018-01-03 12:15:00.000',222424);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (317,'P1C1',3,'2018-01-03 10:45:00.000','2018-01-03 12:15:00.000',222562);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (318,'P1C1',3,'2018-01-03 10:45:00.000','2018-01-03 12:15:00.000',224183);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (319,'P1C1',3,'2018-01-03 13:45:00.000','2018-01-03 15:15:00.000',221921);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (320,'P1C1',3,'2018-01-03 13:45:00.000','2018-01-03 15:15:00.000',223167);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (321,'P1C1',3,'2018-01-03 15:30:00.000','2018-01-03 17:00:00.000',221956);
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (322,'P1C1',3,'2018-01-03 15:30:00.000','2018-01-03 17:00:00.000',223202);
-- 新增无冲突的单独事件,测试组ID=0
INSERT INTO mytable(RoomID,RoomNo,DayOfYear,StartTime,EndTime,ActivityID) 
VALUES (323,'P1C2',3,'2018-01-03 14:00:00.000','2018-01-03 15:00:00.000',223333);

然后是处理逻辑的核心代码:

WITH EventGroups AS (
    -- 标记每个事件是否是新组的开始
    SELECT 
        *,
        CASE 
            WHEN StartTime > MAX(EndTime) OVER (
                PARTITION BY RoomID, DayOfYear 
                ORDER BY StartTime 
                ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
            ) THEN 1 
            ELSE 0 
        END AS IsNewGroup
    FROM mytable
),
GroupIDs AS (
    -- 累计求和生成临时组ID,同一组的事件ID相同
    SELECT 
        *,
        SUM(IsNewGroup) OVER (
            PARTITION BY RoomID, DayOfYear 
            ORDER BY StartTime 
            ROWS UNBOUNDED PRECEDING
        ) AS TempGroupID
    FROM EventGroups
),
GroupStats AS (
    -- 计算每组的统计信息:最早开始、最晚结束、参会人数总和
    SELECT 
        TempGroupID,
        RoomID,
        DayOfYear,
        MIN(StartTime) AS GroupStartTime,
        MAX(EndTime) AS GroupEndTime,
        COUNT(ActivityID) AS TotalParticipants -- 这里假设每个ActivityID对应一个参会人,有单独字段的话替换成SUM(字段名)
    FROM GroupIDs
    GROUP BY TempGroupID, RoomID, DayOfYear
)
-- 关联回原数据,设置最终的冲突组ID:无冲突(仅1个事件)设为0,冲突组用临时ID
SELECT 
    g.*,
    CASE WHEN s.TotalParticipants = 1 THEN 0 ELSE s.TempGroupID END AS ConflictGroupID,
    s.GroupStartTime,
    s.GroupEndTime,
    s.TotalParticipants
FROM mytable g
JOIN GroupStats s 
    ON g.RoomID = s.RoomID 
    AND g.DayOfYear = s.DayOfYear 
    AND g.StartTime >= s.GroupStartTime 
    AND g.EndTime <= s.GroupEndTime
ORDER BY g.RoomID, g.DayOfYear, g.StartTime;

代码解释

  1. EventGroups CTE:用MAX(EndTime) OVER (...)窗口函数,在同一房间、同一天的范围内,取当前事件之前所有事件的最晚结束时间。如果当前事件的开始时间晚于这个时间,说明它和前面的事件都不冲突,标记为新组(1),否则属于当前组(0)。
  2. GroupIDs CTE:对IsNewGroup做累计求和,这样同一组的事件会得到相同的TempGroupID——比如第一个冲突组的所有事件ID都是1,第二个都是2,以此类推。
  3. GroupStats CTE:按临时组ID、房间、日期分组,计算每组的核心统计值。如果你的参会人数不是按ActivityID计数,而是有单独的数字字段,把COUNT(ActivityID)改成SUM(你的参会数字段)即可。
  4. 最终查询:把原数据和统计结果关联,判断如果组内只有1个事件(无冲突),则ConflictGroupID设为0,否则用临时组ID作为唯一标识。

关于每日4个区块的扩展

如果需要只在同一区块内分组冲突事件,先给每个事件标记所属区块,再把区块字段加入到所有CTE的PARTITION BY中即可。比如区块划分可以这样写:

CASE 
    WHEN CAST(StartTime AS TIME) BETWEEN '08:00:00' AND '10:30:00' THEN 1
    WHEN CAST(StartTime AS TIME) BETWEEN '10:45:00' AND '13:15:00' THEN 2
    WHEN CAST(StartTime AS TIME) BETWEEN '13:45:00' AND '16:15:00' THEN 3
    WHEN CAST(StartTime AS TIME) BETWEEN '16:30:00' AND '19:00:00' THEN 4
END AS BlockNumber

把这个字段加到PARTITION BY RoomID, DayOfYear, BlockNumber里,就只会在同一区块内处理冲突分组了。

内容的提问来源于stack exchange,提问作者tek-monkey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:32:00