关于作者:

         我是星河,一个深耕自己,不内耗的长期主义者。一个对技术充满激情,对工作对生活充满热情的热血青年。坚信,真正能让大家看懂的技术文章才是好文章,坚持用更通俗易懂的大白话写技术博文,并会持续更新。我是星河,愿我们既能凝望脚下的路,也能心怀璀璨的远方。

很多小伙伴使用SQL中的GROUP BY时,估计都是迷迷糊糊的。

都知道GROUP BY是分组,但是为什么使用了GROUP BY分组之后,select分组的字段就可以直接select,但是select其他字段就必须使用一个聚和函数?为什么其他字段不使用聚和函数就报错呢?

是不是很多小伙伴都有这个疑惑?

和之前一样,今天还是用通俗易懂的大白话来写点我自己的理解和总结。

跟着我的思路,我绝对给你搞搞的明明白白的!

首先,别感觉很难,看我写的一点都不难。经常看我文章的应该都能感觉到,我写的文章和你看的其他讲技术的文章可能不一样,我写的全都是大白话来写的,基本上没什么生硬的专业术语,而且都是循序渐进来写的。我会把我是怎么从不理解不明白,到最后是怎么搞明白的这个脉络,我会尽可能的用通俗易懂的大白话记录下来。我相信我自己是这么理解过来的,那大多数人按照我的脉络来看,肯定也都能看明白。希望能尽自己的一点绵薄之力,来帮更多人理解这些枯燥难懂的Java知识。

ok,直接开始今天的内容。

假如现在有个表1,表名为test,内容如下图

 表1

那,执行如下的SQL语句:

SELECT name FROM test GROUP BY name;

你应该很容易知道运行的结果,没错,就是下表2:

表2

但,为了能更好的理解“group by”和“聚合函数”,我在思考的时候,由表1到表2的过程中,增加了一个虚构的中间表:虚拟表3。如下图

有了这个虚拟表3之后呢,咱们再来重新思考 上面SQL语句执行情况:

1、假如我只执行:SELECT name FROM test;

        那结果就和上边的表1一样,就是原来的表

2、接着加上了  Group BY name; 

        加上了 Group BY name后,我们想象生成了上图的虚拟表3。

        想象一下生成过程是这样的:加上了 Group BY name后,那么找name那一列具有相同name值的行,合并成一行。比如上图中 对于name值为aa的,那么<1 aa 2>与<2 aa 3>两行合并成1行,所有的id值和number值写到一个单元格里面,如上图所示我标红的红框和红色箭头。然后name为bb的行和刚才aa的操作一样合并到一个单元格,如上图,我使用绿色的框和绿色箭头。

同样的,剩下的cc、dd、ee的都一样的操作。

这样就得到了右侧的虚拟表3

3、接下来就要针对虚拟表3执行Select语句了:

(1)如果执行select * 的话,那么返回的结果应该是虚拟表3,可是虚拟表3中的id和number中有的单元格里面的内容是有多个值的,而关系数据库就是基于关系的,每个单元格(列)中是不允许有多个值的。所以你看,你执行select *  语句就报错了。

(2)我们再来执行 select name ,就不会报错。因为 name列的每个单元格只有一个数据,所以我们select name的话,就没有问题。为什么name列的每个单元格只有一个值呢?因为我们就是用name列来group by分组的。

(3)那么对于 id和number 列里面的单元格有多个数据的情况怎么办呢???

        答案就是使用 聚合函数。聚合函数的作用就是用来输入多个数据,最后给你返回输出一个数据的。而每个聚合函数的输入就是每一个多数据的单元格里的多个数据。

        比如sum(number),它是把number这一列中 一个单元格里的多个数据相加的和返回;再比如count(id),它是把id这一列中一个单元格里的多个数据的个数给你统计一下给你返回。

(4)例如我们执行select name,sum(number) from test group by name,那么sum就对虚拟表3的number列的每个单元格里的多个值 进行sum相加操作。比如对name为aa的那一行的number列执行sum操作,即2+3,返回5;其他的行 以此类推,最后执行结果如下:

到这能理解吧?

(5)接着,group by 多个字段该怎么理解呢:比如group by name,number

        我们可以把name和number 看成一个整体字段,以他们整体来进行分组。如下图

(6)接下来就可以配合select和聚合函数进行操作了。如执行select name,sum(id) from test group by name,number,结果如下图:

最后总结一下:你使用了group by后,那你select的列就只能是group by的列或者对其他列进行聚合运算。

注意:新版的mysql 在group by的时候,如果你查询的字段不在分组字段里,而且你还不使用聚合函数的时候会报only_full_group_by的错

其实正规的来说,就应该给你报错,你group by的时候,查询的字段就应该是分组的字段或者对其他列进行聚合运算。

按分组字段分完组后,其他不是分组的字段,就可能出现多个值,那怎么办,那就需要用聚合函数,聚合函数的作用就是,输入多个数据,给你产出一个数据

但是有些人的sql写的不规范,他就非得想查询不是分组的字段,而且不想聚合,那怎么办?

可以通过修改mysql的sql_mode来不让它报错,虽然不报错了, 但按照分组字段查询完,非分组的字段肯定会有多个值,你又不用聚合函数,那总得取一个值吧,那有多个值,多选一,那它选谁呢?mysql它默认是取多个值里的第一个

那问题就出现在这了:虽然你通过改sql-mode让你可以在不符合规矩的情况下使用group by了

但是那些非分组,且不使用聚合函数的字段,它从多个值里取的第一个值给你返回,这个值在大多数情况下并不是你真正想要的值,可以认为这个数据是有一定的随机性的,所以返回的数据可能就会有问题。

虽然可以通过修改sql-mode来不让他报错,但是非常不建议这么改

下面是修改sql_mode的方法:

一、查询一下当前的sql_mode值
SELECT @@GLOBAL.sql_mode;  或者  SELECT @@SESSION.sql_mode;

注:下面是正常没做过修改的sql_mode的值:

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

二、修改sql-mode

1、修改全局sql_mode

通过下面的set 把 第一项ONLY_FULL_GROUP_BY去掉

set @@global.sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

上面的方法虽然也可以更改sql_mode,但是,一旦MySQL重启,就必须重新设置一次,这样非常的麻烦,还有其它一劳永逸的方法吗?

答案就是下面要说的第二种修改方式


2、通过修改mysql的my.cnf来永久的修改sql-mode
linux下的my.cnf一般在 /etc下
vim /etc/my.cnf
输入关键字 sql-mode 找到后,改成自己想要改的值,进行修改保存

然后重启mysql
service mysqld stop
service mysqld start
 

讲完了对group by的理解,下一篇讲一下GROUP_CONCAT的使用:大白话聊一聊mysql分组中的GROUP_CONCAT使用

纯手敲 原创不易,如果这篇文章对你有所启发或帮助,希望可以花费你一秒钟的时间,点亮【赞和推荐】,如果能点【分享】给更多同行的人,那就更好了。你的每一个互动,都是我持续创作的最大动力。感恩遇见,感谢陪伴。

点击下方 微信公众号 ,获取更多Java干货

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐