目录
6.4 查询数学成绩是58或59或98或99的同学及其数学成绩
6.9 查询白某和总成绩 > 200并且语文成绩 < 数学成绩并且英语成绩 > 80的同学
7.3 查询同学的各门成绩,依次按数学降序、英语降序和语文升序的方式显示
7.7 查询同学的总成绩,拿到总成绩大于200,且为前三的同学成绩
3. 将银狼同学的数学成绩变更为60分,语文成绩变更为70分
5. 显示平均工资低于2000的部门和它的平均工资(having的使用)
一、插入
1. 插入基础语法
要插入数据,一般都是使用insert。insert指令的使用方法如上图所示。在上图中,带有[]的语句是可选语句,即可写可不写。
为了方便后面的插入数据演示,首先准备如下一个students表:
下面的插入操作都会在这张表中完成。
2. 单行数据 + 全列插入
首先我们要了解什么叫“全列插入”。简单来讲,全列插入就是对你的表中所有的列都插入数据。例如你的表中有id和name两列,如果你同时向这两列插入数据,就叫做全列插入。单行数据则很好理解,就是插入一行数据。
这就是一条简单的数据插入。
注意,如果我们要执行全列插入,那么在表名后面的要插入的列是可以不写的。如果不写,就默认是全列插入:
当然,如果你不是全列插入,而只是要对表中的某些列插入数据,就不能够省略。
3. 多行数据 + 全列插入
为了方便写,这里就使用全列插入。非全列和全列插入的语法都是一样的,只是全列插入可以省略要插入数据的列,比较方便写。
假设现在与三条数据,我们不想一条条的插入,想一次性插入,就可以用多行数据插入。方法也简单,就是在values后面的插入的数据中用逗号分隔要插入的多份数据:
查看一下表中的数据:
表中的数据也是正常的。
4. 插入,失败则更新
在某些情况下, 我们可能会想要在不修改其他列的数据的情况下,修改某一列的内容。例如上面的学生表中,张三换了一个qq,此时就需要在表中更新这个qq的数据。但是,张三的属性中,有些列被primary key或unique key修饰,是不允许重复的:
这就导致我们如果直接用insert,就会出现键值冲突:
此时就需要添加“on duplicate key update”字段,用于在插入失败时更新数据。使用方法如下图所示:
在update后面的内容就是你要更新的数据:
可以看到,此时张三的其他内容未被修改的情况,qq被更新了。
当然,如果前面的数据可以被插入,后面的duplicate字段等内容就不会生效。
5. 替换
除了上文中的失败则更新的方法外,我们还可以采用替换的方式更新数据。
替换语句如下图所示:
在这里面,主语句是replace into 表名 (要插入的列) values (插入内容);
替换和更新虽然都可以用于更新数据,但是它们之间有一个不同,更新在插入冲突时是直接在原来的行数据的基础上做修改;替换则是在插入冲突时将原来的行数据全部删除然后替换成该语句中要插入的内容。
我们先插入一个表中不存在的数据:
当表中没有冲突的数据时,replace的功能就是和insert一样的。
但如果我们插入一份会发生冲突的数据,例如更新李四的qq:
替换和发生冲突则更新的作用其实可以看成是一样的,只是书写方式和更新方式不一样,但结果都是一样的。所以如果真的遇到类似情况,用哪个指令就看你自己喜欢用哪个了。
二、基础查询
1. 查询基础语法
查询在mysql中是用的最多的指令。查询用的指令就是select,使用方式如下图:
查询指令在mysql中使用的非常多,很多功能都需要依赖查询实现,这也就导致select后面可以带的选项非常多。上图就是select的一个基本使用,带[]的是可选项。当然, 上图中并没有将select可带的选项全部列完,只是列出了一部分。
为了方便后续的查询演示,我们先创建如下一个exam_result表:
创建好表后,再向表中插入如下内容:
接下来的绝大部分查询相关操作都是在这张表中进行。
2. 全列查询
全列查询的方法很简单,就是直接使用“select * from 表名;”即可,其中的“*”大家可以认为是一个通配符,可以显示该表中的所有内容:
虽然在这里介绍了全列查询,但是在未来的工作中非常不建议大家用全列查询。因为未来我们的数据一般都是存放在远端的数据库的,这就意味着如果我们要从数据库拉取数据,就需要通过网络将数据传输过来。但是在现实中的一个普通公司的数据库中的信息量可能就是几百万上千万,如果直接使用全列查询,就会导致传输的数据量太大,并且也很难从里面找到自己需要的数据。
另一个原因就是全列查询可能会影响到索引的使用,关于索引就放到后面讲解,这里不再赘述。
总而言之,大家在自己写一个简单的数据库做测试的时候可以用全列查询,但是在未来的实际工作中就尽量不要用了。
3. 指定列查询
指定列查询,就是指定要查询某些列的内容。查询方法就是将*替换为指定的列。
上图就是单列查询。我们也可以进行多列查询:
在指定列查询中,就是从数据库找找到对面的数据,然后帮我们把指定的列显示出来。
4. 表达式查询
select语句除了查询数据库中的数据,还可以用于查询表达式:
通过这种方式,我们甚至可以将表达式和要查询的数据放在一起。
既然如此,那我们可以看看能不能帮我们把表中的计算结果显示出来:
可以看到,通过select可以查询表达式的特性,就可以让select帮我们计算表中的数据。但是大家仔细观察就可以发现,计算出的表达式的那一栏的名字很长,而且不方便看,因此,我们可以在表达式后面带上“as 别名”来给表达式取别名:
这里的这个as其实是可以不带的,直接在表达式后写别名也是可行的:
既然表达式可以取别名,那我们可不可以给要查询的列也取别名呢?测试一下:
同样是可以的。当然,大家也是可以带上as的,这里就不再测试了。
5. 结果去重
在查询数据时我们可能遇到数据有重复的情况:
如果我们想去掉重复的数据,只显示不重复的数据怎么办呢?使用“distinct”字段即可。在指定要查询的列前加上distinct字段就可以对对应列去重:
6. where条件
6.1 比较运算符与逻辑运算符
在上面的查询中,我们查出来的值要么是表的全部数据,要么是某列中的全部数据。很明显这类查找方式的查询范围太宽泛了,因此,在实际的查询中,我们是需要根据一定的条件去查询的。而要根据什么条件查询,就是由“where”条件决定。
(1)条件运算符
上图的内容就是mysql中常用的比较用的条件运算符。在这些运算符中,有几点需要注意。
1. 在C/C++中,我们判断两个值是否相等,是用的“==”,但是在mysql中是用“=”。
2. C中的null,我们可以将其看做0,因为在存储上他就是被当做0保存的。但是mysql中的null和0是两个东西,null表示没有对应数据,0表示有数据,但是数据为0。同时mysql中的0是无法参与运算的。
3. 在mysql中,由于null不会参与运算,所以如果比较的数据时null和null比较,此时就是不安全的:
我们知道,两个值比较的话,按道理应该要么返回0,要么返回1。但是由于null是不参与运算的,所以它的返回值是null。因此,当比较的数据中可能有null时,就不能使用“=”,而要使用"<=>"。这两个运算符的作用是一样的,区别只体现在<=>在比较null时是安全的。
但在实际上,如果是有null参与比较,我们更喜欢用is null和is not null来判断。
至于其他运算符,会在后续的讲解中使用到,这里就不再介绍了,图中也有对应的文字介绍。
(2)逻辑运算符
在C/C++的学习中,我们都知道&&、||和!这三个逻辑运算符,分别表示并且、或和非。mysql中同样有一样的含义的逻辑运算符。分别是and、or和not。
这三个运算符理解起来很简单,就不再赘述了。
为了让大家更好的理解这些运算符,在下面就通过几个查询案例来为大家演示如何使用where条件和运算符。
6.2 查询英语不及格的同学及其英语成绩
要查询英语不及格的同学,那我们首先就需要能查询到所有同学和其对应的英语成绩:
找到这些数据后,我们才能开始比较。既然是不及格,就说明小于60。因此查找条件就应该是English<60:
通过这条指令,就成功找到了要求的值。在条件查询中,所有的条件前面都需要带上where,后面就不再提示了。
6.3 查询语文成绩在[80,90]之间的同学及其语文成绩
要查询80到90之间的成绩有两种方法,第一种就是用条件运算符<=、>=和and连接:
第二种方法就是用条件运算符between and比较:
6.4 查询数学成绩是58或59或98或99的同学及其数学成绩
在这个题目中,要求我们查找出4个成绩。因此,我们可以用or来连接:
此时就筛选成功了。图中只有98的成绩是因为其他三个成绩在表中没有,如果大家想看到其他成绩,也可以自行添加几个方便更直观的看到情况。
除了用or连接,我们还可以用in:
在in后面的括号中的内容,大家就可以看成是用or连接起来的,只要有一个为真,就整体为真,将查询到的数据返回:
6.5 查询姓白的同学及白某同学
在有的时候,要求我们查找的值可能并不是一个完整的值,而是一个模糊的值。例如要你找一个姓白的人,但是给你的数据里面只有白这个关键字。此时就需要使用like进行模糊匹配:
通过上图可以发现,在模糊匹配中我们有两个通配符可以使用_和%。%可以表示0个及以上的任意字符;_ 只能表示任意一个字符。
在题目的要求中,第一个要求是查询姓白的同学,此时就可以用%进行匹配:
第二个要求是查询白某同学,这个要求的意思就是查找姓白,并且名字是两个的同学。此时就可以使用_匹配:
6.6 查询语文成绩好于英语成绩的同学
这个查询就比较简单了,直接比较英语和语文成绩即可:
6.7 总分在200分以下的同学
在个题目中,就要求用总分比较。但是表中并没有总分,所以需要我们自行计算。先计算总分:
总分可以计算出来,因此我们再给这条指令带上比较条件:
确实可以拿到值,但是大家有没有觉得这样写很麻烦。在上文中我们不是说过可以取别名么,因此我们再来尝试一下用别名的方式比较:
当我们用别名去比较的时候,mysql就给我们返回了一条错误信息,告诉我们where中找不到total关键字。这就很奇怪了, 明明我们显示的时候都能显示total,那为什么在比较的时候却无法使用呢?
这其实就涉及到了sql语句的执行顺序问题。我们来分析一下这条sql语句的执行顺序。这条mysql语句我们可以分为两个部分,分别是查询条件和显示表数据。
此时我们想一想,在这条sql语句中,到底是先执行查询条件,还是先显示表数据呢?很明显,是先执行查询条件。只有先执行查询条件,到表中去找到到对应的数据后,才能显示这些查找到的数据。因此在这条语句中,如果我们在where中使用显示表中的别名total,此时total还没有生成,mysql自然也就找不到了。
看到这个,有人可能就会向,既然在where中不能用select中的别名,那在select中用where中的别名呢?我们来测试一下:
可以看到,同样是不可行的。此时有人可能就觉得奇怪了,前面的where在select前执行所以无法使用select中的别名还能理解,那这里反过来为什么还不行呢?其实是因为对列做重命名是显示范畴,只是在显示的时候将显示的数据改一下,而筛选的时候是在数据库中筛选数据,不能对筛选的条件做重命名,在语法上就不支持这种做法。因此,上面的报错也告诉我们使用了错误的sql语句。
因此,在筛选数据时,只能选择不用别名的方式筛选:
6.8 查询语文成绩大于80,并且不姓白的同学
在这个题目中有两个条件,语文成绩大于80,并且不姓白。要不姓白,我们首先看如何找不姓白的同学。
从上文中知道,要找姓白的同学,可以用like进行相似匹配:
既然是要不姓白,那我们可以尝试在like前加一个not看能否正常执行:
可以看到,当在like前加上not后,就可以查询到不姓白的同学了。因此这道题就很简单了,只需在这个条件的基础上用and连接成绩大于80的条件即可:
6.9 查询白某和总成绩 > 200并且语文成绩 < 数学成绩并且英语成绩 > 80的同学
这个题目中一共要查询两类同学,一类是白某,另一类是满足特定成绩的同学。因此,我们可以用or将这两个条件连接起来:
在mysql中我们同样也可以用括号将某些条件括起来,将其整体视为一个条件。
6.10 null查询
上面我们查找的都是一个特定的值,在这里,我们再来尝试一下查询null。因为我们用于测试的这个exam_result表中没有null,所以我们在这里就重新建一个null_test表来测试一下:
向这个表中插入如下的值:
首先用“is null”查询是name是null的行:
再用“is not null”查询那么不是不是null的行:
两个条件判断都可以正常执行。注意,在查is not null的时候,显示了一个name为空的行,这也就说明了在mysql中空串和null是两个概念。
因此,我们也可以直接查找空串:
7. 结果排序
7.1 order by子句
在查询数据时,我们也可能遇到需要对查询到的结果进行排序的情况,例如我们想从高到低查看某个班级的同学的成绩,此时就需要对查询结果排序。
mysql中排序的方法也很简单,就是使用"order by"子句。其中by后面跟的字段就是排序方法。asc为升序,desc为降序。使用形式如下:
注意,order by后面也可以不跟排序方法,其默认的方法为asc,即升序。但是没有order by子句的查询,它返回的数据的顺序是未定义的,有的时候可能符合我们的需求,但是不要依赖这个顺序。
为了方便展示如何使用,下面就用几个排序例子来说明。
7.2 查询所有同学及其数学成绩,按数学成绩升序显示
在该题中,要求将查询到的数学成绩升序显示。此时就可以使用order by子句,再加上asc。
上文中说过,order by子句的默认行为就是升序,所以在这里也可以省略asc:
7.3 查询同学,按升序排序
在这道题中,为了与上题做区分,我们换一个表:
在这个表中,存在null和空串。因此,我们就用这个表来看一下排序后的结果。
通过上面的排序大家就应该可以发现,虽然null是不参与运算的,但是在mysql看来,null的值是最小的,并且认为空串的值大于null。
7.3 查询同学的各门成绩,依次按数学降序、英语降序和语文升序的方式显示
在这个题目中,要求依次按数学降序、英语升序和语文升序的方式显示。意思就是先按数学降序排序,如果在这里有人的数学成绩相同,就按英语降序显示;如果此时有人的英语成绩也相同,就按语文升序的方式显示。
当需要同时对多个数据进行排序时,可以用逗号做分隔符,后面依次带上要排序的列和方法。
7.4 查询同学总分,由高到低进行排序
在这道题目中,首先我们要能够得到总分,得到总分的方式前面已经讲过很多次了,只是简单的将列相加即可,不再多说。得到总分后再进行排序。看起来是比较简单的:
现在又有一个问题了。在上文中我们说过,where子句中是不能使用select子句中的列的别名的,那在order by这里,我们能不能用select子句的别名呢?测试一下:
可以看到,是可以的。那为什么order by中可以,where中却不可以呢?其实是同一个原因——sql语句的执行顺序问题。在where中是因为sql语句先执行where的内容,后执行select子句的内容,导致where中不能用select子句中的列别名。但是在order by中,要对数据进行排序,我们首先肯定要得到对应的数据。所以在使用了order by的查询语句中,select子句先执行,order by子句后执行,因此可以用select子句中的列别名。
7.5 查询姓白或姓青的同学,结果按数学成绩由高到低显示
在这道题中要求我们查询姓白或姓青的同学,因此就需要使用where子句和like进行模糊匹配。然后再用order by子句排序。
在where子句中,因为有两个查询条件,并且需要同时出现,所以用or连接。至于order by子句,直接用desc排序即可。
注意,当where和order by配合使用时,where要在order by的前面,否则就可能无法识别语句:
7.6 筛选分页结果
大家在日常中应该都知道,一本书上的所有内容都是按照页数显示的。原因就是如果将所有内容显示在一张纸上不仅不放便阅读,而且还会导致这张纸很大,并且可能遇到显示了很多不需要的内容的情况。因此, 书籍就是用页数来划分每一页的内容的。
mysql数据库中同样如此。有的时候我们可能不需要显示那么多的数据,只是想显示某个区间内的数据,此时就可以使用分页显示。
分别显示中需要使用“limit n offset s”子句。但是这个子句可以简写为“limist s, n”,如下图所示。
在上图中"limit n",表示从起始位置开始,查看n行数据;"limit s, n"表示从s位置开始,查看n行数据;“limit n offset s”,表示从s位置开始,查看n行数据,含义和“limit s, n”是一样的。
在原来的表中因为某些原因,没有显示id为1的同学。这里就临时插入一个id为1的同学:
如果大家的表中的id是从1开始的,就无需执行上述插入。
假设现在我们想查看exam_result表的前5行数据,就可以用limit子句:
可以看到,此时就是从表的起始位置帮我们显示了5行数据。
那这个起始位置的下标是什么呢?我们来测试一下,先从位置1开始:
通过id我们可以发现,当子句为“limit 1, 3”时,是从第二行开始显示的。这就意味着limit子句的起始位置应该是从0开始。我们来验证一下:
可以看到,当起始位置换为0后,就是从第一行数据开始显示了。此时就可以得出一个结论——limit子句的起始位置下标为0。
除了“limit s, n”,上文中说了还可以用“limit n offset s”查询,我们来测试一下:
依然可行。当然,在实际中建议还是使用“limit s, n”。一个是因为符合我们的直觉,显示从s开始的n行数据;另一个就是写起来比较简单。
7.7 查询同学的总成绩,拿到总成绩大于200,且为前三的同学成绩
在这道题中,要求我们拿到总成绩大于200,且总成绩前三的同学。因此我们首先就要用where子句筛选200以上的同学。然后用order by子句降序排序,最后再显示前三行的内容。
我们来思考一下这条sql语句的执行顺序。首先,因为我们需要先有条件才能筛选数据,所以where子句第一个执行;如果要对数据进行排序,我们首先就需要有可以排序的数据,因此select子句第二个执行,order by子句第三个执行;当所有数据都准备好后,就可以准备显示了,而limit子句是限定显示3行,因此limit子句最后执行,在即将显示时执行。
三、 更新
1. update语句
在某些情况下,我们可能需要对行数据进行更新。例如修改某个同学的数学成绩。此时我们就可以使用update语句。使用方法如下所示:
最基础的语句就是“update 表名 set 要修改的数据;”。在要修改的数据后面可以带上如where、order by等语句。虽然说是可选项,但在使用时一般来讲最少都会带上where语句用于筛选,毕竟如果不带筛选条件,那么update就是更新一整列的数据。
在更新这里我们依然采用exam_result表进行测试:
2. 将白飞飞同学的数学成绩变更为80分
要将白飞飞同学的成绩变更,就需要用where进行筛选。然后再用update更新。
更新完后查询成绩:
可以看到,数学成绩此时就已经被更新为了80。
3. 将银狼同学的数学成绩变更为60分,语文成绩变更为70分
在这道题中同样要用where子句筛选,但是需要同时变更它的两个成绩。当需要同时变更多个成绩的时候,就可以用逗号分隔需要变更的成绩。
查看成绩:
4. 将总成绩倒数前三的三位同学的数学成绩加上30分
在这个题目中,要求将总成绩倒数前三的同学的数学成绩加上30分。因此,我们首先来看如何筛选出这三位同学。由于是总成绩倒数前三,所以首先需要得到总成绩,然后用order by升序排序,最后用limit子句筛选出前三的同学,此时就拿到了倒数三位同学的成绩。
先来看倒数前三的同学的总成绩和数学成绩:
拿到成绩后,就可以用upate语句对他们的数学成绩做修改了:
查看所有人的成绩:
可以看到,此时的倒数前三的同学的数学成绩就全部更新了。至于这里的排序和原来不一样,是因为她们加上30分后的总成绩影响了排序。
注意,在sql中,是不支持使用+=、*=等运算符的,只能使用a = b+c这种格式进行计算。
5. 将所有同学的语文成绩更新为原来的2倍
在这个问题中,要求更新所有同学的语文成绩。因此,在这个更新中,就不需要带其他子句了。
先来看更新前的样子:
再用update语句更新语文成绩:
注意,在这里不能使用*=。
查看更新后的成绩:
整列数据都变为了原来的2倍。
注意,虽然在这里我们更新了整列数据,但是在实际中慎用更新整列数据。一旦更新后,就很难恢复。因此在使用update时要额外注意这点。
四、删除
1. delete语法
在某些时候,我们可能也需要删除某些数据。例如有某个同学退学了,此时就需要将该同学的数据从班级中删除。删除语句的使用也很简单,与select语句有点类似。如下图:
基础语法就可以看成“delete from 表名;”如果后面不带任何筛选条件,就是删除表中的所有数据。因此要慎用delete语句。
同样的,用如下的exam_result表进行测试:
2. 删除白飞飞同学的考试成绩
删除很简单,直接用delete语句加上where子句筛选即可:
查看成绩:
可以看到,此时就没有白飞飞同学的成绩了。
3. 删除全班总分倒数第一的同学的成绩
要删除总分倒数第一的同学的成绩,首先要得到总分。然后用order by子句升序排序,最后用limit子句查看第一行的内容。
先来看删除前的倒数第一:
然后再用delete语句删除:
再次查看所有同学的总成绩:
此时总分最低的同学的成绩就被删除了。
4. 删除整张表的数据
4.1 清空表
为了方便删除整张表,我们重新准备如下一个for_delete表:
然后插入如下数据:
大家要注意,在这个表中,我们特意将id的属性设置为了自增长。因此, 在删除前,先来看一下它的创建信息:
在创建信息中可以发现,此时的auto_increment=4。
删除整张表的数据:
再次查看它的创建信息:
可以看到,此时创建信息中的auto_increment依然是4。这就意味着在用delete语句删除数据时,并没有修改表的auto_increment的值。因此,下一次插入的时候自增长值就是从4开始。我们插入一个数据测试一下:
符合预期。
这就是删除表中所有数据的第一种方法,用delete语句清空表。它的特点就是删除数据后不会修改auto_increment的值。
4.2 截断表
清空表的所有数据还有一种方法,就是“截断表”。
要截断表,就需要使用truncate语句,使用形式如下:
依然使用上面的for_delete表。查看它的创建信息:
可以看到,此时的auto_increment的值为5。执行truncate语句:
执行完后再来看for_delete表的创建信息:
可以发现,此时创建信息中就没有auto_increment了。这就说明auto_increment的值被重置了,等待第一个值的插入更新auto_increment的值。因此,我们再插入一个值:
可以看到,id的值又变为了1。查看它的创建信息:
auto_increment的值变为2。符合auto_increment中的值为当前最大自增长值+1的预期。
从上面的例子中,就可以得到一个结论——truncate语句在清空表后,会将auto_increment的值回退到初始值。
注意,truncate语句只能对整表操作。
在这里再介绍一下truncate语句的特点:
1. 只能对整表操作,不能像delete那样对部分数据操作。
2. truncate语句在实际上是不会对数据进行操作,因此比delete的效率更高。但是truncate在删除数据时不会经过真正的“事务”,所以无法回滚。
3. 会重置auto_increment项。
第一点和第三点都好理解。第二点的无法回滚大家可能不理解。在这里大家不了解什么是“事务”,所以不太好多讲。现在大家可以简单的理解为,mysql中的每项有效操作都会被日志文件记录起来,包括delete操作。但是truncate语句执行的内容是不会被记录到日志文件中的。至于回滚,现在大家还感受不到,需要到后面了解了“事务”才能感受到回滚,这里就不再多讲。
5. 删除表中的重复数据
在上文中我们说过,如果表中的数据有重复的时候,可以在select后面加上distinct去重。
但是这道题不同,以前是不修改数据,仅仅显示不重复的数据,而现在则是要将重复的数据删除,留下不重复的部分。
为了方便测试,准备如下一个duplicate_table表:
创建好后,再插入如下数据:
准备好表,我们来看看这个表中有哪些不重复的数据:
可以发现,在duplicate中存在如上的不重复数据。但是这里要求的不是找到不重复数据,而是保留不重复数据。但是我们现在又无法找到重复数据。因此,换个思路,我们可以将这些不重复数据插入到另一个叫no_duplicate_table的表中,然后将原来的duplicate_table删除,最后将no_duplicate_table重命名为duplicate,此时不就相当于是删除了表中的重复数据了么。
第一步,创建一个叫no_duplicate_table的空表。但这有一个问题,如果表的列比较少还好,如果很多,难道我们就按照原表的创建信息一个个输么?这样非常费时。因此, 我们可以用like字段,让一个空表复制目标表的所有列和属性。
查看这两个表的详细属性:
完全相同。
第二步,将duplicate_table中的不重复数据插入no_duplicate_table。虽然我们可以用select语句找到不重复的数据,但我们怎么插入呢?难道在另一个表中对着这些不重复数据一个个手动输入吗?很明显是不可能的。
在这里大家要知道,inert语句和select语句是可以一起用的:
在这两个语句组合使用时,insert语句中就不需要在select前带values了。查看表中的数据:
第三步,将两个表进行重命名。表的重命名在以前的文章中讲过,这里就不再赘述,直接用rename语句即可。
查看duplicate_table的内容:
正是我们需要的数据。至于原来的duplicate_table,它已经被重命名为了old_duplicate_table:
旧表要删除还是留下,取决于实际需求。
注意,在执行这种需要对多个有关联的表重命名的操作时,尽量用一条rename语句完成,多个重命名之间用逗号连接。因为一条rename语句完成是原子性的,可以避免在重命名的时候有其他人在对数据库插入导致数据有问题的情况。
这里还有一个问题,那就是为什么我们不在一开始的时候就对表重命名,而是要在数据插入完成之后以一条rename语句重命名呢?假设有一天我们要上传一份数据到特定的目录下,这份数据很大,上传时间比较久。但是我们想让这份数据在被move到一个目录下的操作是原子的,此时就必不可能在上传的时候就将数据上传到特定目录,因为上传数据的过程很慢,并不是原子的。所以我们就会生成一份临时文件,这份临时文件中就用来保存上传的数据。当数据全部上传完后,就将这份文件整个move到对应的目录下,通过这种方式,就保证了数据在被move到特定目录下时是原子的。
rename也是一个道理,最后rename的原因就是为了让需要等待这份数据的执行的操作全部做完,然后原子性的统一更新文件名。
五、聚合函数
1. 常用的聚合函数
在mysql中也是有函数的。在这里就介绍几个函数,这几个函数可以用于按照记录为单位帮我们进行聚合统计:
这些聚合函数可以从表中帮我们统计一些需要的数据。例如count()就可以用来统计表中有多少数据;sum可以用来统计表中某列数据的总和等等。这些函数后面的括号中就是填你要统计的列。
为了方便大家理解,在这里就通过几个示例演示。
使用的表依然是上文中用的exam_result表:
2. 统计班级有多少同学
要统计班级有多少同学,就是看有多少行数据。此时就可以用count()函数。
在统计时使用“*”不会受到null的影响,而如果用特定的列名或表达式,就可能受到null影响。
假设我们现在有如下一个null_test表:
我们先用*统计所有行:
显示的值为5。但如果我们单独统计name列:
可以看到,此时现实的结果就是3,没有统计null。
3. 统计有多少份数学成绩
要统计有多少分数学成绩也很简单,在count后带上列名即可:
在统计时我们也可以顺带修改表中显示的列名。
4. 统计有多少份不重复的数学成绩
在这个题目中就要求我们要进行去重。注意,去重是对math的数据去重,而不是对count()统计的结果去重。例如如下图:
虽然这里确实将结果显示出来了,但是我们看一下表:
很明显math中有两个重复值,不重复的数据应该是5个才对。但是结果确实6个。原因就是我们是对count的结果去重,而不是对math去重。
修改指令,将distinct放到count中:
此时的统计结果才是正确的。
5. 统计数学成绩的总分
在这里要求统计数学成绩的总分,此时就可以使用sum函数。
6. 统计数学成绩 < 60的人数
要求统计数学成绩 < 60的人数,就可以用count()函数搭配where子句使用:
7. 统计班级的总体平均分
要统计平均分,有两个方式。第一个就是构建表达式:
这个例子只是为了告诉大家,这些函数也是可以形成表达式的。
第二个方法就是直接用avg()函数:
8. 返回英语的最高分
要返回最高分,除了我们以前用的利用order by desc降序排序+limit 1获取第一行的方法外,还可以直接用max函数:
但是要注意,在使用了聚合函数后,select后面就不能带上不能聚合的列了:
name中是字符,无法进行聚合,所以这里就会出问题。对于这些数据,就需要进行分组后才能聚合。至于“分组”的概念,到下面再讲。
9. 返回70分以上的数学成绩的最低分
对于该题,有两个条件,第一个就是70分以上,第二个就是最低分。按照我们以前的思路,就应该是用where筛选,然后用order by asc升序排列,最后用limit 1得到 第一个值。但是现在, 我们在使用where筛选后,就可以直接用min()函数获取最小值了。
六、分组聚合统计
1. group by子句
首先我们要知道,分组的目的就是为了方便聚合统计。例如现在有一个班级成绩表,里面是所有人的成绩。但现在我们想分别统计一下所有的男性和女性的成绩,这其实就是分组聚合统计。
要实现分组聚合统计,就要用group by子句,在select中使用group by子句的形式如下:
同样的,为了更好让大家看到使用方法和现象,同样要用一个表。但这个表就不再是上面自己写的表了,而是用oracle 9i的经典测试表:如下图的四张表:
如果大家没有这些表,可以自行到网络上搜索,很容易就能找到。这四个表中我们只需要关注emp员工表、dept部分表和salgrade工资等级表四张表即可。
2. 显示每个部门的平均工资和最高工资
通过上面的员工表我们可以看到,这些员工都是隶属于不同的部门的。那现在如果我们要获取部分每个部分的平均工资和最高工资,我们如何获取呢?按照以前的select语句加avg聚合函数我们也只能拿到所有人员的平均和最高工资,无法用部门区分啊。
此时,就可以采用group by进行分组。分组依据就是部门:
当然,我们也可以将每个部门的名字带上。
3. 理解分组聚合统计
有了上面的这个例子示范,大家就应该知道要对分组有一个了解了 。在上面的例子中,我们分组的原因就是题目要求按照不同的部分统计数据;而使用了group by后,mysql就是按照group by后面指定的列中的不同数据为依据进行分组;分组标准就是列内的相同数据为一组。在分完组后再分别将划分的各个组中的数据进行聚合统计。
解析出上面的内容后,对于分组聚合统计,我们就可以分成如下几个部分来理解。
1. 分组的目的就是为了在进行分组后,更好的进行聚合统计。
2. 在group by后面的列,就是在指定列名,用这个列中的不同行的数据进行分组。
3. 分组的条件,在组内一定是相同的。即每个组的组形成条件就是指定列的相同数据。这样才能进行聚合压缩。
4. 分组,简单来讲就是将一张大表,在逻辑上按照某个条件拆分为多个子表,然后在各自的表内进行聚合统计。
为了让大家更好理解,我们继续举几个分组聚合统计的例子。
4. 显示每个部门的每种岗位的平均工资和最低工资
在这个题目中,分组依据就有两个,一个是不同部分,另一个是不同部门内的不同岗位。
对于这个题目,我们要先有一个认知,那就是一个表中,是可以按照条件划分为一个个的子表的:
我们要做的,就是按照一定的条件,取到对应层的表数据,然后对其进行聚合统计。因此,无论题目要求划分多深,大家只需要记住,其本质就是让你形成一张张的子表然后进行聚合统计即可。即“先分组,再聚合”。
根据题目来看,分组依据是部门,然后是岗位。聚合统计的内容是平均工资和最低工资:
不同的分组依据可以用逗号分隔。当然,我们也可以将部门和岗位带上:
既然可以显示部门和岗位,那我们再来尝试一下看能否显示姓名:
可以看到,ename无法用于聚合统计。在这个表中,我们首先用部门分组,分完组后的每个子表的部门一定是一样的;随后再用岗位分组,此时每个子表中的部门和岗位就都是相同的了。但是如果我们此时再加一个姓名进去,这个姓名并没有被group by进行分组。由此,每个姓名对应的都是不同的值,无法进行聚合统计。
大家可以简单理解为,除了聚合函数,只有在group by后面出现的用于分组的依据,才能在select后面的聚合统计中出现。
为了验证这一点,我们将ename填入group by后的分组依据:
可以看到,此时ename就可以出现在select后面了。
5. 显示平均工资低于2000的部门和它的平均工资(having的使用)
对于这道题,我们首要要能够找到每个部门的平均工资:
能得到平均工资后,就需要用where加判断条件:
此时可以发现,sql语句错误。这是因为,where子句只能用于普通表的条件判断,无法用于分组聚合统计。在分组聚合统计中的条件判断需要使用“having”:
注意,这里的having可以使用select中的别名。这就是因为sql语句执行顺序的原因——先要计算平均工资,再用条件进行统计。因此select在having子句前执行,可以使用select的别名。
当然,这里重要的不是可以使用别名,而是这个新的having子句。
6. having和where的区别
在上面的实例中,where无法用于分组后的判断条件,那having能否用于未分组的判断条件呢?测试一下:
是可行的。这个结果并不意外。因为我们在上问说过,分组其实就是将一张大表按照某些条件分成一张张子表。无论是原表还是子表,它们都是表,因此,having也就可以用于未分组的表了。
当然,虽然having可以替代where的使用,但严重不推荐这样做。要建立where就是用于未分组的表的判断条件,而having就是用于分组后的表的判断条件。
为了让大家更好的看到使用区别,举个例子。假设现在有一个题目,要求大家统计每个部门下的每个岗位的小于2000的平均工资,但是SMITH员工不参与统计。
首先,要让SMISH员工不参与,因此就要在未分组前就叫他剔除:
剔除完后,再按照每个部门下的每个岗位进行分组和判断:
此时就成功拿到了要求的数据。
在这个例子中,就同时使用了where和having。那么如何理解这条sql语句中的执行顺序和这两个条件筛选的作用呢?首先,因为mysql会去到对应的表中;然后因为有where语句,所以在查找数据之前,需要先拿到筛选条件;当有了筛选条件后,又因为要进行分组,所以在查询数据前,要先对表内的数据按照条件进行分组;分组完后才用select去查询对应要求的数据;当数据查询完后即将显示的时候,才会用having条件筛掉无需显示的数据。
由此,这条sql语句的执行顺序为from -> where ->group by -> select -> having。
通过这个实例,我们就可以知道,where和having的条件筛选阶段完全不同。因此,在未来where和having千万不能错用。
同时我们还要知道,在上面的所有例子中,我们都是把根据一个个选项筛选出来后的数据当做表来处理的。因此,大家不要单纯的认为,只有在磁盘上的表结构导入到mysql中后真实存在于mysql中的表才叫做表。在对表进行处理和筛选的过程中的所有数据,都可以看成是一张张“逻辑上的表”,即“mysql下一切皆表”。
这就是说,在未来的MySQL中,只要我们能够处理好每张表的CRUD, 那么所有的sql场景,就都能以统一的方式处理。