《张三求职日记》基础篇--(7)开发两年,sql都写不对?

398 阅读8分钟

写在前面:作为Java开发程序员,应该大部分都是做应用层面的开发,所以sql基本是我们必会的技能,甚至有不少程序员可能写业务全部依赖于sql,sql写完了功能基本就做完了(千万别这样)。然而,就算我们可能写sql经验丰富,但是一到面试,如果面试官会出手写sql的题目时,很有可能我们根本写不对,别不信,下面我们就来碰碰这些题目。有关于张三的背景介绍参考:张三背景介绍

张三又开始了新的一轮面试。

面试官:先来个自我介绍吧。

张三:!@#¥%……&*。

前面的一阵交谈此处省略

面试官:平时sql写的多吗?

张三心想,平时虽然蛮多sql是通过Mybatis Generator自动生成的,但是自己写的sql肯定也是不少的,老项目中更是要经常改超多行的sql,当然写的算多的呀。

张三:平时写的sql还是不少的。

面试官:那好,我给你出一道题,你看一下sql怎么写:

张三粗略一看题目,心想这不应该很简单吗,应该就只是分组求最小吧

张三:稍等我几分钟,我想想就能写出来。

张三开始分析题目了,首先,我们有一张students表,然后在脑海里脑补一下这张表,有这么些字段,其中对我们有用的字段就是班级、年龄以及学号。我们要找到每个班级内年龄最小的的同学的学号,那不就是将学生表按班级分组,使用min函数求最小,然后得到学号吗?说干就干,先写出来。

张三:我想应该就是这样吧。

SELECT student_no,MIN(age)
FROM students
GROUP BY class

面试官:你再看看。

张三:(过了几分钟)看不出问题……

面试官:那我们再来聊聊……

后面的省略

面试官:这次的面试就先到这里,后面会有我们的HR联系你。

张三:好的……

-----------------------------------------------------------------------------------
在这次面试中,面试官很意外的放出了一道手写sql题目,从张三的表现上来看,分组语句,最小值函数肯定都是了解的,基本可以看出以前张三是干过活的,但是结果却不对,张三也看不出问题,说明张三对sql还是不够清楚,最起码对分组语句的正确用法都还没有吃明白。如果屏幕前的你也看不出问题所在,那么也一定要好好看完这篇文章。其实也不一定会怪张三,毕竟不能调试,仅仅靠经验来写不一定能一次写对,并且其实这样的业务需求通过sql来写很有可能并不如通过代码来写靠谱。但是题目是这样出的,答不出来就是会留下一个不好的印象,我们要找到原因。

既然张三当时的处境没法调试,但是我们现在可以调试啊,我们就来分析问题,找到原因,把知识点弄明白,写出正确的sql。

先来把题目中的表建好,预置几条测试数据:

那我们把张三写的sql语句运行一下:

看起来结果并没有达到我们的预期呢,要找到每个班级内年龄最小的的同学的学号,理应为1班同学学号127、年龄16,2班同学学号123、年龄18,3班同学学号128、年龄18,但是这个一班的同学年龄找对了,但是学号却错了,这是为什么呢?

分析一下我们的sql语句,select...from的语句不可能出问题,min函数我们查了一下也没问题,那么出问题只可能出在group by关键字上面。张三只记得这个关键字是用于分组的,那么它到底有哪些用法呢?我们只有把它的用法彻底搞清楚,才能写出正确的sql语句。

首先我们来写一条最简单的正确的sql语句来开始这次的讲解:

这条语句的意思是,我们根据class这个字段分组,查询class字段。但凡工作过的人,看到这条sql语句,都应该能脑中想到查询出的结果了,没错,就是1,2,3,我们的数据根据班级这个字段成功分为了三组。

那我们再来写一条不规范但是能查询出结果的sql语句:

注意啊,我们的sql语句并不规范,首先,标准的group by语法为

SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name

简单来说就是我们查询的字段或者聚合函数处理的字段都需要为我们的分组的字段(也就是group by后面的字段)。这条语句虽然能够成功执行,但是逻辑是讲不通的,group by的本意是通过分组过滤掉重复的字段,那么我们根据class分组了,那么过滤掉重复的字段后查询出三条结果是能够说得通的。但是问题也随之而来了,我们根据class分组,却把每一列的数据都查出来了,这合理吗?显然是不合理的,所以我们最终的到的结果其实为每一组的第一条数据。而我们(select * )既然得到的是每一组的第一条数据,所以我们查到的student_no字段也就得不到正确的结果了。

现在我们清楚了为什么查询到的student_no不为我们想要的结果。而另一个查询到的结果MIN(age)却是对的,那是因为这确实是group by语句的聚合函数的标准用法啊,既然提到了聚合函数,我们不妨把这些常用函数列出来,方便以后用到时不再陌生:

  1. 求个数/记录数/项目数等:COUNT()
  2. 求某一列平均数:AVG()
  3. 求总和,总分等:SUM()
  4. 求最大值,最高分,最高工资等:MAX()
  5. 求最小值,最低分,最低工资等:MIN()

现在我们知道了最开始的查询语句为什么查到的学号不对,而查到的最小值是对的,但是我们光发现问题还不够,还要能解决问题呀,那么正确的SQL语句该怎么写呢?

其实写法能有很多中,我在这里给大家提供一种思路:
首先,根据上面的知识,我们知道我们根据class字段分组,我们是能够取到每个class分组后对应的最小的age字段值的,那么我们一步一步来,先取到按班级分组后的最小年龄:

select MIN(age) minage,class From students group by class

我们取到了最小的年龄,然后呢?这时,可能还有一些人想不到办法,而有经验的程序员只需要受到一点点拨,马上就能想到后面的方法,没错,就是连表查询,我们只需要通过得到的最小的年龄,再次关联students表,就能够获得正确的student_no了。粗略一想好像很好写,那么我们说做就做,把SQL写出来:

SELECT a.student_no
FROM students a,(SELECT MIN(age) minage,class FROM students GROUP BY class) b
WHERE a.class = b.class
AND a.age = b.minage

我们看看运行的结果:

果然,这次我们得到了我们想要的结果。那么我们再来补充一个知识点吧,在这里我们的sql查询两张表是用逗号隔开的,大家应该都知道这样写的效果是和使用inner join是一样的,感兴趣的同学可以把这段sql改写为inner join,那么问题来了,到我们采用内连接时,如果不加上后面的限定语句(这里是where,用inner join的话是on),查询到的数据和两张表的关系是怎样的?

可能有些专业不是计算机相关的同学就说不出这个词了,一般如果这样问了,就是想从你说的话中听到一个词:笛卡尔积。至于什么是笛卡尔积,可能真的也不是几句话能够说清楚的,在这里我们还是主要讲了group by在使用上可能遇到的坑,笛卡尔积的概念还是大家后面去自行了解,概念并不难,只需要心中明白有这个知识点就行。

现在我们真正搞清楚了group by的用法,以后再遇到相似的面试题目,或者是工作中碰到了要用到它,应该也不会写不出来了吧,至少再次发现了坑的话,能够立马看明白为什么错了。

张三在这次的面试中收获颇丰,我们下周见。