sql range 范围内查询
?一個(gè)朋友有這樣一個(gè)SQL查詢需求:
有一個(gè)登錄表(tmp_test),包含用戶ID(uid)和登錄時(shí)間(login_time)。表結(jié)構(gòu)如下:*************************** 1. row ***************************
Field: uid
Type: int(10) unsigned
Null: NO
Key: MUL
Default: NULL
Extra:
*************************** 2. row ***************************
Field: login_time
Type: timestamp
Null: NO
Key: MUL
Default: 0000-00-00 00:00:00
Extra:
問如何查詢出所有在某一段時(shí)間內(nèi)(如:2012-1-1至2012-1-17)連續(xù)7天都有登錄的用戶。
在寫這個(gè)SQL時(shí),發(fā)現(xiàn)一些很有意思東西,也許對(duì)大家寫SQL有幫助,因此記錄一下。
- 基本思路 Loop Join
??首先想到的思路是一個(gè)類似于Loop Join的方法:
? A. 取出2012-1-1到2012-1-11的每一條記錄.
? B. 對(duì)取出的每一條記錄,再去表中查詢這個(gè)用戶的接下來6天的記錄。
? ? ?如果總數(shù)為6條記錄,則滿足連續(xù)7天的條件
- Range Join
? Loop Join的思路可以通過一個(gè)Join語句來實(shí)現(xiàn)。姑且稱之為Range Join。通常join時(shí),使用的都是
? 等值join. 如果join列的值是唯一的,那么就是左表的一條記錄對(duì)應(yīng)右表的一條記錄。而Range Join
? 中,左表的一行數(shù)據(jù)對(duì)應(yīng)右表的一個(gè)范圍內(nèi)的所有記錄。
? SQL 語句為:
? SELECT DISTINCT t.uid FROM tmp_test AS t JOIN tmp_test AS t1
? ON?date(t.login_time) + 1 <= date(t1.login_time) AND
? ? ?date(t.login_time) + 7 > date(t1.login_time) AND
? ?? t.uid = t1.uid
? WHERE t.login_time BETWEEN ’2012-1-1 00:00:00′ AND?’2012-1-11 23:59:59′?AND
? ? ? ??t1.login_time >=?’2012-1-2′?AND t.login_time < ’2012-1-18′(可去掉)
- COUNT(DISTINCT)
? “計(jì)算連續(xù)7天”,可以通過GROUP BY分組和COUNT()來完成。因?yàn)橐粋€(gè)用戶在1天內(nèi)可能會(huì)有多次登錄,
? 這里需要使用(COUNT DISTINCT).?SQL 語句為:
? GROUP BY t.login_time, t.uid
? HAVING COUNT(DISTINCT date(t1.login_time))=6
- BIT_OR
? 考慮到DISTINCT操作需要緩存數(shù)據(jù),就想到了用bit邏輯運(yùn)算(可能會(huì)效率高一些)。因?yàn)檫B續(xù)的七天
? 與第一天的差分別為,1,2,3,4,5,6,7.可以分別用1-7bit位來表示。根據(jù)這個(gè)特點(diǎn),可以對(duì)分組中
? 的每一行進(jìn)行或(|)運(yùn)算.如果最后的值等于b’1111110′(6個(gè)1).那么就是連續(xù)的7天。這個(gè)辦法可以
? 避免DISTINC操作。沒想到MySQL中真的有了bit操作的聚合函數(shù)。BIT_OR就是我們要用的。
? SQL 語句為:
? GROUP BY t.login_time, t.uid
? HAVING?BIT_OR(1 << datediff(t1.login_time, t.login_time)) = b’1111110′;
? NOTE: 從測(cè)試結(jié)果看,沒有索引時(shí)BIT_OR要比DISTINCT好一點(diǎn)點(diǎn),不是非常明顯。當(dāng)DISTINCT的
? ? ? ? 字段上有索引時(shí),要比BIT_OR要好一點(diǎn)點(diǎn).
- 去掉Range Join
? 雖說上面的思路實(shí)現(xiàn)了這個(gè)查詢要求,但是由于使用了Range Join,效率并不好。在對(duì)uid建索引的情
? 況下,大約需要3.5s(總共約50000條記錄). 有沒有更好的方法呢?
? 受BIT_OR的啟發(fā),可以通過單表掃描,用bit位來記錄每個(gè)用戶2012-1-1至2012-1-17是否有登錄。
? 然后根據(jù)這個(gè)值來判斷是否有連續(xù)7天的情況。
? 我們需要一個(gè)輔助的函數(shù)來進(jìn)行bit的運(yùn)算:
? DELIMITER |
? /* 判斷一個(gè)Bit序列中,是否存在若干個(gè)連續(xù)的1 */
? /* 參數(shù)bits: bit序列*/
? /* 參數(shù)trait: 指定的若干連續(xù)的1.如b’111111‘ */
? CREATE FUNCTION bits_find_N1(bits BIGINT, trait BIGINT)
? RETURNS BOOL
? BEGIN
? ? WHILE bits <> 0 DO
? ? ? IF ((bits & trait) = trait) THEN
? ? ? ? RETURN TRUE;
? ? ? END IF;
? ? ? SET bits = bits >> 1;
? ? END WHILE;
? ? RETURN FALSE;
? END|
? DELIMITER ;
? SQL 語句為:
? SELECT uid AS bit FROM tmp_test
? WHERE login_time BETWEEN ’2012-1-1 00:00:00′ AND ’2012-1-17 23:59:59′
? GROUP BY uid
? HAVING bits_find_N1(BIT_OR(1 << datediff(login_time,?’2012-1-1′)),
? ? ? ? ? ? ? ? ? ? ? ? ? ?? b’1111111′) IS TRUE;
? 這個(gè)語句效率還是比較好的,即使不對(duì)uid建索引,也只需約0.27s
- 超高效率的語句
? 下面是另一個(gè)朋友寫的SQL,雖然有點(diǎn)復(fù)雜,但是效率超高,只需要約0.17s是這樣的
? SET @wy=0;
? SELECT DISTINCT uid
? FROM (SELECT MAX(date)-MIN(date) less,uid
? ? ? ? FROM (SELECT date-rn diff, uid, date, rn
?? ? ? ? ? ?? FROM (SELECT @wy:=@wy+1 rn, uid,
? ?? ? ? ? ? ?? ?? ? ? ? ??datediff(login_time,’1971-01-01′) date,login_time
? ?? ? ? ? ? ?? ??? FROM (SELECT date(login_time) login_time, uid FROM tmp_test
? ?? ? ? ?? ? ?? ? ?? ??? WHERE login_time>=’2012-01-01 00:00:00′ AND
? ?? ? ? ? ? ?? ??? ? ?? ?? ??? login_time <’2012-01-18 00:00:00′
? ?? ? ? ? ? ?? ??? ? ??? GROUP BY uid, date(login_time)
? ?? ? ? ? ? ?? ??? ? ??? ORDER BY uid, date(login_time)
? ?? ? ? ? ? ?? ?? ? ??? )x
?? ? ? ? ? ? ? ??? )x
? ? ?? ? ??? )x
? ?? ??? GROUP BY diff,uid
? ? ??? )x?
??WHERE less>=6;
附上測(cè)試數(shù)據(jù),供大家驗(yàn)證。tmp_test
由于用的是timestamp類型,導(dǎo)入后時(shí)間可能會(huì)有變化,導(dǎo)致結(jié)果不一樣。我們測(cè)試的結(jié)果有183,185兩種。
另外:用戶可以在同一秒內(nèi)登錄多次,即出現(xiàn)多條相同的記錄。
如uid=1, login_time=’2012-1-1 00:00:00′ 會(huì)出現(xiàn)多次。
轉(zhuǎn)載于:https://blog.51cto.com/jilson/1055350
總結(jié)
以上是生活随笔為你收集整理的sql range 范围内查询的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 巫师3尖叫怪怎么引出来 《巫师》系列游戏
- 下一篇: maven手动添加第三方的jar包