在T-SQL中为冲突事件组分配分组ID的技术问询
我明白你现在卡在了冲突事件分组的问题上——用LAG确实能判断单个前后事件的冲突,但没法把一串连续重叠的事件归为同一组。咱们用T-SQL里的**间隙与孤岛(Gap & Island)**技巧来解决,这正是处理这类重叠时间段分组的标准方案,完全能满足你的需求:给冲突事件分配唯一组ID,单独事件组ID设为0,同时计算每组的统计值。
核心思路
- 先按房间、日期、开始时间排序,确保同一房间同一天的事件按时间顺序排列。
- 用窗口函数追踪当前事件之前所有事件的最晚结束时间,判断当前事件是否属于新组(如果当前开始时间晚于之前所有事件的最晚结束时间,就是新组)。
- 通过累计求和生成临时组ID,把连续冲突的事件归为同一组。
- 计算每组的最早开始、最晚结束时间和参会人数总和,最后给无冲突的组(仅单个事件)设置组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;
代码解释
- EventGroups CTE:用
MAX(EndTime) OVER (...)窗口函数,在同一房间、同一天的范围内,取当前事件之前所有事件的最晚结束时间。如果当前事件的开始时间晚于这个时间,说明它和前面的事件都不冲突,标记为新组(1),否则属于当前组(0)。 - GroupIDs CTE:对
IsNewGroup做累计求和,这样同一组的事件会得到相同的TempGroupID——比如第一个冲突组的所有事件ID都是1,第二个都是2,以此类推。 - GroupStats CTE:按临时组ID、房间、日期分组,计算每组的核心统计值。如果你的参会人数不是按
ActivityID计数,而是有单独的数字字段,把COUNT(ActivityID)改成SUM(你的参会数字段)即可。 - 最终查询:把原数据和统计结果关联,判断如果组内只有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
相关产品推荐
相关产品推荐

