在移动日期范围内汇总记录,日期距离

时间:2018-06-20 10:51:44

标签: sql sql-server tsql

我对用户日志记录系统有复杂的计算要求。我需要根据180天之内的登录次数来找到最活跃的用户。一旦两个登录日期相隔181天,则它们不会计入总计,但与其他日期分组在一起时可以计入总计。

例如,这是吉姆的登录历史记录:

Jim 2018-01-01
Jim 2018-04-01
Jim 2018-05-01
Jim 2018-06-01
Jim 2018-07-01
Jim 2018-08-01
Jim 2018-09-01
Jim 2018-12-01

Jim为了简单起见,使用6个月而不是180天,并且只看一个方向的6个月,得出以下总数:

Logins: 5 (2018-01-01 + 6 months)
Logins: 6 (2018-04-01 + 6 months)
Logins: 5 (2018-05-01 + 6 months)
Logins: 5 (2018-06-01 + 6 months)
Logins: 4 (2018-07-01 + 6 months)
Logins: 3 (2018-08-01 + 6 months)
Logins: 2 (2018-09-01 + 6 months)
Logins: 1 (2018-12-01 + 6 months)

所以我的系统会报告6,因为它只想要最大的总数。

除了蛮力计算之外,我对如何构建该系统一无所知。是的,我可以对数据进行非正规化,速度是最重要的。

3 个答案:

答案 0 :(得分:1)

一个基本的解决方案使用join

select l.*
from (select l.name, count(*) as cnt,
             row_number() over (partition by name order by count(*) desc) as seqnum
      from logins l join
           logins l2
           on l.name = l2.name and
              l2.date >= l.date and l2.date < dateadd(day, 181, l.date)
      group by l.name
     ) l
where seqnum = 1;

logins(name, date)上使用索引可能会具有可接受的性能。

答案 1 :(得分:1)

尝试一下:

declare @tbl table(name char(3), dt date);
insert into @tbl values
('Jim', '2018-01-01'),
('Jim', '2018-04-01'),
('Jim', '2018-05-01'),
('Jim', '2018-06-01'),
('Jim', '2018-07-01'),
('Jim', '2018-08-01'),
('Jim', '2018-09-01'),
('Jim', '2018-12-01');

;with cte as (
    select name, dt, DATEADD(day, 181, dt) upperDt from @tbl
), cte2 as (
    select name,
           (select COUNT(*) from cte where dt between c.dt and c.upperDt and name = c.name) cnt
    from cte c
)

select name, MAX(cnt) [max] 
from cte2 
group by name

答案 2 :(得分:1)

使用Common Table Expression计算EndDate窗口并使用CROSS APPLY计算登录总数

DECLARE @t TABLE (UserName NVARCHAR(10), LoginDate DATETIME)
INSERT INTO @t
(UserName,LoginDate) VALUES
('Jim','2018-01-01'),
('Jim','2018-04-01'),
('Jim','2018-05-01'),
('Jim','2018-06-01'),
('Jim','2018-07-01'),
('Jim','2018-08-01'),
('Jim','2018-09-01'),
('Jim','2018-12-01')

; WITH CteDateRange
AS(
    SELECT
         T.UserName
        ,T.LoginDate 
        --,EndDateRange = DATEADD(DAY, 181, LoginDate)
        ,EndDateRange = DATEADD(MONTH, 6, LoginDate)
    FROM @t T
)
SELECT
     DR.UserName
    ,DR.LoginDate
    ,DR.EndDateRange
    ,T.Total
FROM CteDateRange DR
CROSS APPLY (   SELECT  Total = COUNT(D.LoginDate) 
                FROM    CteDateRange D 
                WHERE   D.LoginDate >= DR.LoginDate 
                AND     D.LoginDate <= DR.EndDateRange 
                AND     D.UserName = DR.UserName
            ) T

输出

UserName    LoginDate               EndDateRange            Total
Jim         2018-01-01 00:00:00.000 2018-07-01 00:00:00.000 5
Jim         2018-04-01 00:00:00.000 2018-10-01 00:00:00.000 6
Jim         2018-05-01 00:00:00.000 2018-11-01 00:00:00.000 5
Jim         2018-06-01 00:00:00.000 2018-12-01 00:00:00.000 5
Jim         2018-07-01 00:00:00.000 2019-01-01 00:00:00.000 4
Jim         2018-08-01 00:00:00.000 2019-02-01 00:00:00.000 3
Jim         2018-09-01 00:00:00.000 2019-03-01 00:00:00.000 2
Jim         2018-12-01 00:00:00.000 2019-06-01 00:00:00.000 1