1 介绍
参考常见SQL面试题:经典50题,使用Python的pandas、sqlalchemy和pymysql库来完成练习题的所有操作。其中练习题数据库和表创建是在MySQLWorkbench中完成的。
- 数据库名:exercises
- 表名:
- student:学号、姓名、出生日期、性别
- score:学号、课程号、成绩
- course:课程号、课程名称、教师号
- teacher:教师号、教师姓名
pandas操作mysql数据库,具体使用方法,参考使用Python库操作Mysql数据库
系统环境:
- Mysql版本: 8.0.15 MySQL Community Server - GPL
- pymysql版本: 0.9.3
- pandas版本:0.23.1
- sqlalchemy版本:1.2.8
2 导入需要的包
```python
import pandas as pd
import pymysql
import warnings
from sqlalchemy import create_engine
warnings.filterwarnings(‘ignore’)
<a name="XAjzN"></a># 3 封装查询函数和插入函数<a name="C2Jny"></a>## 3.1 封装查询函数```pythondef query(sql_str, db_name='exercises'): param = 'mysql+pymysql://root:123456@localhost:3306/{db_name}?charset=utf8'.format(db_name=db_name) engine = create_engine(param) df = pd.read_sql(sql=sql_str, con=engine) return df
3.2 封装插入函数¶
def insert(df, tb_name, db_name='exercises'): param = 'mysql+pymysql://root:123456@localhost:3306/{db_name}?charset=utf8'.format(db_name=db_name) engine = create_engine(param) df.to_sql(tb_name, con=engine, if_exists='append', index=False)
3.3 查看连接状态
用query函数查看数据库有哪些表来判断数据库是否连接正常。
sql = 'SHOW TABLES'query(sql)
|
Tables_in_exercises |
| 0 |
course |
| 1 |
score |
| 2 |
student |
| 3 |
teacher |
能够正常查询到exercises数据库内的表,说明数据库连接正常。
4 初始化表内数据
4.1 向student表内添加数据
df_student = pd.DataFrame({'学号': ['0001', '0002', '0003', '0004'], '姓名': ['猴子', '猴子', '马云', '王思聪'], '出生日期': ['1989-01-01', '1990-12-21', '1991-12-21', '1990-05-20'], '性别': ['男', '女', '男', '男']} )df_student
|
学号 |
姓名 |
出生日期 |
性别 |
| 0 |
0001 |
猴子 |
1989-01-01 |
男 |
| 1 |
0002 |
猴子 |
1990-12-21 |
女 |
| 2 |
0003 |
马云 |
1991-12-21 |
男 |
| 3 |
0004 |
王思聪 |
1990-05-20 |
男 |
# 将df_student插入到student表中insert(df_student, 'student')
# 查询student表sql = '''SELECT * FROM student '''query(sql)
|
学号 |
姓名 |
出生日期 |
性别 |
| 0 |
0001 |
猴子 |
1989-01-01 |
男 |
| 1 |
0002 |
猴子 |
1990-12-21 |
女 |
| 2 |
0003 |
马云 |
1991-12-21 |
男 |
| 3 |
0004 |
王思聪 |
1990-05-20 |
男 |
4.2 向score表内添加数据
df_score = pd.DataFrame({'学号': ['0001', '0001', '0001', '0002', '0002', '0003', '0003', '0003'], '课程号': ['0001', '0002', '0003', '0002', '0003', '0001', '0002', '0003'], '成绩': [80, 90, 50, 60, 80, 80, 80, 80]} )df_score
|
学号 |
课程号 |
成绩 |
| 0 |
0001 |
0001 |
80 |
| 1 |
0001 |
0002 |
90 |
| 2 |
0001 |
0003 |
50 |
| 3 |
0002 |
0002 |
60 |
| 4 |
0002 |
0003 |
80 |
| 5 |
0003 |
0001 |
80 |
| 6 |
0003 |
0002 |
80 |
| 7 |
0003 |
0003 |
80 |
# 将df_score插入到score表内insert(df_score, 'score')
# 查询score表sql = '''SELECT * FROM score '''query(sql)
|
学号 |
课程号 |
成绩 |
| 0 |
0001 |
0001 |
80 |
| 1 |
0001 |
0002 |
90 |
| 2 |
0001 |
0003 |
50 |
| 3 |
0002 |
0002 |
60 |
| 4 |
0002 |
0003 |
80 |
| 5 |
0003 |
0001 |
80 |
| 6 |
0003 |
0002 |
80 |
| 7 |
0003 |
0003 |
80 |
4.3 向course表内插入数据
df_course = pd.DataFrame({'课程号': ['0001', '0002', '0003'], '课程名称': ['语文', '数学', '英语'], '教师号': ['0002', '0001', '0003']} )df_course
|
学号 |
姓名 |
出生日期 |
| 0 |
0001 |
语文 |
0002 |
| 1 |
0002 |
数学 |
0001 |
| 2 |
0003 |
英语 |
0003 |
# 将df_course插入到course表内insert(df_course, 'course')
# 查询course表sql = '''SELECT * FROM course '''query(sql)
|
学号 |
姓名 |
出生日期 |
| 0 |
0001 |
语文 |
0002 |
| 1 |
0002 |
数学 |
0001 |
| 2 |
0003 |
英语 |
0003 |
4.4 向teacher表内插入数据
df_teacher = pd.DataFrame({'教师号': ['0001', '0002', '0003', '0004'], '教师姓名': ['孟扎扎', '马化腾', '', '']} )df_teacher
|
学号 |
姓名 |
| 0 |
0001 |
孟扎扎 |
| 1 |
0002 |
马化腾 |
| 2 |
0003 |
|
| 3 |
0004 |
# 将df_teacher插入到teacher表中insert(df_teacher, 'teacher')
# 查询teacher表sql = '''SELECT * FROM teacher '''query(sql)
|
学号 |
姓名 |
| 0 |
0001 |
孟扎扎 |
| 1 |
0002 |
马化腾 |
| 2 |
0003 |
|
| 3 |
0004 |
5 练习题
5.1 简单查询
当字符串包含“%”而不转义时
5.1.1 查询姓“猴”的学生名单
# 使用like关键字进行模糊匹配sql = '''SELECT * FROM student WHERE 姓名 LIKE '猴%%' '''query(sql)
|
学号 |
姓名 |
出生日期 |
性别 |
| 0 |
0001 |
猴子 |
1989-01-01 |
男 |
| 1 |
0002 |
猴子 |
1990-12-21 |
女 |
5.1.2 查询姓名中包含“猴”的学生名单
# 使用like关键字进行模糊匹配sql = '''SELECT * FROM student WHERE 姓名 LIKE '%%猴%%' '''query(sql)
|
学号 |
姓名 |
出生日期 |
性别 |
| 0 |
0001 |
猴子 |
1989-01-01 |
男 |
| 1 |
0002 |
猴子 |
1990-12-21 |
女 |
5.1.3 查询姓名以“猴”结尾的学生名单
# 使用like关键字进行模糊匹配sql = '''SELECT * FROM student WHERE 姓名 LIKE '%%猴' '''query(sql)
5.1.4 查询姓“孟”老师的个数
# 使用count函数计数sql = '''SELECT count(教师号) AS 数量 FROM teacher WHERE 教师姓名 LIKE '孟%%' '''query(sql)
5.2 汇总分析
5.2.1 查询课程号“0002”的总成绩
# 使用sum函数进行求和sql = '''SELECT sum(成绩) AS 总成绩 FROM score WHERE 课程号='0002' '''query(sql)
5.2.2 查询选了课程的学生人数
# 使用distinct关键字去重sql = '''SELECT count(DISTINCT 学号) AS 学生人数 FROM score '''query(sql)
5.3 分组
5.3.1 查询各科成绩最高分和最低分
# 使用group by关键字进行分组sql = '''SELECT 课程号, max(成绩) AS 最高分, min(成绩) AS 最低分 FROM score GROUP BY 课程号 '''query(sql)
|
课程号 |
最高分 |
最低分 |
| 0 |
0001 |
80.0 |
80.0 |
| 1 |
0002 |
90.0 |
60.0 |
| 2 |
0003 |
80.0 |
50.0 |
5.3.2 查询每门课程被选修的学生数
# 使用group by关键字进行分组sql = '''SELECT 课程号, count(学号) AS 学生数 FROM score GROUP BY 课程号 '''query(sql)
|
课程号 |
学生数 |
| 0 |
0001 |
2 |
| 1 |
0002 |
3 |
| 2 |
0003 |
3 |
5.3.3 查询男、女学生人数
# 使用group by关键字进行分组sql = '''SELECT 性别, count(学号) AS 人数 FROM student GROUP BY 性别 '''query(sql)
5.4 分组结果的条件
5.4.1 查询平均成绩大于60分学生的学号和平均成绩
# 使用having关键字进行过滤sql = '''SELECT 学号, avg(成绩) AS 平均成绩 FROM score GROUP BY 学号 HAVING avg(成绩)>60 '''query(sql)
|
学号 |
平均成绩 |
| 0 |
0001 |
73.333333 |
| 1 |
0002 |
70.000000 |
| 2 |
0003 |
80.000000 |
5.4.2 查询至少选修两门课程的学生学号
# 使用having关键字进行过滤sql = '''SELECT 学号, count(课程号) AS 课程数 FROM score GROUP BY 学号 HAVING count(课程号)>=2 '''query(sql)
|
学号 |
课程数 |
| 0 |
0001 |
3 |
| 1 |
0002 |
2 |
| 2 |
0003 |
3 |
5.4.3 查询同名同姓学生名单并统计同名人数
# 按姓名分组后,人数大于等于2的即为sql = '''SELECT 姓名, count(*) AS 人数 FROM student GROUP BY 姓名 HAVING count(*) >=2 '''query(sql)
5.4.4 查询不及格的课程并按课程号降序排列
# 使用order by关键字进行排序sql = '''SELECT 课程号 FROM score WHERE 成绩<60 ORDER BY 课程号 DESC '''query(sql)
5.4.5 查询每门课平均成绩,结果按平均升级升序、课程号降序排列
# 使用order by关键字进行排序sql = '''SELECT 课程号, avg(成绩) AS 平均成绩 FROM score GROUP BY 课程号 ORDER BY avg(成绩) ASC, 课程号 DESC '''query(sql)
|
课程号 |
平均成绩 |
| 0 |
0003 |
70.000000 |
| 1 |
0002 |
76.666667 |
| 2 |
0001 |
80.000000 |
5.4.5 查询课程编号为“0004”且分数小于60的学生号,结果按分数降序排列
# 使用order by关键字进行排序sql = '''SELECT 学号 FROM score WHERE 课程号='0004' AND 成绩<60 ORDER BY 成绩 DESC '''query(sql)
5.4.6 查询每门课程的选修人数(超过2人的课程才统计)
# 使用order by关键字进行排序sql = '''SELECT 课程号, count(*) AS 人数 FROM score GROUP BY 课程号 HAVING count(*)>=2 ORDER BY count(*) DESC, 课程号 ASC '''query(sql)
|
课程号 |
人数 |
| 0 |
0002 |
3 |
| 1 |
0003 |
3 |
| 2 |
0001 |
2 |
5.5 复杂查询
5.5.1 查询所有课程成绩小于60分学生的学号和姓名
# 使用子查询进行条件筛选sql = '''SELECT 学号, 姓名 FROM student WHERE 学号 IN (SELECT 学号 FROM score WHERE 成绩<60) '''query(sql)
5.5.2 查询所有没有学全所有课的学生的学号和姓名
# 使用子查询进行条件筛选sql = '''SELECT 学号, 姓名 FROM student WHERE 学号 IN (SELECT 学号 FROM score GROUP BY 学号 HAVING count(课程号)<(SELECT count(课程号) FROM course)) '''query(sql)
5.5.3 查询只选修了两门课的学生的学号和姓名
# 使用子查询进行条件筛选sql = '''SELECT 学号, 姓名 FROM student WHERE 学号 IN (SELECT 学号 FROM score GROUP BY 学号 HAVING count(课程号)=2) '''query(sql)
5.5.4 查询1990年出生的学生名单
日期函数解释:
| 用途 |
函数 |
案例 |
| 当前日期 |
current_date |
2020-05-02 |
| 当前时间 |
current_time |
10:41:23 |
| 当前日期和时间 |
current_timestamp |
2020-05-02 10:41:23 |
- 获取日期的年份 - 月份 - 天
|
- year**(日期)** - month(日期) - day(日期)
|
year(‘2020-05-02’) 结果:2020 |
| 日期对应星期几 |
dayname(日期) |
dayname(‘2020-05-02 10:41:23’) 结果:星期六 |
# 使用日期函数进行筛选sql = '''SELECT 姓名 FROM student WHERE year(出生日期)=1990 '''query(sql)
5.5.5 查询各科成绩前两名的记录
关联子查询解决TOPN问题
关联子查询内部逻辑
# 用关联子查询来实现sql = '''(SELECT * FROM score WHERE 课程号='0001' ORDER BY 成绩 DESC LIMIT 2) UNION ALL (SELECT * FROM score WHERE 课程号='0002' ORDER BY 成绩 DESC LIMIT 2) UNION ALL (SELECT * FROM score WHERE 课程号='0003' ORDER BY 成绩 DESC LIMIT 2) '''query(sql)
|
学号 |
课程号 |
成绩 |
| 0 |
0001 |
0001 |
80.0 |
| 1 |
0003 |
0001 |
80.0 |
| 2 |
0001 |
0002 |
90.0 |
| 3 |
0003 |
0002 |
80.0 |
| 4 |
0002 |
0003 |
80.0 |
| 5 |
0003 |
0003 |
80.0 |
5.6 多表查询
5.6.1 查询所有学生的学号、姓名、选课数和总成绩
# 左连接student和score表,再用group by关键字进行分组sql = '''SELECT a.学号, a.姓名, count(b.课程号) AS 选课数, sum(b.成绩) AS 总成绩 FROM student AS a LEFT JOIN score AS b ON a.学号=b.学号 GROUP BY a.学号 '''query(sql)
|
学号 |
姓名 |
选课数 |
总成绩 |
| 0 |
0001 |
猴子 |
3 |
220.0 |
| 1 |
0002 |
猴子 |
2 |
140.0 |
| 2 |
0003 |
马云 |
3 |
240.0 |
| 3 |
0004 |
王思聪 |
0 |
NaN |
5.6.2 查询平均成绩大于70的所有学生的学号、姓名和平均成绩
# 左连接student和score表,再用group by进行分组,最后用having进行筛选sql = '''SELECT a.学号, a.姓名, avg(b.成绩) AS 平均成绩 FROM student AS a LEFT JOIN score AS b ON a.学号=b.学号 GROUP BY a.学号 HAVING avg(b.成绩)>70 '''query(sql)
|
学号 |
姓名 |
平均成绩 |
| 0 |
0001 |
猴子 |
73.333333 |
| 1 |
0003 |
马云 |
80.000000 |
5.6.3 查询学生的选课情况:学号、姓名、课程号和课程名称
# 先左连接score和course表,再用student表左连接上述结果表sql = '''SELECT a.学号, a.姓名, c.课程号, c.课程名称 FROM student AS a LEFT JOIN score AS b ON a.学号=b.学号 LEFT JOIN course AS c ON b.课程号=c.课程号 '''query(sql)
|
学号 |
姓名 |
课程号 |
课程名称 |
| 0 |
0001 |
猴子 |
0001 |
语文 |
| 1 |
0003 |
马云 |
0001 |
语文 |
| 2 |
0001 |
猴子 |
0002 |
数学 |
| 3 |
0002 |
猴子 |
0002 |
数学 |
| 4 |
0003 |
马云 |
0002 |
数学 |
| 5 |
0001 |
猴子 |
0003 |
英语 |
| 6 |
0002 |
猴子 |
0003 |
英语 |
| 7 |
0003 |
马云 |
0003 |
英语 |
| 8 |
0004 |
王思聪 |
None |
None |
5.6.4 查询每门课程的及格人数和不及格人数
MySQL case when 用法
# 使用 case when then else来对分组后的小组进行筛选sql = '''SELECT 课程号, sum(CASE WHEN 成绩>=60 THEN 1 ELSE 0 END) AS 及格人数, sum(CASE WHEN 成绩<60 THEN 1 ELSE 0 END) AS 不及格人数 FROM score GROUP BY 课程号 '''query(sql)
|
课程号 |
及格人数 |
不及格人数 |
| 0 |
0001 |
2 |
0 |
| 1 |
0002 |
3 |
0 |
| 2 |
0003 |
2 |
1 |
5.6.5 使用分段来统计各科成绩
# 左连接score和course表,再用group by进行分组,然后用case when进行筛选sql = '''SELECT a.课程号, b.课程名称, sum(CASE WHEN a.成绩 BETWEEN 85 AND 100 THEN 1 ELSE 0 END) AS '85分-100分', sum(CASE WHEN a.成绩 BETWEEN 70 AND 85 THEN 1 ELSE 0 END) AS '75分-85分', sum(CASE WHEN a.成绩 BETWEEN 60 AND 70 THEN 1 ELSE 0 END) AS '60分-70分', sum(CASE WHEN a.成绩<60 THEN 1 ELSE 0 END) AS '不及格' FROM score AS a LEFT JOIN course AS b ON a.课程号=b.课程号 GROUP BY a.课程号 '''query(sql)
|
课程号 |
课程名称 |
85分-100分 |
75分-85分 |
60分-70分 |
不及格 |
| 0 |
0001 |
语文 |
0 |
2 |
0 |
0 |
| 1 |
0002 |
数学 |
1 |
1 |
1 |
0 |
| 2 |
0003 |
英语 |
0 |
2 |
0 |
1 |
6 解决异常
6.1 连接Mysql拒绝访问异常
- 异常:使用pymysql和sqlalchemy连接mysql时,提示“Access denied for user ‘root’@’localhost’ (using password: NO)”异常,原因是pymysql版本太低。
方案:将其升级到最高版本0.7.9可以消除该异常。
6.2 tx_isolation隔离异常
异常:参考MySQL查询隔离级别报错1193。
方案:将sqlalchemy升级到1.2.10,解决该问题。
6.3 Incorrect string value警告
警告:使用sqlalchemy进行查询或者其它操作时,都提示Incorrect string value: ‘\xD6\xD0\xB9\xFA\xB1\xEA…’ for column ‘VARIABLE_VALUE’ at row 3375。
- 方案:参考MySQL 5.7.13 的一个BUG,为了美观使用warnings包,将警告消除。