共计 3234 个字符,预计需要花费 9 分钟才能阅读完成。
磕了一个季度的 MySQL,竟然被这道 SQL 题给搞崩了
明天敌人在群里发了一个 SQL 题,我蒙圈了,半天没思路。我磕了整个 Q2 的 MySQL,看各种索引优化、MVCC、锁、B+ 树,此时心里就只有”花里胡哨,心里没点 B 树?“
题目:
有一张表 b
字段包含:用户 id,年,月,请查问在 2020 年每个月都有记录的用户 id?
且不探讨有没有什么场景会用到这样的一张表以及其合理性。请用 SQL 实现上边的问题
我看到的时候,第一个想到的就是用group by
select user_id from b where year=2020 group by user_id having count(month)=12;
没有建表测试,本人也不确定对不对
依照平时写业务代码的思维,获取每个月都有哪些用户 id,而后取交加,发现走不通啊【手动捂脸】
那就一步一步来,如果晓得一个 SQL 执行每一步的过程是什么样的,那还会有难写的 SQL?
之前整顿过一篇 SQL 执行原理的文章,有趣味的能够看一下:SQL 查问执行程序详解
创立一个测试表,构造如下:
CREATE TABLE `b` (`user_id` int(11) NOT NULL,
`month` int(10) DEFAULT NULL,
`year` int(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
而后造一些测试数据:
insert into b values(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);
insert into b values(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);
insert into b values(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);
insert into b values(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);
insert into b values(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);
insert into b values(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);
insert into b values(7,1,2020),(7,2,2020),(7,4,2020),(7,5,2020),(7,7,2020),(7,9,2020),(7,10,2020),(7,11,2020);
insert into b values(12,1,2020),(12,4,2020),(12,5,2020),(12,7,2020),(12,9,2020),(12,10,2020),(12,11,2020);
insert into b values(12,1,2019),(12,4,2019),(12,5,2019),(12,7,2019),(12,9,2019),(12,10,2019),(12,11,2019);
第一步,先从简略的开始,我就先查进去 year 为 2020 的,然而,因为可能会有反复数据,所以我顺便去重
select DISTINCT month,user_id from b where year=2020;
阐明:这里发现个坑啊,如果应用 DISTINCT,必须得放最前边?我如果把 user_id 放前边会报错
select user_id,DISTINCT month from b where year=2020;
这个 SQL 的执行后果,聪慧的你肯定晓得是什么样的,因为数据较多,截取局部如下:
咱们假如将这个查问后果叫 表 1
看到这个后果我就在想,那我依照 user_id 进行分组,而后统计个数不就行了?既然是 2020 年的每一个月都有记录,那么,如果一个用户在表 1 中有 12 条记录,这个用户就是我要找的
select user_id from 表 1 as c group by c.user_id having count(month)=12;
OK,那到这里根本就进去了,表 1 是个子查问
select user_id from (select DISTINCT month,user_id from b where year=2020) as c group by c.user_id having count(month)=12;
执行后果
我最开始想到的那个 SQL 显然是不对的,没有思考反复的状况
这里插一个小常识
为什么不能用 select from table group by id,为什么肯定不能是,而是某一个列或者某个列的聚合函数
这个可能就须要理解 group by 的执行原理
假如有一个 test 表,记录如下:
记为table1
执行以下 SQL 失去 table2
SELECT name FROM test GROUP BY name;
如果对 SQL 的执行程序比拟相熟的话,就应该晓得,上边这个 SQL 会先查问出 test 表中的所有记录之后,而后执行的 group by 操作(之前整顿过一篇 SQL 的执行原理文章,感兴趣的能够看看:SQL 查问执行程序详解)
那么,MySQL 在执行 group by 的时候做了什么?
能够设想 MySQL 在执行 group by 的时候,生成了下边这样的一个虚构表
左边为执行 group by name 之后生成的虚构表,如果此时咱们不是 select name,而是 select *
显然是会报错的,id 和 number 中有的单元格外面的内容是多个值的,而关系数据库就是基于关系的,单元格中是不容许有多个值的
- 再看 name 列,每个单元格只有一个数据,所以咱们 select name 的话,就没有问题了。为什么 name 列每个单元格只有一个值呢?因为咱们就是用 name 列来 group by 的
- 那么对于 id 和 number 外面的单元格有多个数据的状况怎么办呢?答案就是用 聚合函数,聚合函数就用来输出多个数据,输入一个数据的。如 cout(id),sum(number),而每个聚合函数的输出就是每一个多数据的单元格
- 例如咱们执行 select name,sum(number) from test group by name,那么 sum 就对虚构表的 number 列的每个单元格进行 sum 操作,例如对 name 为 aa 的那一行的 number 列执行 sum 操作,即 2 +3,返回 5,最初执行后果如下:
group by 执行原理参考:
https://blog.csdn.net/qq40358…
在疾速变动的技术中寻找不变,才是一个技术人的外围竞争力。知行合一,实践联合实际