当某些记录不完整时,计算登录和注销之间的持续时间

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_timeDATEDIFF 将返回 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

获取 logback.xml 以记录不可变日志

使用 Ionic 框架在登录/注销时清除历史记录并重新加载页面

在mysql中计算登录注销时间

拦截登录/注销 ejabberd

Laravel - 注销特定用户