当某些记录不完整时,计算登录和注销之间的持续时间
Posted
技术标签:
【中文标题】当某些记录不完整时,计算登录和注销之间的持续时间【英文标题】:Calculate duration between login and logout time when some records are incomplete 【发布时间】:2016-05-24 12:34:44 【问题描述】:我想计算用户在网站上花费的总时间。有3种情况。
存在用户登录时间和注销时间的记录。
-->总时间应该是登录和注销时间差的总和。
有用户登录时间记录,但没有注销时间。
-->总时间应标记为-1。
用户多次登录,只有一次退出。
-->总时间应该是最早登录时间和注销时间的时间差总和。
我的桌子
CREATE TABLE #my_table
(
id BIGINT IDENTITY PRIMARY KEY
,userID INT
,login_time DATETIME
,logout_time DATETIME
);
INSERT INTO #my_table
SELECT 222222, '2016-05-19 01:06:00.000', '2016-05-19 01:10:00.000'
UNION ALL SELECT 222222, '2016-05-19 01:12:00.000', '2016-05-19 01:20:00.000'
UNION ALL SELECT 333333, '2016-05-24 14:44:00.000', '2016-05-24 14:47:00.000'
UNION ALL SELECT 333333, '2016-05-24 14:59:00.000', NULL
UNION ALL SELECT 444444, '2016-05-24 14:48:00.000', '2016-05-24 14:49:00.000'
UNION ALL SELECT 444444, '2016-05-24 14:50:00.000', NULL
UNION ALL SELECT 444444, '2016-05-24 14:51:00.000', NULL
UNION ALL SELECT 444444, '2016-05-24 14:53:00.000', '2016-05-24 14:59:00.000'
预期结果
对于大多数情况,数据库中捕获的记录将是案例 1,但有时也会捕获案例 2 和案例 3。我需要一个脚本来计算所有情况的总登录时间。
应该如何查询?
【问题讨论】:
请添加带有 SQL Server 版本的标签,并将您的示例数据显示为 文本,而不是图像。如果您的示例数据采用INSERT
语句的形式,那就更好了。
请以文本的形式提供示例数据
@VladimirBaranov:谢谢!我已经在代码中添加了。
【参考方案1】:
下面的查询使用ROW_NUMBER
函数多次选择需要的行,并在logout_time
为NULL 时使用LEAD
函数“向前看”。 LEAD
自 SQL Server 2012 起可用。
逐步、逐个 CTE 运行查询并检查中间结果以了解其工作原理。
CTE_Groups
是一个经典的gaps-and-islands
查询,用于标记logout_time
中具有连续NULL 的行。
CTE_RN
以这样一种方式将数字分配给行,logout_time
中的连续 NULL 获得连续数字。此结果在CTE_Fixed
中过滤,以仅获取每组 NULL 的第一行。如果logout_time
为NULL,则使用LEAD
函数从下一行中选择一个值来生成fixed_logout_time
。
logout_time
为 NULL 的行和logout_time
为非 NULL 的下一行将在CTE_Fixed
中一起列出。我们只需要从这些对中挑出一行。同样的方法 - 在CTE_FixedRN
中使用ROW_NUMBER
并在CTE_Sum
中选择第一行。
然后我们可以以分钟为单位计算Duration
,并将总和按userID
分组。
如果没有非 NULL logout_time
,DATEDIFF
将返回 NULL,它将被一些大的负数替换。在最后的SELECT
中,否定的Duration
将替换为-1
,以表明最后一个区间仍然打开。
WITH
CTE_Groups
AS
(
SELECT
userID
,login_time
,logout_time
,ROW_NUMBER()
OVER(PARTITION BY userID ORDER BY login_time)
- ROW_NUMBER()
OVER(PARTITION BY userID, logout_time ORDER BY login_time) AS GroupNumber
FROM #my_table
)
,CTE_RN
AS
(
SELECT
userID
,login_time
,logout_time
,ROW_NUMBER()
OVER(PARTITION BY userID, GroupNumber ORDER BY login_time) AS rn
FROM CTE_Groups
)
,CTE_Fixed
AS
(
SELECT
userID
,login_time
,ISNULL(logout_time, LEAD(logout_time)
OVER(PARTITION BY userID ORDER BY login_time)) AS fixed_logout_time
FROM CTE_RN
WHERE rn = 1
)
,CTE_FixedRN
AS
(
SELECT
userID
,login_time
,fixed_logout_time
,ROW_NUMBER()
OVER(PARTITION BY userID, fixed_logout_time ORDER BY login_time) AS rn
FROM CTE_Fixed
)
,CTE_Sum
AS
(
SELECT
userID
,SUM(ISNULL(
DATEDIFF(minute, login_time, fixed_logout_time),
-1000000)) AS Duration
FROM CTE_FixedRN
WHERE rn = 1
GROUP BY userID
)
SELECT
userID
,CASE WHEN Duration < 0 THEN -1 ELSE Duration END AS Duration
FROM CTE_Sum
ORDER BY userID;
结果
+--------+----------+
| userID | Duration |
+--------+----------+
| 222222 | 12 |
| 333333 | -1 |
| 444444 | 10 |
+--------+----------+
【讨论】:
这个解决方案非常棒.. 非常感谢,弗拉基米尔! @eric,不客气。考虑为有用的答案投票并接受对您最有帮助的答案。【参考方案2】:IF OBJECT_ID('tempdb..#my_table') IS NOT NULL
DROP TABLE #my_table
CREATE TABLE #my_table
(
id BIGINT IDENTITY PRIMARY KEY
,userID INT
,login_time DATETIME
,logout_time DATETIME
);
DECLARE @MT TABLE
(
id BIGINT
,userID INT
,login_time DATETIME
,logout_time DATETIME
);
DECLARE @DRes TABLE (
userID INT,
logtime INT
)
DECLARE @Counter1 INT = 0,
@login_time1 DATETIME,
@logout_time1 DATETIME,
@login_time2 DATETIME
INSERT INTO #my_table VALUES
(222222, '2016-05-19 01:06:00.000', '2016-05-19 01:10:00.000')
,(222222, '2016-05-19 01:12:00.000', '2016-05-19 01:20:00.000')
,(333333, '2016-05-24 14:44:00.000', '2016-05-24 14:47:00.000')
,(333333, '2016-05-24 14:59:00.000', NULL)
,(444444, '2016-05-24 14:48:00.000', '2016-05-24 14:49:00.000')
,(444444, '2016-05-24 14:50:00.000', NULL)
,(444444, '2016-05-24 14:51:00.000', NULL)
,(444444, '2016-05-24 14:53:00.000', '2016-05-24 14:59:00.000')
INSERT INTO @MT
SELECT * FROM #my_table
;WITH MaxLog
AS (
SELECT userID, MAX(login_time) AS max_login
FROM @MT
GROUP BY userID
),
DelRec
AS (
SELECT ml.userID
FROM MaxLog ml
LEFT JOIN @MT mt
ON ml.userID = mt.userID
WHERE mt.logout_time IS NULL
AND ml.max_login = mt.login_time
)
DELETE mt
FROM @MT mt
INNER JOIN
DelRec dr
ON mt.userID = dr.userID
WHERE mt.logout_time IS NOT NULL
;WITH StillIn
AS (
SELECT userID, COUNT(*) AS cnt
FROM @MT
GROUP BY userID
HAVING COUNT(*) = 1
)
UPDATE mt
SET logout_time = DATEADD(mi,-1,login_time)
FROM @MT mt
JOIN StillIn si
ON si.UserID = mt.UserId
WHILE @Counter1 < (SELECT MAX(id) FROM @MT)
BEGIN
SET @Counter1 += 1
SET @login_time1 = (SELECT login_time FROM @MT WHERE id = @Counter1)
SET @logout_time1 = (SELECT logout_time FROM @MT WHERE id = @Counter1)
IF @logout_time1 IS NULL
BEGIN
IF @login_time2 IS NULL
BEGIN
SET @login_time2 = @login_time1
END
END
ELSE
BEGIN
IF @login_time2 IS NULL
BEGIN
INSERT INTO @DRes
SELECT userID, DATEDIFF(mi,@login_time1,@logout_time1)
FROM @MT
WHERE id = @Counter1
END
ELSE
BEGIN
INSERT INTO @DRes
SELECT userID, DATEDIFF(mi,@login_time2,@logout_time1)
FROM @MT
WHERE id = @Counter1
SET @login_time2 = NULL
END
END
END
SELECT userID, SUM(logtime)
FROM @DRes
GROUP BY userID
【讨论】:
超时。如果可以的话,我会回来完成这件事。但至少有人有更好的起点。 所以,这很丑,我承认。但它有效。 对我来说,您的方法最大的问题不是WHILE
循环和逐行构建结果(这可能很慢),而是您更改了原始数据这一事实。
对,我后来想到了。我会继续修复它。我还将修改原始插入语句...希望适用于您的系统?以上是关于当某些记录不完整时,计算登录和注销之间的持续时间的主要内容,如果未能解决你的问题,请参考以下文章
不知何故,某些 css 仅在用户登录时显示 - 而对已注销的用户不显示。 HTML/CSS