将EF C#登录登出配对逻辑转换为SQL存储过程以提升性能
解决方案:SQL存储过程实现登录登出事件配对
首先,我假设你的Results类包含以下字段(如果实际结构不同,直接调整存储过程的输出列即可):
UserId:用户唯一标识LoginTime:登录事件的发生时间LogoutTime:配对的登出事件时间(未完成登出则为NULL)SessionDurationSeconds:会话持续时长(单位:秒,未登出则为NULL)
下面的存储过程完全匹配你需要的逻辑:重复登录时取首个登录匹配后续有效登出,也就是用户在未登出状态下的重复登录会被忽略,仅第一个登录会对应后续的第一个有效登出;正常的登录-登出序列则按时间顺序一一配对。
CREATE PROCEDURE GetUserSessionPairs AS BEGIN SET NOCOUNT ON; -- 提取所有登录事件,按用户+时间排序并给每个登录编号 WITH Logins AS ( SELECT UserId, EventTime AS LoginTime, ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY EventTime ASC) AS LoginSeq FROM EventLogs WHERE EventType = 11 -- 登录事件类型标识 ), -- 提取所有登出事件,按用户+时间排序并给每个登出编号 Logouts AS ( SELECT UserId, EventTime AS LogoutTime, ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY EventTime ASC) AS LogoutSeq FROM EventLogs WHERE EventType = 12 -- 登出事件类型标识 ) -- 配对登录与登出事件,保留未登出的登录记录 SELECT l.UserId, l.LoginTime, lo.LogoutTime, -- 仅当存在登出时间时计算会话时长 CASE WHEN lo.LogoutTime IS NOT NULL THEN DATEDIFF(SECOND, l.LoginTime, lo.LogoutTime) ELSE NULL END AS SessionDurationSeconds FROM Logins l -- 通过用户ID+序列编号匹配对应的登出事件 LEFT JOIN Logouts lo ON l.UserId = lo.UserId AND l.LoginSeq = lo.LogoutSeq -- 可选:如果不需要保留未登出的登录记录,去掉下面的注释 -- WHERE lo.LogoutTime IS NOT NULL ORDER BY l.UserId, l.LoginTime; END GO
逻辑说明
- 事件序列编号:用
ROW_NUMBER()窗口函数给每个用户的登录、登出事件分别按时间顺序编号,确保第一个登录对应第一个登出,第二个登录对应第二个登出,完美贴合“首个登录优先匹配后续登出”的规则。 - 重复登录处理:如果用户在未登出时重复登录,后续的登录会对应更靠后的登出事件(如果存在);若没有后续登出,则该登录记录的
LogoutTime为NULL,符合你原C#逻辑的处理方式。 - 性能优化建议:如果
EventLogs表数据量较大,建议创建以下索引来加速查询:CREATE NONCLUSTERED INDEX IX_EventLogs_UserId_EventType_EventTime ON EventLogs (UserId, EventType, EventTime);
内容的提问来源于stack exchange,提问作者Rohit
相关产品推荐
相关产品推荐

