有人可以给我一个想法或暗示如何在存储登录(用户ID,时间戳)的数据库表(MySQL)中连续X天检查吗?
Stackoverflow可以做到这一点(例如像Enthusiast这样的徽章 - 如果你连续登录30天左右......)。您需要使用哪些功能或者如何使用它?
像SELECT 1 FROM login_dates WHERE ...
?
答案 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 ,则会返回1
或0
在过去。
这个查询首当其冲的是第一个子选择。让我们仔细看看,以便我们更好地了解其工作原理:
使用以下示例数据集:
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
。
答案 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 |