需求:测试库t_user_download_log(如下)没有做user_id,appVersion唯一索引,但正式库有,需要在测试库里面找出所有userId,appVersion重复的记录,相同的记录显示时间最早的,并将结果输出到另外一张表中。
/*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 '标签状态',
`create_time` datetime NOT NULL COMMENT '插入时间',
PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
由于测试数据比较多,这里便于说明,新建一个和t_user_download_log结构一样的表t_user_test,插入一些数据做测试:
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')
执行select * from t_user_test:
这时候我想要的查询结果是:
方式1:使用子查询的方式(最容易想到的应该就是这种方式),实现方式如下:
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。
这种方式最大的缺点是:查询速度慢,由于临时表的产生会导致数据库在查询的时候多出了额外的时间开销和空间开销,每次查询会多出一个数据fetch的过程,当数据量足够大的时,会耗费相当长的时间在数据fetch的过程中,因此,sql查询中应当尽量少使用子查询的方式,即使要使用也应该尽量少的使用子查询,多使用聚合函数的形式代替子查询。
使用2次子查询和使用3次子查询的性能对比:
方式2:使用聚合函数代替子查询(这种方式的关键是找到能够代替子查询的聚合函数),实现方式如下:
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
这里从查询性能方面对两次方式查询结果做一个对比(由于在数据量比较大的时候的对比结果比较明显,所以这里使用的是t_user_download_log表,数据量:5742条数据):
方式2中涉及的聚合函数介绍:
1.group_concat
group_concat:将指定组的字段拼接到一块,并且可以设定拼接的顺序(order by...)
语法结构: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个字节
查询时不受影响,但是如果将查询结果导出到另外的一张表中就会提示
类似“Error Code: 1260. Row 631 was cut by GROUP_CONCAT()0.015 sec”的错误
解决方案:设置group_concat长度:
在mysql中执行SET GLOBAL group_concat_max_len=102400;需要重启mysql
group_concat与group的区别:
group_concat相当于是将某一个字段的所有值拼接起来,显示的是拼接后的结果
而group是按字段分组,显示的结果还是原来的数据,只不过以分组的形式显示。
例如:以上面的t_user_test这张表做例子(总共存在5条数据,即5个user_id)
select user_id,count(*) as 'user_id出现个数' from t_user_test group by user_id
显示结果如下:
select group_concat(user_id) ,count(*) as 'user_id出现个数'from t_user_test
显示结果如下:
2.substring_index:字符串截取函数,通过指定分隔符分割后按照index截取对应字段。
例如上面的substring_index(group_concat(a.create_time order by create_time),',',1)
表示使用逗号分割group_concat(a.create_time order by create_time)结果,并且使用第1个索引处的字段。
补充:mysql不严格的语义限制
在语义限制的严格sql语句中,显示结果的列一定要出现在 聚合函数或GROUP BY子句中
例如:
select ID,title from xinwen group by ID
这样的写法就会出错,因为title这个结果列并没有出现在group by分组中也没有使用聚合函数的形式。
正确写法:
select ID,title from xinwen group by ID,title
或者
select ID,count(title) from xinwen group by ID
但是在mysql中,由于存在着不严格的语义限制(mysql早期的非ONLY_FULL_GROUP_BY语义,了解可以以这个为关键词搜索查看相关文章),导致会出现类似select ID,title from xinwen group by ID语句也可以得到正确执行。