Python-Core-50-Courses/第43课.SQL详解之DQL.md

491 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 第43课SQL详解之DQL
接下来,我们利用之前创建的学校选课系统数据库,为大家讲解 DQL 的应用。无论对于开发人员还是数据分析师DQL 都是非常重要的,它关系着我们能否从关系数据库中获取我们需要的数据。建议大家把上上一节课中建库建表的 DDL 以及 上一节课中插入数据的 DML 重新执行一次,确保表和数据跟没有问题再执行下面的操作。
```SQL
use `school`;
-- 01. 查询所有学生的所有信息
select *
from tb_student;
select stu_id
, stu_name
, stu_sex
, stu_birth
, stu_addr
, col_id
from tb_student;
-- 02. 查询学生的学号、姓名和籍贯(投影和别名)
select stu_id as 学号
, stu_name as 姓名
, stu_addr as 籍贯
from tb_student;
-- 03. 查询所有课程的名称及学分(投影和别名)
select cou_name as 课程名称
, cou_credit as 学分
from tb_course;
-- 04. 查询所有女学生的姓名和出生日期(数据筛选)
select stu_name
, stu_birth
from tb_student
where stu_sex = 0;
-- 05. 查询籍贯为“四川成都”的女学生的姓名和出生日期(数据筛选)
select stu_name
, stu_birth
from tb_student
where stu_sex = 0 and stu_addr = '四川成都';
-- 06. 查询籍贯为“四川成都”或者性别是女的学生(数据筛选)
select stu_name
, stu_birth
from tb_student
where stu_sex = 0 or stu_addr = '四川成都';
-- 07. 查询所有80后学生的姓名、性别和出生日期(数据筛选)
select stu_name
, stu_sex
, stu_birth
from tb_student
where '1980-1-1' <= stu_birth and stu_birth <= '1989-12-31';
select stu_name
, stu_sex
, stu_birth
from tb_student
where stu_birth between '1980-1-1' and '1989-12-31';
-- MySQL方言
select stu_name
, if(stu_sex, '男', '女') as stu_sex
, stu_birth
from tb_student
where stu_birth between '1980-1-1' and '1989-12-31';
select stu_name
, case stu_sex
when 1 then '男'
else '女'
end as stu_sex
, stu_birth
from tb_student
where stu_birth between '1980-1-1' and '1989-12-31';
-- 08. 查询学分大于2分的课程名称和学分(数据筛选)
select cou_name
, cou_credit
from tb_course
where cou_credit > 2;
-- 09. 查询学分是奇数的课程的名称和学分(数据筛选)
select cou_name
, cou_credit
from tb_course
where cou_credit mod 2 <> 0;
-- 10. 查询选择选了1111的课程考试成绩在90分以上的学生学号(数据筛选)
select stu_id
from tb_record
where cou_id = 1111 and score > 90;
-- 11. 查询名字叫“杨过”的学生的姓名和性别
select stu_name
, stu_sex
from tb_student
where stu_name = '杨过';
-- 12. 查询姓“杨”的学生姓名和性别(模糊查询)
-- wild card - 通配符 - % - 代表零个或任意多个字符
select stu_name
, stu_sex
from tb_student
where stu_name like '杨%';
-- 13. 查询姓“杨”名字两个字的学生姓名和性别(模糊查询)
-- wild card - 通配符 - _ - 精确匹配一个字符
select stu_name
, stu_sex
from tb_student
where stu_name like '杨_';
-- 14. 查询姓“杨”名字三个字的学生姓名和性别(模糊查询)
select stu_name
, stu_sex
from tb_student
where stu_name like '杨__';
-- 15. 查询名字中有“不”字或“嫣”字的学生的姓名(模糊查询)
select stu_name
from tb_student
where stu_name like '%不%' or stu_name like '%嫣%';
select stu_name
from tb_student
where stu_name like '%不%'
union
select stu_name
from tb_student
where stu_name like '%嫣%';
update tb_student
set stu_name = '岳不嫣'
where stu_id = 1572;
select stu_name
from tb_student
where stu_name like '%不%'
union all
select stu_name
from tb_student
where stu_name like '%嫣%';
-- 16. 查询姓“杨”或姓“林”名字三个字的学生的姓名(正则表达式模糊查询)
-- regular expression
select stu_name
from tb_student
where stu_name regexp '[杨林][\\u4e00-\\u9fa5]{2}';
-- 17. 查询没有录入籍贯的学生姓名(空值处理)
select stu_name
from tb_student
where stu_addr is null or trim(stu_addr) = '';
update tb_student
set stu_addr = ' '
where stu_id = 1572;
-- 18. 查询录入了籍贯的学生姓名(空值处理)
select stu_name
from tb_student
where stu_addr is not null and trim(stu_addr) <> '';
-- 19. 查询学生选课的所有日期(去重)
select distinct sel_date
from tb_record;
-- 20. 查询学生的籍贯(空值处理和去重)
select distinct stu_addr
from tb_student
where stu_addr is not null and trim(stu_addr) <> '';
-- 21. 查询男学生的姓名和生日按年龄从大到小排列(排序)
-- ascending / descending
select stu_name
, stu_birth
from tb_student
where stu_sex = 1
order by stu_birth asc;
-- 22. 将上面查询中的生日换算成年龄(日期函数、数值函数)
-- 获取当前日期curdate()
-- 计算时间差timestampdiff(unit, date1, date2)
select stu_name
, timestampdiff(year, stu_birth, curdate()) as stu_age
from tb_student
where stu_sex = 1
order by stu_age desc;
-- 聚合函数max / min / avg / sum / count / std / variance
-- 聚合函数会自动忽略掉null
-- 23. 查询年龄最大的学生的出生日期(聚合函数)
select min(stu_birth)
from tb_student;
-- 24. 查询年龄最小的学生的出生日期(聚合函数)
select max(stu_birth)
from tb_student;
-- 25. 查询编号为1111的课程考试成绩的最高分(聚合函数)
select max(score)
from tb_record
where cou_id = 1111;
-- 26. 查询学号为1001的学生考试成绩的最低分(聚合函数)
select min(score)
from tb_record
where stu_id = 1001;
-- 27. 查询学号为1001的学生考试成绩的平均分和标准差(聚合函数)
-- 四舍五入函数round(num, n)
select round(avg(score), 1) as avg_score
, round(std(score), 4) as std_score
from tb_record
where stu_id = 1001;
-- 28. 查询学号为1001的学生考试成绩的平均分如果有null值null值算0分(聚合函数)
select sum(score) / count(*)
from tb_record
where stu_id = 1001;
-- 29. 查询男女学生的人数(分组和聚合函数)
select case stu_sex when 1 then '男' else '女' end as stu_sex
, count(*) as total
from tb_student
group by stu_sex;
-- 30. 查询每个学院学生人数(分组和聚合函数)
select col_id
, count(*) as total
from tb_student
group by col_id
with rollup;
-- 31. 查询每个学院男女学生人数(分组和聚合函数)
select col_id
, case stu_sex when 1 then '男' else '女' end as stu_sex
, count(*) as total
from tb_student
group by col_id, stu_sex;
-- 32. 查询选课学生的学号和平均成绩(分组和聚合函数)
select stu_id
, round(avg(score), 1) as avg_score
from tb_record
group by stu_id;
-- 33. 查询平均成绩大于等于90分的学生的学号和平均成绩(分组和聚合函数)
-- 结论分组前的筛选使用where子句分组后的筛选使用having子句
select stu_id
, round(avg(score), 1) as avg_score
from tb_record
group by stu_id
having avg(score) >= 90;
-- 34. 查询所有课程成绩大于80分的同学的学号(分组和聚合函数)
select stu_id
from tb_record
group by stu_id
having min(score) > 80;
-- Error Code: 1242. Subquery returns more than 1 row
select stu_id
, stu_name
from tb_student
where stu_id in (select stu_id
from tb_record
group by stu_id
having min(score) > 80);
-- 35. 查询年龄最大的学生的姓名(嵌套查询)
-- 嵌套查询/子查询:把一个查询的结果作为另外一个查询的一部分来使用
select @a := min(stu_birth)
from tb_student;
select stu_name
from tb_student
where stu_birth = @a;
select stu_name
from tb_student
where stu_birth = (select min(stu_birth)
from tb_student);
-- 36. 查询选了两门以上的课程的学生姓名(嵌套查询/分组/数据筛选)
select stu_name
from tb_student
where stu_id in (select stu_id
from tb_record
group by stu_id
having count(*) > 2);
-- 37. 查询学生的姓名、生日和所在学院名称(连接查询)
select stu_name
, stu_birth
, col_name
from tb_student, tb_college
where tb_student.col_id = tb_college.col_id;
select stu_name
, stu_birth
, col_name
from tb_student inner join tb_college
on tb_student.col_id = tb_college.col_id;
select stu_name
, stu_birth
, col_name
from tb_student natural join tb_college;
-- 38. 查询学生姓名、课程名称以及成绩(连接查询)
select stu_name
, cou_name
, score
from tb_student, tb_course, tb_record
where tb_student.stu_id = tb_record.stu_id
and tb_course.cou_id = tb_record.cou_id
and score is not null;
select stu_name
, cou_name
, score
from tb_student
inner join tb_record
on tb_student.stu_id = tb_record.stu_id
inner join tb_course
on tb_course.cou_id = tb_record.cou_id
where score is not null;
select stu_name
, cou_name
, score
from tb_student
natural join tb_record
natural join tb_course
where score is not null;
-- 39. 上面的查询结果按课程和成绩排序取前5条数据(分页查询)
select stu_name
, cou_name
, score
from tb_student
natural join tb_record
natural join tb_course
where score is not null
order by cou_id asc, score desc
limit 5;
-- 40. 上面的查询结果按课程和成绩排序取第6-10条数据(分页查询)
select stu_name
, cou_name
, score
from tb_student
natural join tb_record
natural join tb_course
where score is not null
order by cou_id asc, score desc
limit 5
offset 5;
-- 41. 上面的查询结果按课程和成绩排序取第11-15条数据(分页查询)
select stu_name
, cou_name
, score
from tb_student
natural join tb_record
natural join tb_course
where score is not null
order by cou_id asc, score desc
limit 5
offset 10;
-- 42. 查询选课学生的姓名和平均成绩(嵌套查询和连接查询)
select stu_name
, avg_score
from tb_student
natural join (select stu_id
, avg(score) as avg_score
from tb_record
group by stu_id) as tmp;
-- 43. 查询学生的姓名和选课的数量(嵌套查询和连接查询)
select stu_name
, total
from tb_student
inner join (select stu_id
, count(*) as total
from tb_record
group by stu_id) as tmp
on tb_student.stu_id = tmp.stu_id;
-- 44. 查询所有学生的姓名和选课数量(左外连接和嵌套查询)
-- 左外连接把左表写在join左边的表所有的数据都拿到不满足连表条件的地方填充null - left outer join
-- 右外连接把右表写在join右边的表所有的数据都拿到不满足连表条件的地方填充null - right outer join
-- 全外连接把左表和右表的数据全部拿到即便它们不满足连表条件MySQL不支持全外连接 - full outer join
select stu_name
, coalesce(total, 0) as total
from tb_student
left join (select stu_id
, count(*) as total
from tb_record
group by stu_id) as tmp
on tb_student.stu_id = tmp.stu_id;
-- 45. 查询没有选课的学生的姓名(左外连接和数据筛选)
select stu_name
from tb_student
left join tb_record
on tb_student.stu_id = tb_record.stu_id
where tb_record.stu_id is null;
```
上面的 DQL 有几个地方需要加以说明:
1. MySQL目前的版本不支持全外连接上面我们通过`union`操作,将左外连接和右外连接的结果求并集实现全外连接的效果。大家可以通过下面的图来加深对连表操作的认识。
<img src="http://localhost/mypic/20211121135117.png" style="zoom:50%">
2. MySQL 中支持多种类型的运算符,包括:算术运算符(`+`、`-`、`*`、`/`、`%`)、比较运算符(`=`、`<>`、`<=>`、`<`、`<=`、`>`、`>=`、`BETWEEN...AND..`.、`IN`、`IS NULL`、`IS NOT NULL`、`LIKE`、`RLIKE`、`REGEXP`)、逻辑运算符(`NOT`、`AND`、`OR`、`XOR`)和位运算符(`&`、`|`、`^`、`~`、`>>`、`<<`),我们可以在 DML 中使用这些运算符处理数据。
3. 在查询数据时,可以在`SELECT`语句及其子句(如`WHERE`子句、`ORDER BY`子句、`HAVING`子句等)中使用函数,这些函数包括字符串函数、数值函数、时间日期函数、流程函数等,如下面的表格所示。
常用字符串函数。
| 函数 | 功能 |
| --------------------------- | ----------------------------------------------------- |
| `CONCAT` | 将多个字符串连接成一个字符串 |
| `FORMAT` | 将数值格式化成字符串并指定保留几位小数 |
| `FROM_BASE64` / `TO_BASE64` | BASE64解码/编码 |
| `BIN` / `OCT` / `HEX` | 将数值转换成二进制/八进制/十六进制字符串 |
| `LOCATE` | 在字符串中查找一个子串的位置 |
| `LEFT` / `RIGHT` | 返回一个字符串左边/右边指定长度的字符 |
| `LENGTH` / `CHAR_LENGTH` | 返回字符串的长度以字节/字符为单位 |
| `LOWER` / `UPPER` | 返回字符串的小写/大写形式 |
| `LPAD` / `RPAD` | 如果字符串的长度不足,在字符串左边/右边填充指定的字符 |
| `LTRIM` / `RTRIM` | 去掉字符串前面/后面的空格 |
| `ORD` / `CHAR` | 返回字符对应的编码/返回编码对应的字符 |
| `STRCMP` | 比较字符串,返回-1、0、1分别表示小于、等于、大于 |
| `SUBSTRING` | 返回字符串指定范围的子串 |
常用数值函数。
| 函数 | 功能 |
| -------------------------------------------------------- | ---------------------------------- |
| `ABS` | 返回一个数的绝度值 |
| `CEILING` / `FLOOR` | 返回一个数上取整/下取整的结果 |
| `CONV` | 将一个数从一种进制转换成另一种进制 |
| `CRC32` | 计算循环冗余校验码 |
| `EXP` / `LOG` / `LOG2` / `LOG10` | 计算指数/对数 |
| `POW` | 求幂 |
| `RAND` | 返回[0,1)范围的随机数 |
| `ROUND` | 返回一个数四舍五入后的结果 |
| `SQRT` | 返回一个数的平方根 |
| `TRUNCATE` | 截断一个数到指定的精度 |
| `SIN` / `COS` / `TAN` / `COT` / `ASIN` / `ACOS` / `ATAN` | 三角函数 |
常用时间日期函数。
| 函数 | 功能 |
| ----------------------------- | ------------------------------------- |
| `CURDATE` / `CURTIME` / `NOW` | 获取当前日期/时间/日期和时间 |
| `ADDDATE` / `SUBDATE` | 将两个日期表达式相加/相减并返回结果 |
| `DATE` / `TIME` | 从字符串中获取日期/时间 |
| `YEAR` / `MONTH` / `DAY` | 从日期中获取年/月/日 |
| `HOUR` / `MINUTE` / `SECOND` | 从时间中获取时/分/秒 |
| `DATEDIFF` / `TIMEDIFF` | 返回两个时间日期表达式相差多少天/小时 |
| `MAKEDATE` / `MAKETIME` | 制造一个日期/时间 |
常用流程函数。
| 函数 | 功能 |
| -------- | ------------------------------------------------ |
| `IF` | 根据条件是否成立返回不同的值 |
| `IFNULL` | 如果为NULL则返回指定的值否则就返回本身 |
| `NULLIF` | 两个表达式相等就返回NULL否则返回第一个表达式的值 |
其他常用函数。
| 函数 | 功能 |
| -------------------------- | ----------------------------- |
| `MD5` / `SHA1` / `SHA2` | 返回字符串对应的哈希摘要 |
| `CHARSET` / `COLLATION` | 返回字符集/校对规则 |
| `USER` / `CURRENT_USER` | 返回当前用户 |
| `DATABASE` | 返回当前数据库名 |
| `VERSION` | 返回当前数据库版本 |
| `FOUND_ROWS` / `ROW_COUNT` | 返回查询到的行数/受影响的行数 |
| `LAST_INSERT_ID` | 返回最后一个自增主键的值 |
| `UUID` / `UUID_SHORT` | 返回全局唯一标识符 |