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

Posted

技术标签:

【中文标题】检查 x 连续天 - 数据库中的给定时间戳【英文标题】:Check for x consecutive days - given timestamps in database 【发布时间】:2012-07-18 10:13:35 【问题描述】:

谁能给我一个想法或提示,如何在存储登录名(用户 ID、时间戳)的数据库表 (mysql) 中检查连续 X 天?

*** 会做到这一点(例如,像 Enthusiast 这样的徽章 - 如果您连续登录 30 天左右......)。您必须使用哪些功能或如何使用的想法是什么?

类似SELECT 1 FROM login_dates WHERE ...

【问题讨论】:

你的意思是从今天开始,还是只是一个范围? @podiluska:一般来说,这就是重点:) 所以,不限于过去 30 天,而是看看是否有连续 30 天... 【参考方案1】:

您可以将移位自外连接与变量结合使用来完成此操作。请参阅此解决方案:

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

这将返回 10,这取决于用户过去是否在任何时间连续登录 30 天或更长时间。

这个查询的首当其冲实际上是在第一个子选择中。让我们仔细看看,以便更好地理解它是如何工作的:

使用以下示例数据集:

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

将产生:

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

现在我们知道不连续的天在哪里,我们可以使用一个变量来区分每个连续天的,通过检测移位表的行是否是NULL。如果它们是NULL,则天数不是连续的,因此只需递增变量即可。如果它们是NOT NULL,则不要增加变量:

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

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


见SQLFiddle step-by-step demo

【讨论】:

现在还不确定 SQL Fiddle 发生了什么,但现在又回来了。 哇,这太棒了!非常感谢赞恩的努力!并通过示例和分步指南获得这个全面的答案!哇!再次感谢! :) 这是本网站上最好的答案之一。如果可以的话,我会给这个 +10。 但是如何选择 consec_count 最高的用户? @Zane-bien 我需要重新阅读您的答案几次,但这也是解决我的问题的开始(我认为)。在我的情况下,我需要找到列值具有值 P(resent) 或 S(tay) 的连续天数(5、9 或 30)。所以不是真正意义上的连续。我只需要继续计数,直到有 5 、 9 或 30 个实例并吐出相应的值。问题是您的 SQL Fiddle 似乎不再起作用了???我喜欢看到这一点。我会自己问一个适当的问题,但任何提示、指针或链接都非常感谢【参考方案2】:

您可以将 X 添加到时间戳日期并检查此日期范围内的不同(日期)是否 == X:

这 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

【讨论】:

哇,看起来很复杂 :) 谈论过去 30 天,这是否会返回用户在过去 30 天内登录 30 次的结果?或者只是那些在这 30 天内每天至少登录一次的人? 如果用户在这 30 天内每天至少登录一次,则此查询返回 1。最近 30 天的查询限制非常简单。我会发布它。 哦,哇,谢谢 danihp!你称之为“真的很容易”吗?太好了,这个星球上有这么多聪明的人! :) 所以,每次登录时最好检查一下,这样你就解决了很多麻烦,对吧?因为那样你只需要检查过去 30 天... 第二个查询比第一个查询更轻松。记得创建一个包含用户和时间戳字段的复合索引。【参考方案3】:

这是一个单独使用 SQL 很难解决的问题。

问题的核心是您需要在一个查询中比较动态结果集。例如,您需要获取一个 DATE 的所有登录名/会话 ID,然后将它们与 DATE() 中的一组登录名的列表一起加入或联合(您可以使用 DATE_ADD 来确定)。您可以对 N 个连续日期执行此操作。如果您还剩下任何行,那么这些会话已在此期间登录。

假设如下表:

sessionid int,创建日期

此查询返回过去两天有行的所有 sessionid:

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

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

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

祝你好运。

【讨论】:

【参考方案4】:

如果在 login_dates 表中增加一个默认值为 1 的 Continuous_days 列不是更简单吗?这将指示以那一天结束的连续日期的长度。

您在 login_dates 的触发器之后创建一个插入,您可以在其中检查是否有前一天的条目。

如果没有,则该字段将具有默认值 1,这意味着在该日期开始一个新序列。

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

例如:

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

【讨论】:

虽然这可能适用于正在开发的新表和系统,但它并不能解决这里的一般问题,即查询已经存在的数据。 这个问题也有一年多的历史了,已经有一个非常强的接受答案。非常感谢您的意见,但您更有可能通过回答最近的问题或没有被接受的答案的旧问题来获得支持和声誉。 我知道这是一个老话题,但在发布我的解决方案之前,我自己正在寻找同样问题的解决方案。当我正在构建一个新系统时,我的解决方案似乎更合适。我发布它是因为人们可能会在功能中偶然发现这个线程(就像我在一年半后所做的那样),如果他们正在构建一个新系统或者可以轻松更改他们正在使用的系统,我的解决方案更简单,并且比接受的要快得多。 是的,我同意。请理解,我无意冒犯他人,我只是在审核您的第一篇帖子,并希望提供一些指导。欢迎来到 SO。

以上是关于检查 x 连续天 - 数据库中的给定时间戳的主要内容,如果未能解决你的问题,请参考以下文章

查找第一个“连续第 x 天”

Scala:检查当前时间戳是不是大于我的数据框中的时间戳列

Bigtable:在行键上使用时间戳时避免热点

SQL:查找给定字段连续几天具有不同字符串值的记录

SQL - 显示给定范围内的所有日期,并使用数据库中的时间戳计算该日期有多少帖子

c_cpp 编写一个函数来检查给定字符串是否与给定模式匹配为非连续子字符串:即,模式中的所有字符