九. select的7大子句
9.1 7大子句顺序
(1)from:从哪些表中筛选
(2)inner | left | right join ... on:关联多表查询时,去除笛卡尔积
(3)where:从表中原始字段数据筛选的条件
(4)group by:分组依据(with rollup)
注意:
1.在没有进行关联查询的前提下 select 后只能放分组字段 否则 有结果无意义
2.对分组后的结果进行统计 group后 添加 with rollup
3.可以多字段分组
4.先关联再进行分组
(5)having:在分组统计结果中再次筛选(对分组后的结果再次筛选)
(6)order by:排序字段1[,排序字段2] 默认 [desc] -- 从小到大
desc 从大到小
(7)limit:(pageNo-1)*pageSize,pageSize
pageNo: 页码(第几页)
pageSize:每页显示的条数
必须按照(1)-(7)的顺序编写子句。
9.2 演示

9.2.1 from子句
sql
#1、from子句
SELECT *
FROM t_employee; #表示从某个表中筛选数据
9.2.2 on子句
sql
#2、on子句
/*
(1)on必须配合join使用
(2)on后面只写关联条件
所谓关联条件是两个表的关联字段的关系
(3)有n张表关联,就有n-1个关联条件
两张表关联,就有1个关联条件
三张表关联,就有2个关联条件
*/
SELECT *
FROM t_employee INNER JOIN t_department
ON t_employee.did = t_department.did; #1个关联条件

sql
#查询员工的编号,姓名,职位编号,职位名称,部门编号,部门名称
#需要t_employee员工表,t_department部门表,t_job职位表
SELECT eid,ename,t_job.job_id,t_job.job_name, `t_department`.`did`,`t_department`.`dname`
FROM t_employee INNER JOIN t_department INNER JOIN t_job
ON t_employee.did = t_department.did AND t_employee.job_id = t_job.job_id;

9.2.3 where子句
sql
#3、where子句,在查询结果中筛选
#查询女员工的信息,以及女员工的部门信息
SELECT *
FROM t_employee INNER JOIN t_department
ON t_employee.did = t_department.did
WHERE gender = '女';

9.2.4 group by子句
sql
#4、group by分组
#查询所有员工的平均薪资
SELECT AVG(salary) FROM t_employee;
#查询每一个部门的平均薪资
SELECT did,ROUND(AVG(salary),2 )
FROM t_employee
GROUP BY did;

sql
#查询每一个部门的平均薪资,显示部门编号,部门的名称,该部门的平均薪资
SELECT t_department.did,dname,ROUND(AVG(salary),2 )
FROM t_department LEFT JOIN t_employee
ON t_department.did = t_employee.did
GROUP BY t_department.did;

sql
#查询每一个部门的平均薪资,显示部门编号,部门的名称,该部门的平均薪资
#要求,如果没有员工的部门,平均薪资不显示null,显示0
SELECT t_department.did,dname,IFNULL(ROUND(AVG(salary),2),0)
FROM t_department LEFT JOIN t_employee
ON t_department.did = t_employee.did
GROUP BY t_department.did;

sql
#查询每一个部门的女员工的平均薪资,显示部门编号,部门的名称,该部门的平均薪资
#要求,如果没有员工的部门,平均薪资不显示null,显示0
SELECT t_department.did,dname,IFNULL(ROUND(AVG(salary),2),0)
FROM t_department LEFT JOIN t_employee
ON t_department.did = t_employee.did
WHERE gender = '女'
GROUP BY t_department.did;

问题1:合计,WITH ROLLUP,加在group by后面
sql
#问题1:合计,WITH ROLLUP,加在group by后面
#按照部门统计人数
SELECT did, COUNT(*) FROM t_employee GROUP BY did;
#按照部门统计人数,并合计总数
SELECT did, COUNT(*) FROM t_employee GROUP BY did WITH ROLLUP;
SELECT IFNULL(did,'合计'), COUNT(*) FROM t_employee GROUP BY did WITH ROLLUP;
SELECT IFNULL(did,'合计') AS "部门编号" , COUNT(*) AS "人数" FROM t_employee GROUP BY did WITH ROLLUP;

问题2:是否可以按照多个字段分组统计
sql
#问题2:是否可以按照多个字段分组统计
#按照不同的部门,不同的职位,分别统计男和女的员工人数
SELECT did, job_id, gender, COUNT(*)
FROM t_employee
GROUP BY did, job_id, gender;

问题4:分组统计时,select后面字段列表的问题
sql
#问题4:分组统计时,select后面字段列表的问题
SELECT eid,ename, did, COUNT(*) FROM t_employee;
#eid,ename, did此时和count(*),不应该出现在select后面
SELECT eid,ename, did, COUNT(*) FROM t_employee GROUP BY did;
#eid,ename此时和count(*),不应该出现在select后面
SELECT did, COUNT(*) FROM t_employee GROUP BY did;
#分组统计时,select后面只写和分组统计有关的字段,其他无关字段不要出现,否则会引起歧义

9.2.5 having子句
5、having
having子句也写条件
where的条件是针对原表中的记录的筛选。where后面不能出现分组函数。
having子句是对统计结果(分组函数计算后)的筛选。having可以加分组函数。
sql
#查询每一个部门的女员工的平均薪资,显示部门编号,部门的名称,该部门的平均薪资
#要求,如果没有员工的部门,平均薪资不显示null,显示0
#最后只显示平均薪资高于12000的部门信息
SELECT t_department.did,dname,IFNULL(ROUND(AVG(salary),2),0)
FROM t_department LEFT JOIN t_employee
ON t_department.did = t_employee.did
WHERE gender = '女'
GROUP BY t_department.did
HAVING IFNULL(ROUND(AVG(salary),2),0) >12000;

sql
#查询每一个部门的男和女员工的人数
SELECT did,gender,COUNT(*)
FROM t_employee
GROUP BY did,gender;
#查询每一个部门的男和女员工的人数,显示部门编号,部门的名称,性别,人数
SELECT t_department.did,dname,gender,COUNT(eid)
FROM t_employee RIGHT JOIN t_department
ON t_employee.did = t_department.did
GROUP BY t_department.did,gender;
#查询每一个部门薪资超过10000的男和女员工的人数,显示部门编号,部门的名称,性别,人数
#只显示人数低于3人
SELECT t_department.did,dname,gender,COUNT(eid)
FROM t_employee RIGHT JOIN t_department
ON t_employee.did = t_department.did
WHERE salary > 10000
GROUP BY t_department.did,gender
HAVING COUNT(eid) < 3;

9.2.6 order by子句
6、排序 order by
升序和降序,默认是升序
asc代表升序
desc 代表降序
sql
#查询员工信息,按照薪资从高到低
SELECT * FROM t_employee
ORDER BY salary DESC;
#查询每一个部门薪资超过10000的男和女员工的人数,显示部门编号,部门的名称,性别,人数
#只显示人数低于3人,按照人数升序排列
SELECT t_department.did,dname,gender,COUNT(eid)
FROM t_employee RIGHT JOIN t_department
ON t_employee.did = t_department.did
WHERE salary > 10000
GROUP BY t_department.did,gender
HAVING COUNT(eid) < 3
ORDER BY COUNT(eid);
#查询员工的薪资,按照薪资从低到高,薪资相同按照员工编号从高到低
SELECT *
FROM t_employee
ORDER BY salary ASC , eid DESC;

9.2.7 limit子句
演示limit子句
limit子句是用于分页显示结果。
limit m,n
n:表示最多该页显示几行
m:表示从第几行开始取记录,第一个行的索引是0
m = (page-1)*n page表示第几页
每页最多显示5条,n=5
第1页,page=1,m = (1-1)*5 = 0; limit 0,5
第2页,page=2,m = (2-1)*5 = 5; limit 5,5
第3页,page=3,m = (3-1)*5 = 10; limit 10,5
sql
#查询员工表的数据,分页显示,每页显示5条记录
#第1页
SELECT * FROM t_employee LIMIT 0,5;
#第2页
SELECT * FROM t_employee LIMIT 5,5;
#第3页
SELECT * FROM t_employee LIMIT 10,5;
#第4页
SELECT * FROM t_employee LIMIT 15,5;
#第5页
SELECT * FROM t_employee LIMIT 20,5;
#第6页
SELECT * FROM t_employee LIMIT 25,5;

sql
#查询所有的男员工信息,分页显示,每页显示3条,第2页
#limit m,n n=3,page=2,m=(page-1)*n=3
SELECT *
FROM t_employee
WHERE gender ='男'
LIMIT 3,3
#查询每一个编号为奇数的部门,显示部门编号,名称,员工数量,
#只显示员工数量>=2的结果,按照员工数量升序排列,
#每页显示2条,显示第1页
SELECT t_department.did,dname,COUNT(eid)
FROM t_employee RIGHT JOIN t_department
ON t_employee.did = t_department.did
WHERE t_department.did%2=0
GROUP BY t_department.did
HAVING COUNT(eid)>=2
ORDER BY COUNT(eid)
LIMIT 0,2;


9.3 行列转置
9.3.1 单表
sql
DROP TABLE IF EXISTS `temp`;
CREATE TABLE `temp` (
`sid` int NOT NULL,
`sname` varchar(255),
`course` varchar(255) ,
`score` int
) ;
-- ----------------------------
-- Records of temp
-- ----------------------------
INSERT INTO `temp` VALUES (1, '张三', '语文', 89);
INSERT INTO `temp` VALUES (2, '张三', '数学', 96);
INSERT INTO `temp` VALUES (3, '张三', '英语', 75);
INSERT INTO `temp` VALUES (4, '李四', '语文', 88);
INSERT INTO `temp` VALUES (5, '李四', '数学', 99);
sql
select * from temp;

sql
/*
不够完美
select sid,sname,
case when course='语文' then score end as "语文",
CASE WHEN course='数学' THEN score END AS "数学",
CASE WHEN course='英语' THEN score END AS "英语"
from temp;*/
SELECT sid,sname,
SUM(CASE WHEN course='语文' THEN score END) AS "语文",
SUM(CASE WHEN course='数学' THEN score END) AS "数学",
SUM(CASE WHEN course='英语' THEN score END) AS "英语"
FROM temp
GROUP BY sname;

9.3.2 多表
sql
#学生表
drop table if exists t_stu;
create table t_stu(
sid int ,
sname varchar(20)
);
#添加学生信息
insert into t_stu values(1,'张三');
insert into t_stu values(2,'李四');
#课程表
drop table if exists t_course;
create table t_course(
cid int ,
cname varchar(50)
);
#添加课程信息
insert into t_course values(1,'java');
insert into t_course values(2,'mysql');
#成绩表
drop table if exists t_score;
create table t_score(
id int ,
sid int ,
cid int ,
score int
);
INSERT INTO t_score VALUES(1,1,1,89);
INSERT INTO t_score VALUES(2,2,1,100);
INSERT INTO t_score VALUES(3,1,2,79);
INSERT INTO t_score VALUES(3,2,2,89);
sql
#查询每个人的每门课的成绩
SELECT t_stu.sid,sname,t_course.cid,cname,score
FROM t_stu INNER JOIN t_score ON t_stu.sid = t_score.sid
INNER JOIN t_course ON t_course.cid = t_score.cid
ORDER BY t_stu.sid;
sql
#查询每个人的每门课的成绩
SELECT sid,sname,
SUM(CASE WHEN cname='java' THEN score END) AS "java",
SUM(CASE WHEN cname='mysql' THEN score END) AS "mysql"
FROM
(SELECT t_stu.sid,sname,t_course.cid,cname,score
FROM t_stu INNER JOIN t_score ON t_stu.sid = t_score.sid
INNER JOIN t_course ON t_course.cid = t_score.cid
ORDER BY t_stu.sid) temp
GROUP BY sid
ORDER BY sid;

十. 子查询
10.1 SELECT的SELECT中嵌套子查询
子查询:嵌套在另一个SQL语句中的查询。
SELECT语句可以嵌套在另一个SELECT中,UPDATE,DELETE,INSERT,CREATE语句等。
(1)SELECT的SELECT中嵌套子查询
sql
#(1)在"t_employee"表中查询每个人薪资和公司平均薪资的差值,
#并显示员工薪资和公司平均薪资相差5000元以上的记录。
SELECT ename AS "姓名",
salary AS "薪资",
ROUND((SELECT AVG(salary) FROM t_employee),2) AS "全公司平均薪资",
ROUND(salary-(SELECT AVG(salary) FROM t_employee),2) AS "差值"
FROM t_employee
WHERE ABS(ROUND(salary-(SELECT AVG(salary) FROM t_employee),2))>5000;

sql
#(2)在"t_employee"表中查询每个部门平均薪资和公司平均薪资的差值。
SELECT did,AVG(salary),
AVG(salary)-(SELECT AVG(salary) FROM t_employee) '差值'
FROM t_employee
GROUP BY did;

10.2 SELECT的WHERE或HAVING中嵌套子查询
当子查询结果作为外层另一个SQL的过滤条件,通常把子查询嵌入到 WHERE 或 HAVING 中。根据子查询结果的情况,分为如下三种情况。
- 当子查询的结果是单列单个值,那么可以直接使用比较运算符,如 "<"、"<="、">"、">="、"="、"!=" 等与子查询结果进行比较。
- 当子查询的结果是单列多个值,那么可以使用比较运算符 IN 或 NOT IN 进行比较。
- 当子查询的结果是单列多个值,还可以使用比较运算符, 如"<"、"<="、">"、">="、"="、"!="等搭配 ANY、ALL等关键字与查询结果进行比较。
子查询嵌套在where后面。
在where或having后面的子查询结果是:
(1)单个值,那么可以用=,>,<,>=,<=,!=这样的运算符和子查询的结果做比较
(2)多个值,那么需要用in,not in, >all,>any....形式做比较
如"<"、"<="、">"、">="、"="、"!="等搭配ANY、SOME、ALL等关键字与查询结果进行比较
select内
sql
-- select 查看 每一个员工的名字 薪资 与公司平均薪资的差值
SELECT ename,salary,
ROUND(salary- (SELECT AVG(salary) FROM t_employee)) '差值'
FROM t_employee;
where内
-- where
-- 如果子查询结果>1行 需要使用两个关键字 all any
-- all 与最值比
-- any与任意一个比
sql
#(1)在"t_employee"表中查询薪资最高的员工姓名(ename)和薪资(salary)。
#SELECT ename,MAX(salary) FROM t_employee;#错误
#取表中第一行员工的姓名和全公司最高的薪资值一起显示。
SELECT ename,salary
FROM t_employee
WHERE salary = (SELECT MAX(salary) FROM t_employee);
#(2)在"t_employee"表中查询比全公司平均薪资高的男员工姓名和薪资。
SELECT ename,salary
FROM t_employee
WHERE salary > (SELECT AVG(salary) FROM t_employee) AND gender = '男';
#(3)在"t_employee"表中查询和"白露","谢吉娜"同一部门的员工姓名和电话。
SELECT ename,tel,did
FROM t_employee
WHERE did IN(SELECT did FROM t_employee WHERE ename='白露' || ename='谢吉娜');
SELECT ename,tel,did
FROM t_employee
WHERE did =ANY(SELECT did FROM t_employee WHERE ename='白露' || ename='谢吉娜');
#(4)在"t_employee"表中查询薪资比"白露","李诗雨","黄冰茹"三个人的薪资都要高的员工姓名和薪资。
SELECT ename,salary
FROM t_employee
WHERE salary >ALL(SELECT salary FROM t_employee WHERE ename IN('白露','李诗雨','黄冰茹'));
having
sql
#(5)查询"t_employee"和"t_department"表,按部门统计平均工资,
#显示部门平均工资比全公司的总平均工资高的部门编号、部门名称、部门平均薪资,
#并按照部门平均薪资升序排列。
SELECT t_department.did,dname,AVG(salary)
FROM t_employee RIGHT JOIN t_department
ON t_employee.did = t_department.did
GROUP BY t_department.did
HAVING AVG(salary) >(SELECT AVG(salary) FROM t_employee)
ORDER BY AVG(salary);

10.3 SELECT中的EXISTS型子查询
EXISTS型子查询也是存在外层SELECT的WHERE子句中,不过它和上面的WHERE型子查询的工作模式不相同,所以这里单独讨论它。
如果EXISTS关键字后面的参数是一个任意的子查询,系统将对子查询进行运算以判断它是否返回行,如果至少返回一行,那么EXISTS的结果为true,此时外层查询语句将进行查询;如果子查询没有返回任何行,那么EXISTS的结果为false,此时外层查询语句不进行查询。EXISTS和NOT EXISTS的结果只取决于是否返回行,而不取决于这些行的内容,所以这个子查询输入列表通常是无关紧要的。
如果EXISTS关键字后面的参数是一个关联子查询,即子查询的WHERE条件中包含与外层查询表的关联条件,那么此时将对外层查询表做循环,即在筛选外层查询表的每一条记录时,都看这条记录是否满足子查询的条件,如果满足就再用外层查询的其他WHERE条件对该记录进行筛选,否则就丢弃这行记录。
(1)exists()中的子查询和外面的查询没有联合的情况下,
如果exists()中的子查询没有返回任何行,那么外面的子查询就不查了。
(2)exists()中的子查询与外面的查询有联合工作的情况下,
循环进行把外面查询表的每一行记录的值,代入()中子查询,如果可以查到结果,
就留下外面查询的这条记录,否则就舍去。
sql
#(1)查询"t_employee"表中是否存在部门编号为NULL的员工,
#如果存在,查询"t_department"表的部门编号、部门名称。
SELECT * FROM t_department
WHERE EXISTS(SELECT * FROM t_employee WHERE did IS NULL);
#(2)查询"t_department"表是否存在与"t_employee"表相同部门编号的记录,
#如果存在,查询这些部门的编号和名称。
SELECT * FROM t_department
WHERE EXISTS(SELECT * FROM t_employee WHERE t_employee.did = t_department.did);
#查询结果等价于下面的sql
SELECT DISTINCT t_department.*
FROM t_department INNER JOIN t_employee
ON t_department.did = t_employee.did;
1.如果EXISTS关键字后面的参数是一个任意的子查询,系统将对子查询进行运算以判断它是否返回行,
如果至少返回一行,那么EXISTS的结果为true,此时外层查询语句将进行查询;
如果子查询没有返回任何行,那么EXISTS的结果为false,此时外层查询语句不进行查询。
sql
SELECT * '
FROM t_employee
WHERE EXISTS (SELECT * FROM t_job WHERE jid=1);
2.如果EXISTS关键字后面的参数是一个关联子查询,即子查询的WHERE条件中包含与外层查询表的关联条件,
那么此时将对外层查询表做循环,即在筛选外层查询表的每一条记录时,都看这条记录是否满足子查询的条件,
如果满足就再用外层查询的其他WHERE条件对该记录进行筛选,否则就丢弃这行记录。
sql
SELECT *
FROM t_department d
WHERE EXISTS (SELECT * FROM t_employee e WHERE e.`did`=d.`did`);

10.4 SELECT的FROM中嵌套子查询
当子查询结果是多列的结果时,通常将子查询放到FROM后面,然后采用给子查询结果取别名的方式,把子查询结果当成一张"动态生成的临时表"使用。
当一个查询要基于另一个查询结果来筛选的时候,
另一个查询还是多行多列的结果,那么就可以把这个查询结果当成一张临时表,
放在from后面进行再次筛选。
sql
#(1)在"t_employee"表中,查询每个部门的平均薪资,
#然后与"t_department"表联合查询
#所有部门的部门编号、部门名称、部门平均薪资。
SELECT did,AVG(salary) FROM t_employee GROUP BY did;
+------+-------------+
| did | AVG(salary) |
+------+-------------+
| 1 | 11479.3125 |
| 2 | 13978 |
| 3 | 37858.25 |
| 4 | 12332 |
| 5 | 11725 |
+------+-------------+
5 ROWS IN SET (0.00 sec)
#用上面的查询结果,当成一张临时表,与t_department部门表做联合查询
#要给这样的子查询取别名的方式来当临时表用,不取别名是不可以的。
#而且此时的别名不能加""
#字段的别名可以加"",表的别名不能加""
SELECT t_department.did ,dname,AVG(salary)
FROM t_department LEFT JOIN (SELECT did,AVG(salary) FROM t_employee GROUP BY did) temp
ON t_department.did = temp.did;
#错误,from后面的t_department和temp表都没有salary字段,
#SELECT t_department.did ,dname,AVG(salary)出现AVG(salary)是错误的
SELECT t_department.did ,dname,pingjun
FROM t_department LEFT JOIN (SELECT did,AVG(salary) AS pingjun FROM t_employee GROUP BY did) temp
ON t_department.did = temp.did;

sql
#(2)在"t_employee"表中查询每个部门中薪资排名前2的员工姓名、部门编号和薪资。
SELECT * FROM (
SELECT ename,did,salary,
DENSE_RANK() over (PARTITION BY did ORDER BY salary DESC) AS paiming
FROM t_employee) temp
WHERE temp.paiming <=2;

10.5CTE通用表达式
通用表达式简称为CTE(Common Table Expressions)。
CTE是命名的临时结果集,作用范围是当前语句。CTE可以理解为一个可以复用的子查询,但是和子查询又有区别,一个CTE可以引用其他CTE,CTE还可以是自引用(递归CTE),也可以在同一查询中多次引用,但子查询不可以
语法结构
with [recursive]
cte_name [(字段名1,字段名2)] as (子查询),
cte_name [(字段名1,字段名2)] as (子查询)
通用表达式以with开头,如果with后面加recursive就表示接下来在通用表达式中需要递归引用自己,否则就不递归引用**。每一个通用表达式都需要有一个名字**,它相当于是子查询结果集的名字。
sql
# 查询大于10000的员工 展示对应的部门名称
WITH high_salary AS (SELECT * FROM t_employee WHERE salary> 10000)
SELECT d.dname '部门',h.ename '员工姓名',h.salary
FROM t_department AS d, high_salary AS h
WHERE d.did = h.did;

sql
-- 查询薪资低于9000的员工编号,员工姓名,员工薪资,领导编号,领导姓名,领导薪资
with
emp as (select eid,ename,salary,`mid` from t_employee where salary<9000),
mgr(meid,mename,msalary) as (select eid,ename,salary from t_employee)
select
eid as "员工薪资",
ename as "员工姓名",
salary as "员工薪资",
meid as "领导编号",
mename as "领导姓名",
msalary as "领导薪资"
from emp join mgr on emp.mid=mgr.meid;

sql
-- 查询编号1~6的部门
WITH recursive nums AS (
SELECT 1 AS n
UNION
SELECT n+1 FROM nums
WHERE n<6
)
SELECT * FROM t_department d JOIN nums n ON d.did=n.n;
