谁能给我一个想法或提示您如何在存储登录名(用户ID,时间戳)的数据库表(MySQL)中检查连续X天?
Stackoverflow 做到了(例如,像 Enthusiast 这样的徽章——如果你连续登录 30 天左右......)。您必须使用哪些功能或如何使用它的想法是什么?
像SELECT 1 FROM login_dates WHERE ...
什么?
谁能给我一个想法或提示您如何在存储登录名(用户ID,时间戳)的数据库表(MySQL)中检查连续X天?
Stackoverflow 做到了(例如,像 Enthusiast 这样的徽章——如果你连续登录 30 天左右......)。您必须使用哪些功能或如何使用它的想法是什么?
像SELECT 1 FROM login_dates WHERE ...
什么?
您可以使用移位自外连接和变量来完成此操作。请参阅此解决方案:
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 天或更长时间,这将返回 a1
或 a 。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 天。对于与前一天不连续的每一天,NULL
LEFT JOIN 会生成一个值。
现在我们知道了非连续天在哪里,我们可以使用一个变量来区分每组连续天,方法是检测移位表的行是否为NULL
。如果它们是NULL
,则这些天不是连续的,因此只需增加变量即可。如果它们是NOT NULL
,则不要增加变量:
在我们用递增变量区分每组连续天后,只需按每个“组”(如consec_set
列中定义)进行分组并使用HAVING
过滤掉任何少于指定连续天数的集合(在您的示例中为 30):
最后,我们包装那个查询并简单地计算连续 30 天或更多天的集合数。如果有这些集合中的一个或多个,则返回1
,否则返回0
。
您可以将 X 添加到时间戳日期并检查此日期范围内的 distinct( dates ) 是否为 == 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
这是一个单独使用 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
这进一步假设登录表每天都会随着会话更新。
我不知道这是否真的解决了你的问题,但我希望我已经帮助解决了这个问题。
祝你好运。
在 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 |