Python学习之旅—Mysql数据库之表操作(where+group by+having+order by),pythonmysql,前言      本篇


前言   

   本篇博客将在上一篇的基础上,继续为大家梳理数据库的表操作。前面一篇博客主要对Mysql数据库和表的基础操作进行了介绍,这里将深入介绍如下五个重要知识点:

一 单表查询的语法
二 关键字的执行优先级(重点)
三 简单查询
四 WHERE约束
五 分组查询:GROUP BY
六 HAVING过滤
七 查询排序:ORDER BY
八 限制查询的记录数:LIMIT
九 使用正则表达式查询

一.单表查询

    前面我们已经简单演示了查询语法的使用,这里笔者给出一个更加详细的语法,方便各位后期进行复习:

SELECT 字段1,字段2... FROM 表名                  WHERE 条件                  GROUP BY field                  HAVING 筛选                  ORDER BY field                  LIMIT 限制条数

二. 单表查询中关键字的优先级

SELECT 字段1,字段2... FROM 表名                  WHERE 约束条件                  GROUP BY field  分组字段                  HAVING 过滤条件                  ORDER BY field 排序字段                  LIMIT 限制条数

根据上面的单表查询语法,其优先级分别为:首先会执行from 表名,即找到表,然后通过where条件来过滤掉一部分数据,注意这里where条件使用的是原始表中的字段进行过滤,;然后再进行分组操作,执行group by操作,分组完毕后

使用having关键字对分组后的结果进行过滤,注意这里和where条件过滤不同,这里一般是对聚合函数进行过滤。执行完having子句后,接下来是去显示我们的字段,例如要求查询的字段1,字段2,字段3等。在这一步,我们是可以给字段改名称的,因为后面order by有可能根据这个字段进行排序,如果原始名称比较长,那么排序的时候,写的SQL语句比较臃肿。接下来执行order by子句,最后是执行limit操作。综上所述,我们来总结下这几个关键字的执行顺序:

1.找到表:from

2.拿着where指定的约束条件,去文件/表中取出一条条记录

3.将取出的一条条记录进行分组group by,如果没有group by,则整体作为一组

4.将分组的结果进行having过滤

5.执行select

6.去重

7.将结果按条件排序:order by

8.限制结果的显示条数

   大家先在此有一个基本的概念,后面笔者将带领大家通过一个实际的综合性案例进行全面巩固。

三.插入数据语法补充

  在前面我们都是直接使用insert语句往表中插入数据,事实上,我们还可以将select和insert语句结合起来使用,完成表数据的插入!我们先来看看这种插入方式的语法:

语法:    INSERT INTO 表名(字段1,字段2,字段3…字段n)                     SELECT (字段1,字段2,字段3…字段n) FROM 表2                    WHERE …;

【001】创建表的同时插入数据:

create table user_new(  # 创建表的同时插入数据  host CHAR(60),  user CHAR(16))SELECT host,user FROM mysql.user;drop TABLE user_new;desc user_new;SELECT * from user_new;

以上SQL语句的执行结果如下:

技术分享

【002】我们再来看如下的SQL语句,当我们在新表中的字段和原始数据表中的字段不一致时,Mysql默认会将原始表的字段和数据添加到新表中,SQL语句如下所示:

CREATE table t3(  x CHAR(60), # 新的字段,原始user表中的字段为host  y CHAR(16)  # xe ) SELECT host, user from mysql.user;desc t3;SELECT *FROM t3;

以上语句的执行结果如下所示:

技术分享

这可能与我们的初衷相违背,我们可能需要的就是mysql.user表中的host和user字段,要想实现这样的效果,我们可以使用as来修改字段的名称,来看下面的例子:

CREATE table user_new1(  ip CHAR(60),  username CHAR(16)) SELECT host as ip,user as username from mysql.user;desc user_new1;SELECT *FROM user_new1;

以上代码的执行结果就是我们想要的:

技术分享


为了方便演示后面的知识点,我们在这里先新建一张表:

#创建表create table employee(id int not null unique auto_increment,name varchar(20) not null,sex enum(‘male‘,‘female‘) not null default ‘male‘, #大部分是男的age int(3) unsigned not null default 28,hire_date date not null,post varchar(50),post_comment varchar(100),salary double(15,2),office int, #一个部门一个屋子depart_id int);
插入数据:
insert into employee(name,sex,age,hire_date,post,salary,office,depart_id) values(‘egon‘,‘male‘,18,‘20170301‘,‘Python之家驻沙河办事处外交大使‘,7300.33,401,1), #以下是教学部(‘alex‘,‘male‘,78,‘20150302‘,‘teacher‘,1000000.31,401,1),(‘wupeiqi‘,‘male‘,81,‘20130305‘,‘teacher‘,8300,401,1),(‘yuanhao‘,‘male‘,73,‘20140701‘,‘teacher‘,3500,401,1),(‘liwenzhou‘,‘male‘,28,‘20121101‘,‘teacher‘,2100,401,1),(‘jingliyang‘,‘female‘,18,‘20110211‘,‘teacher‘,9000,401,1),(‘jinxin‘,‘male‘,18,‘19000301‘,‘teacher‘,30000,401,1),(‘成龙‘,‘male‘,48,‘20101111‘,‘teacher‘,10000,401,1),(‘歪歪‘,‘female‘,48,‘20150311‘,‘sale‘,3000.13,402,2),#以下是销售部门(‘丫丫‘,‘female‘,38,‘20101101‘,‘sale‘,2000.35,402,2),(‘丁丁‘,‘female‘,18,‘20110312‘,‘sale‘,1000.37,402,2),(‘星星‘,‘female‘,18,‘20160513‘,‘sale‘,3000.29,402,2),(‘格格‘,‘female‘,28,‘20170127‘,‘sale‘,4000.33,402,2),(‘张野‘,‘male‘,28,‘20160311‘,‘operation‘,10000.13,403,3), #以下是运营部门(‘程咬金‘,‘male‘,18,‘19970312‘,‘operation‘,20000,403,3),(‘程咬银‘,‘female‘,18,‘20130311‘,‘operation‘,19000,403,3),(‘程咬铜‘,‘male‘,18,‘20150411‘,‘operation‘,18000,403,3),(‘程咬铁‘,‘female‘,18,‘20140512‘,‘operation‘,17000,403,3);

四.单表查询之定义显示格式

  前面我们讲过了单表查询的一些关键字,例如as,distinct等,这里笔者不再赘述。我们重点来关注两个函数concat()函数和concat_ws()函数。

先来看看他们的格式:

#定义显示格式   CONCAT() 函数用于连接字符串   SELECT CONCAT(‘姓名: ‘,name,‘  年薪: ‘, salary*12)  AS Annual_salary    FROM employee;      CONCAT_WS() 第一个参数为分隔符   SELECT CONCAT_WS(‘:‘,name,salary*12)  AS Annual_salary    FROM employee;

以上两个函数的执行结果分别如下所示:

第一个函数:

技术分享

第二个函数:

技术分享

五.WHERE约束

Where条件中可以使用如下的关键字表达形式:

1. 比较运算符:> < >= <= <> !=2. between 80 and 100 值在10到20之间3. in(80,90,100) 值是10或20或304. like ‘egon%‘    pattern可以是%或_,    %表示任意多字符    _表示一个字符 5. 逻辑运算符:在多个条件直接可以使用逻辑运算符 and or not

笔者在上一篇博客中已经详细介绍了这里面几个重点关键字的使用,这里我们重点关注下in关键字的使用。

# 需求:查询员工表中年龄为60,70,80,18的员工SELECT id,name,ageFROM employeeWHERE age in (60,70,80,18);
注意这里in后面是一个集合,我们还可以在里面添加以一个字符串,例如:
where age in (60,70,‘aaa‘)

六.分组查询

  我们还是通过一个个实际的案例来详细说明下分组统计。

【001】

SELECT post, nameFROM employeeGROUP BY post;

以上代码的执行结果如下:

技术分享

   以上的例子说明了我们在实际应用中容易犯的一个错误,在使用group by的分组子句中,select后面的字段要么是分组字段,例如这里的post,要么是聚合函数产生的字段。不能存在原始表中的字段,例如我们在例子中直接使用了表中的原始字段name,这样虽然执行出结果,但是每组对应的name都是该组的第一个人,这显然不满足我们的需求。 

    出现上述错误的原因在于,我们没有很好地理解分组的本质,即分组的目的在于一类一类去处理,例如我想统计各个部门的最高工资,我想统计男女的人数等等。在分组完毕后,我们是以组为单位来处理数据,而不是来单独处理每条记录。

# 查看teacher部门的员工数SELECT count(id) FROM employee WHERE post=‘teacher‘;
这里我们并没有使用group by来进行直接分组,而是直接通过where条件来过滤出属于teacher的记录。因此
这里默认所有符合条件的记录都在一个组,所以可以使用聚合函数count来进行计算。

【002】ONLY_FULL_GROUP_BY参数

   前面我们说过类似于如下的这几种使用group by子句的查询是毫无意义的:

SELECT post, salaryFROM employeeGROUP BY post;

执行结果为:

技术分享

上面的查询语句根本毫无意义,但是Mysql依然为我们执行出结果,这在实际中往往会误导人。因此,我们可以利用参数ONLY_FULL_GROUP_BY来解决这个问题。说白了,该参数的语义表示select后面的字段要么是group by的分组字段,要么是聚合函数的结果,如果是其余的字段,它会报错。基于此,我们来试验下:

# 查看当前数据库所使用的sql_modeSELECT @@global.sql_mode;# 在root用户下修改数据库所使用sql_modeset GLOBAL SQL_MODE = ‘only_full_group_by‘;# 修改完毕后,退出root用户,然后重新登入# 我们再来执行原有的SQL语句,会出现如下的错误:# ERROR 1055 (42000): ‘mysql.employee.salary‘ isn‘t in GROUP BY# 这就表明我们修改成功

通过上面的修改,在使用group by子句时,select后面的字段要么是分组字段,要么是聚合函数,如果出现其他的字段,会直接报错!

这就设置该参数的意义所在。

【003】group by和其他函数的结合使用

刚才我们演示过,使用group by 能够查看的字段要么是分组字段,要么是聚合函数,但是现在我有这样的需求:我想查看属于同一组的组内成员姓名,怎么办?!这就要使用到group_concat函数了。

单独使用GROUP BY关键字分组    SELECT post FROM employee GROUP BY post;    注意:我们按照post字段分组,那么select查询的字段只能是post,想要获取组内的其他相关信息,需要借助函数GROUP BY关键字和GROUP_CONCAT()函数一起使用    SELECT post,GROUP_CONCAT(name) FROM employee GROUP BY post;#按照岗位分组,并查看组内成员名    SELECT post,GROUP_CONCAT(name) as emp_members FROM employee GROUP BY post;

上述SQL语句的执行结果;

技术分享

在使用group_concat函数时,我们还可以对group_concat函数的执行结果进行重命名,看如下的例子:

SELECT post, group_concat(‘薪资‘, salary) as ‘薪资待遇‘FROM employee GROUP BY post;

以上SQL语句的执行结果如下:

技术分享

下面我们来总结下group by子句的特点:

#1、首先明确一点:分组发生在where之后,即分组是基于where之后得到的记录而进行的#2、分组指的是:将所有记录按照某个相同字段进行归类,比如针对员工信息表的职位分组,或者按照性别进行分组等#3、为何要分组呢?    取每个部门的最高工资    取每个部门的员工数    取男人数和女人数小窍门:‘每’这个字后面的字段,就是我们分组的依据#4、大前提:    可以按照任意字段分组,但是分组完毕后,    比如group by post,只能查看post字段,如果想查看组内信息,需要借助于聚合函数
# 5.如果我们用unique的字段作为分组的依据,则每一条记录自成一组,这种分组没有意义的,在实际中,多条记录之间的某个字段值相同,该字段通常用来作为分组的依据

讲了这么多,我们一起来做几个例子测试下:

1. 查询岗位名以及岗位包含的所有员工名字2. 查询岗位名以及各岗位内包含的员工个数3. 查询公司内男员工和女员工的个数4. 查询岗位名以及各岗位的平均薪资5. 查询岗位名以及各岗位的最高薪资6. 查询岗位名以及各岗位的最低薪资7. 查询男员工与男员工的平均薪资,女员工与女员工的平均薪资

上述SQL语句的代码如下所示:

# 按照岗位进行分组,并查看组内成员的姓名SELECT post, group_concat(name)FROM employee GROUP BY post;SELECT post, group_concat(‘薪资‘, salary) as ‘薪资待遇‘FROM employee GROUP BY post;#  查询岗位名以及岗位包含的所有员工名字SELECT post, group_concat(name)FROM employeeGROUP BY post;#查询岗位名以及各岗位内包含的员工个数SELECT post, count(id)FROM employeeGROUP BY post;# 查询公司内男员工和女员工的个数SELECT sex, count(id) as ‘员工数‘FROM employeeGROUP BY sex;# 查询岗位名以及各岗位的平均薪资SELECT post, avg(salary) as ‘平均薪资‘FROM employeeGROUP BY post;#查询岗位名以及各岗位的最高薪资SELECT post, max(salary) as ‘最高工资‘FROM employeeGROUP BY post;# 查询岗位名以及各岗位的最低薪资SELECT post, min(salary) as ‘最高工资‘FROM employeeGROUP BY post;# 查询男员工与男员工的平均薪资,女员工与女员工的平均薪资SELECT sex, avg(salary) as ‘平均薪资‘FROM employeeGROUP BY sex;

七.Having过滤条件

   Having过滤和where过滤都可以用来过滤掉不需要的记录,但是它们的区别如下:

#!!!执行优先级从高到低:where > group by > having #1. Where 发生在分组group by之前,因而Where中可以有任意字段,但是绝对不能使用聚合函数。#2. Having发生在分组group by之后,因而Having中可以使用分组的字段,无法直接取到其他字段,可以使用聚合函数

   我们通过以下的实际例子来说明:

sql> select post from employeeGROUP BY postHAVING salary >10000[2017-10-25 18:17:46] [42S22][1054] Unknown column ‘salary‘ in ‘having clause‘

sql> SELECT post, group_concat(name) from employee GROUP BY post HAVING salary > 10000 #这里会报错,因为having发生在分组之后,但是having无法直接获取到salary,要想使用having,需要使用聚合函数
[2017-10-25 18:20:53] [42S22][1054] Unknown column ‘salary‘ in ‘having clause‘

讲解完上面的知识后,我们一起来练习几个需求,如下:

#1. 查询各岗位内包含的员工个数大于5的岗位名、岗位内包含员工名字、个数SELECT post,group_concat(name) as ‘员工‘,count(id) as ‘员工数‘FROM employeeGROUP BY postHAVING count(id) > 5;#3. 查询各岗位平均薪资大于10000的岗位名、平均工资SELECT post as ‘岗位‘, avg(salary) as ‘平均薪资‘from employeeGROUP BY postHAVING avg(salary) > 10000;#3. 查询各岗位平均薪资大于10000且小于20000的岗位名、平均工资SELECT post as ‘岗位‘,avg(salary) as ‘平均工资‘FROM employeeGROUP BY postHAVING avg(salary) >10000 and avg(salary)<20000;

最后我们来看一个小需求,从而完成对having知识点的结束:

# 取出平均工资>10000的部门以及它的平均工资SELECT post, avg(salary)from employee GROUP BY post HAVING avg(salary)>10000;#如果直接使用avg(salary)的别名x能完成上面的需求吗?为什么?# 在DG中可以执行,但是在cmd命令行中执行失败,并提示如下的错误:#ERROR 1463 (42000): Non-grouping field ‘x‘ is used in HAVING clause# 报错的原因是因为执行having语句时,select 语句还没有执行,既然如此,肯定没有执行到别名# 这一步,因此归根到底要等select执行完毕后,才会执行having语句。# 前面我们一再强调,having是对分组后的结果进行过滤,过滤条件要么是分组字段,要么是聚合函数# 这里的x显然不满足我们的需求。SELECT post, avg(salary) as xFROM employeeGROUP BY postHAVING x > 10000;

八.查询排序——order by

   前面我们说过order by是在having子句执行后才执行的,因此理解笔者在第一节中分析的代码执行流程是非常重要的。这里笔者结合实际的案例来详细分析下order by的使用流程。order by分为两种情况,一种是按照单列排序,另一种是按照多列排序,如下:

按单列排序    SELECT * FROM employee ORDER BY salary;    SELECT * FROM employee ORDER BY salary ASC;
# 默认是按照升序排序的,所以上面两条语句等同 SELECT * FROM employee ORDER BY salary DESC;按多列排序:先按照age排序,如果年纪相同,则按照薪资排序 SELECT * from employee ORDER BY age, salary DESC;# 这里表示如果年纪相同,那么就直接按照薪资降序排序

我们一起来实现几个需求,看看order by 到底是如何使用的:

# 1. 查询所有员工信息,先按照age升序排序,如果age相同则按照hire_date降序排序SELECT *FROM employeeORDER BY age,hire_date DESC ;# 2. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资升序排列SELECT post as ‘岗位‘, avg(salary) as ‘平均工资‘FROM employeeGROUP BY postHAVING avg(salary)>10000ORDER BY avg(salary);# 3. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资降序排列SELECT post as ‘岗位‘, avg(salary) as ‘平均工资‘FROM employeeGROUP BY postHAVING avg(salary)>10000ORDER BY avg(salary) DESC ;

PS:大家需要注意的是:在使用order by 进行排序的时候,我们使用的是排序字段,而不是聚合函数。我们在上面的第2个需求和第3个需求中很容易给大家造成这样的误会,下面我们来简单改写下上面的sql语句,以此来验证order by 使用的是字段。

# 2. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资升序排列SELECT post as ‘岗位‘, avg(salary) as xFROM employeeGROUP BY postHAVING avg(salary)>10000ORDER BY x;# 3. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资降序排列SELECT post as ‘岗位‘, avg(salary) as xFROM employeeGROUP BY postHAVING avg(salary)>10000ORDER BY x DESC ;

可以清楚地看到order by排序使用的是字段。其实这点也比较好理解,order by子句是在分组后的having子句后面执行的,此时select子句已经执行了,所以我们可以使用order by来排序字段。

九.limit限制查询记录的条数

   我们直接通过实际的案例来说明:

示例:    SELECT * FROM employee ORDER BY salary DESC         LIMIT 3;                    #默认初始位置为0         SELECT * FROM employee ORDER BY salary DESC        LIMIT 0,3; #从第0开始,即先查询出第一条,然后包含这一条在内往后查5条  # 即指定从0开始作为起始位置,然后往后数3条    SELECT * FROM employee ORDER BY salary DESC        LIMIT 3,3; #从第3条记录开始,即先查询出第4条,然后包含这一条在内往后查3条
# 按照这样的规律,limit是不包前,但是包后。例如我们可以按照如下的方式去取表中的数据:
SELECT * from employee LIMIT 0,3;
SELECT * from employee LIMIT 3,3;
SELECT * from employee LIMIT 6,3;
SELECT * from employee LIMIT 9,3;

【001】来看第一个需求:

# 需求:我现在想查询工资最高的员工信息SELECT *FROM employeeORDER BY salary DESC LIMIT 1;

【002】比如我现在有30213条记录,现在怎么使用呢?现在一页只能显示30条,这意味着步长是30

SELECT * from employee LIMIT 0,30;SELECT * from employee LIMIT 30,30;SELECT * from employee LIMIT 60,30; SELECT * from employee LIMIT 90,30;
。。。。。。

我们虽然可以这么做,但是这样做实际是有效率问题的。后面我们会结合实际的需求进行优化。

十.使用正则表达式进行查询

前面我们匹配表中的记录时,我们都是通过like来进行匹配,但是like只能匹配指定的模式。因此我们这里使用正则表达式的匹配关键字regexp来进行匹配,一起来看看下面的几个例子:

# 查询name以a开头的用户记录SELECT *FROM employeeWHERE name REGEXP ‘^al‘;# 查询name以on结尾的用户记录SELECT *FROM employeeWHERE name REGEXP ‘on$‘;# 查询name以e开头,以n结尾的用户记录SELECT *from employeeWHERE name REGEXP ‘^e.*n$‘;# 精确匹配m字符2次SELECT *FROM employeeWHERE name REGEXP ‘m{2}‘;

以上就是本次博客的主要内容,下一篇我们将聚焦于多表查询,希望各位好好掌握今天的知识点。

Python学习之旅—Mysql数据库之表操作(where+group by+having+order by)

评论关闭