需求:測試庫t_user_download_log(如下)沒有做user_id,appVersion唯一索引,但正式庫有论巍,需要在測試庫里面找出所有userId,appVersion重復(fù)的記錄,相同的記錄顯示時間最早的窜醉,并將結(jié)果輸出到另外一張表中拌汇。
/*Table structure for table `t_user_download_log` */
DROP TABLE IF EXISTS `t_user_download_log`;
CREATE TABLE `t_user_download_log` (
`uuid` varchar(32) NOT NULL,
`user_id` int(11) NOT NULL COMMENT '用戶ID',
`appVersion` varchar(50) NOT NULL COMMENT 'appVersion',
`status_` tinyint(4) NOT NULL DEFAULT '0' COMMENT '標(biāo)簽狀態(tài)',
`create_time` datetime NOT NULL COMMENT '插入時間',
PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
由于測試數(shù)據(jù)比較多,這里便于說明谱净,新建一個和t_user_download_log結(jié)構(gòu)一樣的表t_user_test,插入一些數(shù)據(jù)做測試:
Insert into `t_user_test`(`uuid`,`user_id`,`appVersion`,`status_`,`create_time`) values ('10',1,'5.1.5',0,'2017-05-11 10:42:52'),('11',1,'5.1.5',0,'2017-05-11 11:42:52'),('12',2,'5.1.2',0,'2017-05-11 10:42:52'),('13',1,'5.1.5',0,'2017-05-10 9:42:52'),('14',3,'5.1.5',0,'2017-05-8 9:42:52')
執(zhí)行select * from t_user_test:
這時候我想要的查詢結(jié)果是:
方式1:使用子查詢的方式(最容易想到的應(yīng)該就是這種方式)窑邦,實現(xiàn)方式如下:
select *
from (
select *
from t_user_test
order by create_time
) as a
where uuid =(
select uuid
from t_user_test as b
where a.user_id=b.user_id and a.appVersion=b.appVersion
order by create_time limit 1
)order by uuid
(使用2次子查詢)
select *
from t_user_download_log
where UUID in (
select _uuid
from (
select user_id _id,
appVersion _app,
(
select UUID
from t_user_download_log l3
where l3.appVersion=_app and l3.user_id=_id
order by create_time limit 1
) _uuid
from t_user_download_log l1
) t1
) order by user_id , appVersion;
(使用3次子查詢)
方法中使用了兩次子查詢,生成了兩張臨時表a和b壕探。
這種方式最大的缺點是:查詢速度慢冈钦,由于臨時表的產(chǎn)生會導(dǎo)致數(shù)據(jù)庫在查詢的時候多出了額外的時間開銷和空間開銷,每次查詢會多出一個數(shù)據(jù)fetch的過程李请,當(dāng)數(shù)據(jù)量足夠大的時瞧筛,會耗費相當(dāng)長的時間在數(shù)據(jù)fetch的過程中厉熟,因此,sql查詢中應(yīng)當(dāng)盡量少使用子查詢的方式较幌,即使要使用也應(yīng)該盡量少的使用子查詢揍瑟,多使用聚合函數(shù)的形式代替子查詢。
使用2次子查詢和使用3次子查詢的性能對比:
方式2:使用聚合函數(shù)代替子查詢(這種方式的關(guān)鍵是找到能夠代替子查詢的聚合函數(shù))乍炉,實現(xiàn)方式如下:
select ?substring_index(group_concat(uuid order by create_time),',',1) as uuid,
user_id,
appVersion,
substring_index(group_concat(status_),',',1) as status_,
substring_index(group_concat(create_time order by create_time),',',1) as create_time
from t_user_test
group by user_id,appVersion
order by uuid
這里從查詢性能方面對兩次方式查詢結(jié)果做一個對比(由于在數(shù)據(jù)量比較大的時候的對比結(jié)果比較明顯月培,所以這里使用的是t_user_download_log表,數(shù)據(jù)量:5742條數(shù)據(jù)):
方式2中涉及的聚合函數(shù)介紹:
1.group_concat
group_concat:將指定組的字段拼接到一塊恩急,并且可以設(shè)定拼接的順序(order by...)
語法結(jié)構(gòu):GROUP_CONCAT([DISTINCT] expr [,expr ...] [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name ...]] [SEPARATOR str_val])
其中杉畜,separator表示按照指定的str_val分隔符分割。
注意:
mysql中默認的group_concat長度為1024個字節(jié)
查詢時不受影響衷恭,但是如果將查詢結(jié)果導(dǎo)出到另外的一張表中就會提示
類似“Error Code: 1260. Row 631 was cut by GROUP_CONCAT()0.015 sec”的錯誤
解決方案:設(shè)置group_concat長度:
在mysql中執(zhí)行SET GLOBAL group_concat_max_len=102400;需要重啟mysql
group_concat與group的區(qū)別:
group_concat相當(dāng)于是將某一個字段的所有值拼接起來此叠,顯示的是拼接后的結(jié)果
而group是按字段分組,顯示的結(jié)果還是原來的數(shù)據(jù)随珠,只不過以分組的形式顯示灭袁。
例如:以上面的t_user_test這張表做例子(總共存在5條數(shù)據(jù),即5個user_id)
select user_id,count(*) as 'user_id出現(xiàn)個數(shù)' from t_user_test group by user_id
顯示結(jié)果如下:
select group_concat(user_id) ,count(*) as 'user_id出現(xiàn)個數(shù)'from t_user_test
顯示結(jié)果如下:
2.substring_index:字符串截取函數(shù)窗看,通過指定分隔符分割后按照index截取對應(yīng)字段茸歧。
例如上面的substring_index(group_concat(a.create_time order by create_time),',',1)
表示使用逗號分割group_concat(a.create_time order by create_time)結(jié)果,并且使用第1個索引處的字段显沈。
補充:mysql不嚴格的語義限制
在語義限制的嚴格sql語句中软瞎,顯示結(jié)果的列一定要出現(xiàn)在 聚合函數(shù)或GROUP BY子句中
例如:
select ID,title from xinwen group by ID
這樣的寫法就會出錯,因為title這個結(jié)果列并沒有出現(xiàn)在group by分組中也沒有使用聚合函數(shù)的形式拉讯。
正確寫法:
select ID,title from xinwen group by ID涤浇,title
或者
select ID,count(title) from xinwen group by ID
但是在mysql中,由于存在著不嚴格的語義限制(mysql早期的非ONLY_FULL_GROUP_BY語義魔慷,了解可以以這個為關(guān)鍵詞搜索查看相關(guān)文章)只锭,導(dǎo)致會出現(xiàn)類似select ID,title from xinwen group by ID語句也可以得到正確執(zhí)行。