SQL 语句为:
1 | SELECT DISTINCT t.uid FROM tmp_test AS t JOIN tmp_test AS t1 |
2 | ON date (t.login_time) + 1 <= date (t1.login_time) AND |
3 | date (t.login_time) + 7 > date (t1.login_time) AND |
5 | WHERE t.login_time BETWEEN ’2012-1-1 00:00:00′ AND ’2012-1-11 23:59:59′ AND |
6 | t1.login_time >= ’2012-1-2′ AND t.login_time < ’2012-1-18′(可去掉) |
- COUNT(DISTINCT)
“计算连续7天”,可以通过GROUP BY分组和COUNT()来完成。因为一个用户在1天内可能会有多次登录,
这里需要使用(COUNT DISTINCT). SQL 语句为:
1 | GROUP BY t.login_time, t.uid |
2 | HAVING COUNT ( DISTINCT date (t1.login_time))=6 |
- BIT_OR
考虑到DISTINCT操作需要缓存数据,就想到了用bit逻辑运算(可能会效率高一些)。因为连续的七天
与第一天的差分别为,1,2,3,4,5,6,7.可以分别用1-7bit位来表示。根据这个特点,可以对分组中
的每一行进行或(|)运算.如果最后的值等于b’1111110′(6个1).那么就是连续的7天。这个办法可以
避免DISTINC操作。没想到MySQL中真的有了bit操作的聚合函数。BIT_OR就是我们要用的。
SQL 语句为:
1 | GROUP BY t.login_time, t.uid |
2 | HAVING BIT_OR(1 << datediff(t1.login_time, t.login_time)) = b’1111110′; |
NOTE: 从测试结果看,没有索引时BIT_OR要比DISTINCT好一点点,不是非常明显。当DISTINCT的
字段上有索引时,要比BIT_OR要好一点点.
- 去掉Range Join
虽说上面的思路实现了这个查询要求,但是由于使用了Range Join,效率并不好。在对uid建索引的情
况下,大约需要3.5s(总共约50000条记录). 有没有更好的方法呢?
受BIT_OR的启发,可以通过单表扫描,用bit位来记录每个用户2012-1-1至2012-1-17是否有登录。
然后根据这个值来判断是否有连续7天的情况。
我们需要一个辅助的函数来进行bit的运算:
02 | /* 判断一个 Bit 序列中,是否存在若干个连续的1 */ |
04 | /* 参数trait: 指定的若干连续的1.如b’111111‘ */ |
05 | CREATE FUNCTION bits_find_N1(bits BIGINT , trait BIGINT ) |
09 | IF ((bits & trait) = trait) THEN |
SQL 语句为:
1 | SELECT uid AS bit FROM tmp_test |
2 | WHERE login_time BETWEEN ’2012-1-1 00:00:00′ AND ’2012-1-17 23:59:59′ |
4 | HAVING bits_find_N1(BIT_OR(1 << datediff(login_time, ’2012-1-1′)), |
这个语句效率还是比较好的,即使不对uid建索引,也只需约0.27s
- 超高效率的语句
下面是另一个朋友写的SQL,虽然有点复杂,但是效率超高,只需要约0.17s是这样的
03 | FROM ( SELECT MAX ( date )- MIN ( date ) less,uid |
04 | FROM ( SELECT date -rn diff, uid, date , rn |
05 | FROM ( SELECT @wy:=@wy+1 rn, uid, |
06 | datediff(login_time,’1971-01-01′) date ,login_time |
07 | FROM ( SELECT date (login_time) login_time, uid FROM tmp_test |
08 | WHERE login_time>=’2012-01-01 00:00:00′ AND |
09 | login_time <’2012-01-18 00:00:00′ |
10 | GROUP BY uid, date (login_time) |
11 | ORDER BY uid, date (login_time) |