MySQL不同的GROUP BY

mysql 5.7以後,對group by的處理有所區別,這裏基於一個demo作一些探究html

官方文檔:mysql

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

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

 

需求

統計用戶登陸次數,顯示用戶Id和姓名,以下:oracle

基於上面的需求,假設目前只有一張'用戶登陸日誌表'可用:函數

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 談起:this

-- SELECT 後面 包含了GROUP BY 後面沒有的一個列 user_name
SELECT user_id,user_name,count(*) FROM user_login GROUP BY user_id;

上面的sql,給人的第一感就是語法會報錯,尤爲對於部分從oracle過渡到mysql的人。spa

真實狀況是什麼樣呢?日誌

默認設置下,在mysql 5.7 以前,上面sql的語法是正確的,以後的版本纔會報錯,以下:

 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,就ok,關於什麼是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模式,經過命令能夠查看:

-- 查看全局的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纔會報錯,關閉便可解決問題。

把上面查詢出的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');

顯然用戶name列 functionally dependent on 用戶id列,因此下面的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還爲咱們提供了另外一種解決方案,即經過函數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後面,除非多出的非聚合列和group by後面的列有函數依賴關係。

相關文章
相關標籤/搜索