MySQL不一样的GROUP BY


声明:本文转载自https://my.oschina.net/wangxu3655/blog/1538778,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

mysql版本的变化对group by的处理也有所区别,这里基于一个demo做一些探究

官方文档:

https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_only_full_group_by

 

需求

统计用户登录次数,显示用户Id和姓名,如下:

基于上面的需求,假设目前只有一张'用户登录日志表'可用:

DROP TABLE IF EXISTS user_login;  CREATE TABLE `user_login` ( 	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '自增主键', 	`user_id` INT (11) DEFAULT NULL COMMENT '用户Id', 	`user_name` VARCHAR (100) DEFAULT NULL COMMENT '用户姓名(冗余:可变性不大,业务要求不严)', 	`login_time` datetime DEFAULT NULL COMMENT '登录时间', 	PRIMARY KEY (`id`) ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT = '用户登录日志表';  INSERT INTO `user_login` ( `user_id`, `user_name`, `login_time`) VALUES ( '1', '小A', '2017-09-13 22:06:49'); INSERT INTO `user_login` ( `user_id`, `user_name`, `login_time`) VALUES ( '1', '小A', '2017-09-14 12:06:51'); INSERT INTO `user_login` ( `user_id`, `user_name`, `login_time`) VALUES ( '2', '小B', '2017-09-14 16:16:54');

直观数据如下:

 

问题

先从下面这条有问题的sql谈起:

-- SELECT 后面 包含了GROUP BY 后面没有的一个列 user_name SELECT user_id,user_name,count(*) FROM user_login GROUP BY user_id; 

第一感觉就是上面的sql会报错,尤其是部分从oracle过渡到mysql的人。

其实不一定,默认设置下,mysql 5.7之前上面的写法是正确的,但之后的版本会报错:

 SELECT list is not in GROUP BY clause and contains nonaggregated column 'user_login.user_name' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

-- SELECT 后面包含了非聚合的列user_name,而user_name和GROUP BY 后面的列没有函数依赖关系;这个违背了only_full_group_by的sql模式

当然我们在GROUP BY后面带上user_name,肯定ok,就不说了,这里我们来探究一下mysql 5.7 之后给出了哪些解决方法。从报错提示中似乎可以得知,如果user_name和user_id之间functionally dependent,就可以解决问题,关于什么是functionally dependent,后面再说,这里先说一下mysql的only_full_group_by 。

 

only_full_group_by

Reject queries for which the select list, HAVING condition, or ORDER BY list refer to nonaggregated columns that are neither named in the GROUP BY clause nor are functionally dependent on (uniquely determined by) GROUP BY columns.

-- select、HAVING、ORDER BY 后面不能出现‘非聚合的列’(GROUP BY后面没有的列),除非这些非聚合列和GROUP BY后面的列之间‘函数依赖’(唯一绑定) .

mysql 5.7之后默认开启了only_full_group_by模式,通过命令可以查看:

-- 查看全局的sql_mode SELECT @@GLOBAL .sql_mode; -- 查看当前会话的sql_mode SELECT @@SESSION.sql_mode; -- 结果如下,第一个就是ONLY_FULL_GROUP_BY ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

正是因为only_full_group_by的约束,sql才会报错,关闭only_full_group_by模式即可解决问题。

把上面查询出的sql_mode的值,去掉ONLY_FULL_GROUP_BY一项,然后重新设置一下:

2种方式:

SET SESSION sql_mode = 'modes';-- 仅对当前会话有效

SET GLOBAL sql_mode = 'modes'; -- 全局有效

-- 设置当前session的sql_mode SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; -- 设置之后,在当前session下,下面sql不会报错: SELECT user_id,user_name,count(*) FROM user_login GROUP BY user_id; 

此外还可以通过配置文件来修改sql_mode,这里不探究 ,接下来简单说一下上面提到函数依赖。

 

functionally dependent

functionally dependent(函数依赖) 等同于 uniquely determined(唯一决定) ,一个字段完全依赖于另一个字段,即其值由另一个字段来决定。

比如:一个表的非主键列 uniquely determined by 主键列,我们可以称它们函数依赖。

基于上面的测试场景,其实我们还有一张‘用户表‘:

CREATE TABLE `user` ( 	`id` INT (11) NOT NULL AUTO_INCREMENT, 	`name` VARCHAR (64) DEFAULT NULL COMMENT '姓名(和主键id函数依赖)', 	PRIMARY KEY (`id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT '用户表';  INSERT INTO `user` VALUES (1, '小A'); INSERT INTO `user` VALUES (2, '小B'); 

因为functionally dependent,所以下面的sql语法任何时候都是正确的:

SELECT 	ul.user_id, 	u.name, -- 这一列虽然不是聚合列,但是它和主键u.id是函数依赖的,所以整个sql都ok 	count(*) FROM 	user_login ul INNER JOIN user u ON ul.user_id = u.id GROUP BY 	ul.user_id;

.

any_value

如果多出的非聚合列没有functionally dependent特性,而且我们也不想修改sql_mode的话,  mysql还为我们提供了另一种解决方案,即通过mysql自身函数any_value,来绕过only_full_group_by模式,如下:

-- any_value会随便选取一个user_name值,通常是第一个 SELECT user_id,any_value(user_name),count(*) FROM user_login GROUP BY user_id;

.

规范

有人会说,费那么大劲干啥,在最后面补上user_name不就ok了么,而且这样写也是理所当然:

-- 符合SQL99规范的group by SELECT user_id,user_name,count(*) FROM user_login GROUP BY user_id,user_name;

确实如此,在实际工作中,除非升级mysql之后,发现一些历史遗留的sql报错时,我们才有必要考虑如何去解决这些问题;正常情况下,我们只要按照only_full_group_by的规范来开发就行,即select、having、order by这些关键字后面的列必须同时出现在group by后面,除非有函数依赖关系的。

PS:欢迎交流指正

本文发表于2017年09月18日 08:37
(c)注:本文转载自https://my.oschina.net/wangxu3655/blog/1538778,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1971 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1