我一直在MySQL上工作了一个季度,但是这个SQL问题真的让我崩溃了。今天有朋友在群里发了一道SQL题。我一头雾水,久久没有头绪。把整个Q2的MySQL都敲了一遍,各种索引优化,MVCC,锁,B+树都看了。这时,我只有“花里胡哨,脑子里没有B树?”题目:有一个表,有b个字段,包括:userid,year,month,请查询2020年每个月记录的userid?并且不讨论有没有什么场景会用到这样的表,以及它的合理性。请用SQL来实现上面的问题。当我看到它时,我首先想到的是使用groupbyselectuser_idfrombwhereyear=2020groupbyuser_idhavingcount(month)=12;我没有建表测试,我也不确定。不行,按照平时写业务代码的思路,每个月获取用户id,然后走路口,发现不行[手动捂脸]然后一步一步走,如果你知道执行一条SQL的每一步的过程是这样的,那么写SQL会有难度吗?之前整理过一篇关于SQL执行原理的文章。有兴趣可以阅读:SQL查询执行顺序详解创建测试表,结构如下:CREATETABLE`b`(`user_id`int(11)NOTNULL,`month`int(10)DEFAULTNULL,`year`int(10)DEFAULTNULL)ENGINE=InnoDBDEFAULTCHARSET=latin1;然后创建一些测试数据:insertintobvalues(1,1,2020),(1,2,2020),(1,3,2020),(1,4,2020),(1,5,2020),(1,6,2020),(1,7,2020),(1,8,2020),(1,9,2020),(1,10,2020),(1,11,2020),(1,12,2020),(1,1,2020);插入b值(2,1,2020),(2,2,2020),(2,3,2020),(2,4,2020),(2,5,2020),(2,6,2020),(2,7,2020),(2,8,2020),(2,9,2020),(2,10,2020),(2,11,2020),(2,12,2020);插入b值(6,1,2020),(6,2,2020),(6,3,2020),(6,4,2020),(6,5,2020),(6,6,2020),(6,7,2020),(6,8,2020),(6,9,2020),(6,10,2020),(6,11,2020),(6,12,2020);插入b值(10,1,2020),(10,2,2020),(10,3,2020),(10,4,2020),(10,5,2020),(10,6,2020),(10,7,2020),(10,8,2020),(10,9,2020),(10,10,2020),(10,11,2020),(10,12,2020);插入b值(25,1,2020),(25,2,2020),(25,3,2020),(25,4,2020),(25,5,2020),(25,6,2020),(25,7,2020),(25,8,2020),(25,9,2020),(25,10,2020),(25,11,2020),(25,12,2020);插入b值(66,1,2020),(66,2,2020),(66,6,2020),(66,4,2020),(66,5,2020),(66,6,2020),(66,7,2020),(66,8,2020),(66,9,2020),(66,10,2020),(66,11,2020),(66,12,2020);插入b值(7,1,2020),(7,2,2020),(7,4,2020),(7,5,2020),(7,7,2020),(7,9,2020),(7,10,2020),(7,11,2020);插入b值(12,1,2020),(12,4,2020),(12,5,2020),(12,7,2020),(12,9,2020),(12,10,2020),(12,11,2020);插入b值(12,1,2019),(12,4,2019),(12,5,2019),(12,7,2019),(12,9,2019),(12,10,2019),(12,11,2019);第一步是从简单的开始。我会查到年份是2020年,但是因为可能有重复数据,所以我会重新选择DISTINCTmonth,user_idfrombwhereyear=2020;说明:我在这里发现了一个坑。如果用DISTINCT,一定要放在最前面?如果我把user_id放在前面,就会报错selectuser_id,DISTINCTmonthfrombwherey耳朵=2020;聪明的你一定知道这条SQL的执行结果是什么样的,因为数据很多,截取的部分如下:假设查询结果叫表1,看到这个结果的时候我在想,那我就按照User_id分组,然后统计个数还不够?由于2020年每个月都有记录,如果一个用户在表1中有12条记录,这个用户就是我要找的用户selectuser_idfromtable1ascgroupbyc.user_idhavingcount(month)=12;OK,然后它基本上就在这里出来了,表1是一个子查询selectuser_idfrom(selectDISTINCTmonth,user_idfrombwhereyear=2020)ascgroupbyc.user_idhavingcount(month)=12;execute结果一开始想到的SQL明显是错误的,没有考虑重复。这里有一个小知识,为什么不能用selectfromtablegroupbyid,为什么一定不能用,而是某列或者某列的聚合函数,这个可能需要了解groupby的执行原理。假设有一张test表,记录如下:记录为table1,执行如下SQL,从testGROUPBYname中获取table2SELECTname;如果熟悉SQL的执行顺序,应该知道上面的SQL会先查询test表中的所有记录,然后执行groupby操作(之前整理过一篇SQL执行原理的文章,有兴趣可以阅读:SQL查询执行顺序详解)那么,MySQL在执行组的时候做了什么?可以想象,当MySQL执行groupby时,会生成一个如下图的虚表。右边是groupbyname执行后生成的虚表。如果我们此时不选择name,而是选择*,显然会报错,id和number中有些单元格包含多个值,而关系型数据库是基于关系的。单元格不允许有多个值。查看名称栏。每个cell只有一个数据,所以我们选择name,那么就没有问题了为什么名称列每个单元格只有一个值?因为我们是用name列来分组的,如果id和number里面的单元格有多个数据怎么办?答案是使用聚合函数,它用于输入多个数据并输出一个数据。比如cout(id)、sum(number),每个聚合函数的输入是每个多数据cell。比如我们执行selectname,sum(number)fromtestgroupbyname,那么sum就是虚拟表的编号对列的每个单元格进行求和运算,例如对name为aa的行的number列,即2+3,返回5,最终执行结果如下:groupby执行原理参考:https://blog.csdn.net/qq40358...一个技术人的核心竞争力就是在瞬息万变的技术中找到相同点。知行合一,理论与实践相结合
