检查连续x天 - 给定数据库中的时间戳

时间:2012-07-18 10:13:35

标签: mysql sql database gaps-and-islands

有人可以给我一个想法或暗示如何在存储登录(用户ID,时间戳)的数据库表(MySQL)中连续X天检查吗?

Stackoverflow可以做到这一点(例如像Enthusiast这样的徽章 - 如果你连续登录30天左右......)。您需要使用哪些功能或者如何使用它?

SELECT 1 FROM login_dates WHERE ...

之类的东西

4 个答案:

答案 0 :(得分:28)

您可以使用移位的自外连接和变量来完成此操作。看到这个解决方案:

SELECT IF(COUNT(1) > 0, 1, 0) AS has_consec
FROM
(
    SELECT *
    FROM
    (
        SELECT IF(b.login_date IS NULL, @val:=@val+1, @val) AS consec_set
        FROM tbl a
        CROSS JOIN (SELECT @val:=0) var_init
        LEFT JOIN tbl b ON 
            a.user_id = b.user_id AND
            a.login_date = b.login_date + INTERVAL 1 DAY
        WHERE a.user_id = 1
    ) a
    GROUP BY a.consec_set
    HAVING COUNT(1) >= 30
) a

如果用户已连续30天或更长时间登录 ANYTIME ,则会返回10在过去。

这个查询首当其冲的是第一个子选择。让我们仔细看看,以便我们更好地了解其工作原理:

使用以下示例数据集:

CREATE TABLE tbl (
  user_id INT,
  login_date DATE
);

INSERT INTO tbl VALUES
(1, '2012-04-01'),  (2, '2012-04-02'),
(1, '2012-04-25'),  (2, '2012-04-03'),
(1, '2012-05-03'),  (2, '2012-04-04'),
(1, '2012-05-04'),  (2, '2012-05-04'),
(1, '2012-05-05'),  (2, '2012-05-06'),
(1, '2012-05-06'),  (2, '2012-05-08'),
(1, '2012-05-07'),  (2, '2012-05-09'),
(1, '2012-05-09'),  (2, '2012-05-11'),
(1, '2012-05-10'),  (2, '2012-05-17'),
(1, '2012-05-11'),  (2, '2012-05-18'),
(1, '2012-05-12'),  (2, '2012-05-19'),
(1, '2012-05-16'),  (2, '2012-05-20'),
(1, '2012-05-19'),  (2, '2012-05-21'),
(1, '2012-05-20'),  (2, '2012-05-22'),
(1, '2012-05-21'),  (2, '2012-05-25'),
(1, '2012-05-22'),  (2, '2012-05-26'),
(1, '2012-05-25'),  (2, '2012-05-27'),
                    (2, '2012-05-28'),
                    (2, '2012-05-29'),
                    (2, '2012-05-30'),
                    (2, '2012-05-31'),
                    (2, '2012-06-01'),
                    (2, '2012-06-02');

此查询:

SELECT a.*, b.*, IF(b.login_date IS NULL, @val:=@val+1, @val) AS consec_set
FROM tbl a
CROSS JOIN (SELECT @val:=0) var_init
LEFT JOIN tbl b ON 
    a.user_id = b.user_id AND
    a.login_date = b.login_date + INTERVAL 1 DAY
WHERE a.user_id = 1

将产生:

Example Result

正如您所看到的,我们正在做的是将联接表移动 +1天。对于前一天不连续的每一天,LEFT JOIN生成NULL值。

现在我们知道非连续日的位置,我们可以通过检测移位表的行来使用变量来区分连续几天的每个NULL。如果它们是NULL,则日期不是连续的,所以只需增加变量即可。如果它们是NOT NULL,则不要增加变量:

Result With Highlighted Groupings

在我们使用递增变量区分每组连续天数后,只需按每个“集合”(在consec_set列中定义)并使用HAVING进行分组这一事项很简单过滤掉任何小于指定连续天数的集合(在您的示例中为30):

Result With Grouping By The consec_set column

最后,我们将 THAT 查询包装起来,并简单地计算连续30天或更多天的集合数量。如果有一个或多个这样的集合,则返回1,否则返回0


参见SQLFiddle step-by-step demo

答案 1 :(得分:5)

如果此日期范围内的不同(日期)为== X,您可以将X添加到时间戳日期和chech:

这30天中每天至少一次:

SELECT distinct 1 
FROM 
   login_dates l1 
inner join
   login_dates l2
      on l1.user = l2.user and 
         l2.timestamp between l1.timestamp and  
                              date_add( l1.timestamp, Interval X day )
where l1.user = some_user
group by 
   DATE(l1.timestamp)
having 
   count( distinct DATE(l1.timestamp) ) = X

(你不需要考虑性能要求......;))

*已编辑* 仅查询过去X天:该30天内每天一次的东方

SELECT distinct 1 
FROM 
   login_dates l1 
where l1.user = some_user
      and l1.timestamp >  date_add( CURDATE() , Interval -X day )
group by
    l1.user
having 
   count( distinct DATE(l1.timestamp) ) = X

答案 2 :(得分:1)

单独使用SQL解决这个难题。

问题的核心是您需要在一个查询中将动态结果集相互比较。例如,您需要获取一个DATE的所有登录/会话ID,然后使用列表JOIN或UNION它们来自DATE()的一组登录(您可以使用DATE_ADD来确定)。您可以为N个连续日期执行此操作。如果您还有任何行,那么这段会话已经过了一段时间。

假设下表:

sessionid int,创建日期

此查询返回过去两天有行的所有会话ID:

select t1.sessionid from logins t1 
  join logins t2 on t1.sessionid=t2.sessionid 
  where t1.created = DATE(date_sub(now(), interval 2 day)) 
    AND t2.created = DATE(date_sub(now(), interval 1 day));

正如您所看到的,SQL将在30天内变得粗糙。让脚本生成它。 :-D

这进一步假设每天都会使用会话更新登录表。

我不知道这是否真的解决了你的问题,但我希望我帮助解决问题。

祝你好运。

答案 3 :(得分:0)

在login_dates表中使用默认值1添加额外列的连续日期不是更简单。这将指示在该日结束的连续日期的长度。

您在login_dates触发后创建一个插入,在那里检查是否有前一天的条目。

如果没有,则该字段将具有默认值1,表示在该日期开始新序列。

如果这是前一天的条目,那么您将days_logged_in值从默认值1更改为比前一天更大的值。

例如:

| date       | consecutive_days |
|------------|------------------|
| 2013-11-13 | 5                |
| 2013-11-14 | 6                |
| 2013-11-16 | 1                |
| 2013-11-17 | 2                |
| 2013-11-18 | 3                |