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’)

  1. <a name="XAjzN"></a>
  2. # 3 封装查询函数和插入函数
  3. <a name="C2Jny"></a>
  4. ## 3.1 封装查询函数
  5. ```python
  6. def query(sql_str, db_name='exercises'):
  7. param = 'mysql+pymysql://root:123456@localhost:3306/{db_name}?charset=utf8'.format(db_name=db_name)
  8. engine = create_engine(param)
  9. df = pd.read_sql(sql=sql_str, con=engine)
  10. return df

3.2 封装插入函数

  1. def insert(df, tb_name, db_name='exercises'):
  2. param = 'mysql+pymysql://root:123456@localhost:3306/{db_name}?charset=utf8'.format(db_name=db_name)
  3. engine = create_engine(param)
  4. df.to_sql(tb_name, con=engine, if_exists='append', index=False)

3.3 查看连接状态

用query函数查看数据库有哪些表来判断数据库是否连接正常。

  1. sql = 'SHOW TABLES'
  2. query(sql)
Tables_in_exercises
0 course
1 score
2 student
3 teacher

能够正常查询到exercises数据库内的表,说明数据库连接正常。

4 初始化表内数据

4.1 向student表内添加数据

  1. df_student = pd.DataFrame({'学号': ['0001', '0002', '0003', '0004'],
  2. '姓名': ['猴子', '猴子', '马云', '王思聪'],
  3. '出生日期': ['1989-01-01', '1990-12-21', '1991-12-21', '1990-05-20'],
  4. '性别': ['男', '女', '男', '男']}
  5. )
  6. df_student
学号 姓名 出生日期 性别
0 0001 猴子 1989-01-01
1 0002 猴子 1990-12-21
2 0003 马云 1991-12-21
3 0004 王思聪 1990-05-20
  1. # 将df_student插入到student表中
  2. insert(df_student, 'student')
  1. # 查询student表
  2. sql = '''SELECT *
  3. FROM student
  4. '''
  5. 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表内添加数据

  1. df_score = pd.DataFrame({'学号': ['0001', '0001', '0001', '0002', '0002', '0003', '0003', '0003'],
  2. '课程号': ['0001', '0002', '0003', '0002', '0003', '0001', '0002', '0003'],
  3. '成绩': [80, 90, 50, 60, 80, 80, 80, 80]}
  4. )
  5. 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
  1. # 将df_score插入到score表内
  2. insert(df_score, 'score')
  1. # 查询score表
  2. sql = '''SELECT *
  3. FROM score
  4. '''
  5. 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表内插入数据

  1. df_course = pd.DataFrame({'课程号': ['0001', '0002', '0003'],
  2. '课程名称': ['语文', '数学', '英语'],
  3. '教师号': ['0002', '0001', '0003']}
  4. )
  5. df_course
学号 姓名 出生日期
0 0001 语文 0002
1 0002 数学 0001
2 0003 英语 0003
  1. # 将df_course插入到course表内
  2. insert(df_course, 'course')
  1. # 查询course表
  2. sql = '''SELECT *
  3. FROM course
  4. '''
  5. query(sql)
学号 姓名 出生日期
0 0001 语文 0002
1 0002 数学 0001
2 0003 英语 0003

4.4 向teacher表内插入数据

  1. df_teacher = pd.DataFrame({'教师号': ['0001', '0002', '0003', '0004'],
  2. '教师姓名': ['孟扎扎', '马化腾', '', '']}
  3. )
  4. df_teacher
学号 姓名
0 0001 孟扎扎
1 0002 马化腾
2 0003
3 0004
  1. # 将df_teacher插入到teacher表中
  2. insert(df_teacher, 'teacher')
  1. # 查询teacher表
  2. sql = '''SELECT *
  3. FROM teacher
  4. '''
  5. query(sql)
学号 姓名
0 0001 孟扎扎
1 0002 马化腾
2 0003
3 0004

5 练习题

5.1 简单查询

当字符串包含“%”而不转义时

5.1.1 查询姓“猴”的学生名单

  1. # 使用like关键字进行模糊匹配
  2. sql = '''SELECT *
  3. FROM student
  4. WHERE 姓名 LIKE '猴%%'
  5. '''
  6. query(sql)
学号 姓名 出生日期 性别
0 0001 猴子 1989-01-01
1 0002 猴子 1990-12-21

5.1.2 查询姓名中包含“猴”的学生名单

  1. # 使用like关键字进行模糊匹配
  2. sql = '''SELECT *
  3. FROM student
  4. WHERE 姓名 LIKE '%%猴%%'
  5. '''
  6. query(sql)
学号 姓名 出生日期 性别
0 0001 猴子 1989-01-01
1 0002 猴子 1990-12-21

5.1.3 查询姓名以“猴”结尾的学生名单

  1. # 使用like关键字进行模糊匹配
  2. sql = '''SELECT *
  3. FROM student
  4. WHERE 姓名 LIKE '%%猴'
  5. '''
  6. query(sql)
学号 姓名 出生日期 性别

5.1.4 查询姓“孟”老师的个数

  1. # 使用count函数计数
  2. sql = '''SELECT count(教师号) AS 数量
  3. FROM teacher
  4. WHERE 教师姓名 LIKE '孟%%'
  5. '''
  6. query(sql)
数量
0 1

5.2 汇总分析

5.2.1 查询课程号“0002”的总成绩

  1. # 使用sum函数进行求和
  2. sql = '''SELECT sum(成绩) AS 总成绩
  3. FROM score
  4. WHERE 课程号='0002'
  5. '''
  6. query(sql)
总成绩
0 230.0

5.2.2 查询选了课程的学生人数

  1. # 使用distinct关键字去重
  2. sql = '''SELECT count(DISTINCT 学号) AS 学生人数
  3. FROM score
  4. '''
  5. query(sql)
学生人数
0 3

5.3 分组

5.3.1 查询各科成绩最高分和最低分

  1. # 使用group by关键字进行分组
  2. sql = '''SELECT 课程号, max(成绩) AS 最高分, min(成绩) AS 最低分
  3. FROM score
  4. GROUP BY 课程号
  5. '''
  6. query(sql)
课程号 最高分 最低分
0 0001 80.0 80.0
1 0002 90.0 60.0
2 0003 80.0 50.0

5.3.2 查询每门课程被选修的学生数

  1. # 使用group by关键字进行分组
  2. sql = '''SELECT 课程号, count(学号) AS 学生数
  3. FROM score
  4. GROUP BY 课程号
  5. '''
  6. query(sql)
课程号 学生数
0 0001 2
1 0002 3
2 0003 3

5.3.3 查询男、女学生人数

  1. # 使用group by关键字进行分组
  2. sql = '''SELECT 性别, count(学号) AS 人数
  3. FROM student
  4. GROUP BY 性别
  5. '''
  6. query(sql)
性别 人数
0 3
1 1

5.4 分组结果的条件

5.4.1 查询平均成绩大于60分学生的学号和平均成绩

  1. # 使用having关键字进行过滤
  2. sql = '''SELECT 学号, avg(成绩) AS 平均成绩
  3. FROM score
  4. GROUP BY 学号
  5. HAVING avg(成绩)>60
  6. '''
  7. query(sql)
学号 平均成绩
0 0001 73.333333
1 0002 70.000000
2 0003 80.000000

5.4.2 查询至少选修两门课程的学生学号

  1. # 使用having关键字进行过滤
  2. sql = '''SELECT 学号, count(课程号) AS 课程数
  3. FROM score
  4. GROUP BY 学号
  5. HAVING count(课程号)>=2
  6. '''
  7. query(sql)
学号 课程数
0 0001 3
1 0002 2
2 0003 3

5.4.3 查询同名同姓学生名单并统计同名人数

  1. # 按姓名分组后,人数大于等于2的即为
  2. sql = '''SELECT 姓名, count(*) AS 人数
  3. FROM student
  4. GROUP BY 姓名
  5. HAVING count(*) >=2
  6. '''
  7. query(sql)
姓名 人数
0 猴子 2

5.4.4 查询不及格的课程并按课程号降序排列

  1. # 使用order by关键字进行排序
  2. sql = '''SELECT 课程号
  3. FROM score
  4. WHERE 成绩<60
  5. ORDER BY 课程号 DESC
  6. '''
  7. query(sql)
课程号
0 0003

5.4.5 查询每门课平均成绩,结果按平均升级升序、课程号降序排列

  1. # 使用order by关键字进行排序
  2. sql = '''SELECT 课程号, avg(成绩) AS 平均成绩
  3. FROM score
  4. GROUP BY 课程号
  5. ORDER BY avg(成绩) ASC, 课程号 DESC
  6. '''
  7. query(sql)
课程号 平均成绩
0 0003 70.000000
1 0002 76.666667
2 0001 80.000000

5.4.5 查询课程编号为“0004”且分数小于60的学生号,结果按分数降序排列

  1. # 使用order by关键字进行排序
  2. sql = '''SELECT 学号
  3. FROM score
  4. WHERE 课程号='0004' AND 成绩<60
  5. ORDER BY 成绩 DESC
  6. '''
  7. query(sql)
学号

5.4.6 查询每门课程的选修人数(超过2人的课程才统计)

  1. # 使用order by关键字进行排序
  2. sql = '''SELECT 课程号, count(*) AS 人数
  3. FROM score
  4. GROUP BY 课程号
  5. HAVING count(*)>=2
  6. ORDER BY count(*) DESC, 课程号 ASC
  7. '''
  8. query(sql)
课程号 人数
0 0002 3
1 0003 3
2 0001 2

5.5 复杂查询

5.5.1 查询所有课程成绩小于60分学生的学号和姓名

  1. # 使用子查询进行条件筛选
  2. sql = '''SELECT 学号, 姓名
  3. FROM student
  4. WHERE 学号 IN (SELECT 学号
  5. FROM score
  6. WHERE 成绩<60)
  7. '''
  8. query(sql)
学号 姓名
0 0001 猴子

5.5.2 查询所有没有学全所有课的学生的学号和姓名

  1. # 使用子查询进行条件筛选
  2. sql = '''SELECT 学号, 姓名
  3. FROM student
  4. WHERE 学号 IN (SELECT 学号
  5. FROM score
  6. GROUP BY 学号
  7. HAVING count(课程号)<(SELECT count(课程号)
  8. FROM course))
  9. '''
  10. query(sql)
学号 姓名
0 0001 猴子

5.5.3 查询只选修了两门课的学生的学号和姓名

  1. # 使用子查询进行条件筛选
  2. sql = '''SELECT 学号, 姓名
  3. FROM student
  4. WHERE 学号 IN (SELECT 学号
  5. FROM score
  6. GROUP BY 学号
  7. HAVING count(课程号)=2)
  8. '''
  9. query(sql)
学号 姓名
0 0002 猴子

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’)
结果:星期六
  1. # 使用日期函数进行筛选
  2. sql = '''SELECT 姓名
  3. FROM student
  4. WHERE year(出生日期)=1990
  5. '''
  6. query(sql)
课程号
0 猴子
1 王思聪

5.5.5 查询各科成绩前两名的记录

关联子查询解决TOPN问题
关联子查询内部逻辑

  1. # 用关联子查询来实现
  2. sql = '''(SELECT * FROM score WHERE 课程号='0001' ORDER BY 成绩 DESC LIMIT 2)
  3. UNION ALL
  4. (SELECT * FROM score WHERE 课程号='0002' ORDER BY 成绩 DESC LIMIT 2)
  5. UNION ALL
  6. (SELECT * FROM score WHERE 课程号='0003' ORDER BY 成绩 DESC LIMIT 2)
  7. '''
  8. 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 查询所有学生的学号、姓名、选课数和总成绩

  1. # 左连接student和score表,再用group by关键字进行分组
  2. sql = '''SELECT a.学号, a.姓名, count(b.课程号) AS 选课数, sum(b.成绩) AS 总成绩
  3. FROM student AS a
  4. LEFT JOIN score AS b
  5. ON a.学号=b.学号
  6. GROUP BY a.学号
  7. '''
  8. 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的所有学生的学号、姓名和平均成绩

  1. # 左连接student和score表,再用group by进行分组,最后用having进行筛选
  2. sql = '''SELECT a.学号, a.姓名, avg(b.成绩) AS 平均成绩
  3. FROM student AS a
  4. LEFT JOIN score AS b
  5. ON a.学号=b.学号
  6. GROUP BY a.学号
  7. HAVING avg(b.成绩)>70
  8. '''
  9. query(sql)
学号 姓名 平均成绩
0 0001 猴子 73.333333
1 0003 马云 80.000000

5.6.3 查询学生的选课情况:学号、姓名、课程号和课程名称

  1. # 先左连接score和course表,再用student表左连接上述结果表
  2. sql = '''SELECT a.学号, a.姓名, c.课程号, c.课程名称
  3. FROM student AS a
  4. LEFT JOIN score AS b
  5. ON a.学号=b.学号
  6. LEFT JOIN course AS c
  7. ON b.课程号=c.课程号
  8. '''
  9. 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 用法

  1. # 使用 case when then else来对分组后的小组进行筛选
  2. sql = '''SELECT 课程号,
  3. sum(CASE WHEN 成绩>=60 THEN 1 ELSE 0 END) AS 及格人数,
  4. sum(CASE WHEN 成绩<60 THEN 1 ELSE 0 END) AS 不及格人数
  5. FROM score
  6. GROUP BY 课程号
  7. '''
  8. query(sql)
课程号 及格人数 不及格人数
0 0001 2 0
1 0002 3 0
2 0003 2 1

5.6.5 使用分段来统计各科成绩

  1. # 左连接score和course表,再用group by进行分组,然后用case when进行筛选
  2. sql = '''SELECT a.课程号, b.课程名称,
  3. sum(CASE WHEN a.成绩 BETWEEN 85 AND 100 THEN 1 ELSE 0 END) AS '85分-100分',
  4. sum(CASE WHEN a.成绩 BETWEEN 70 AND 85 THEN 1 ELSE 0 END) AS '75分-85分',
  5. sum(CASE WHEN a.成绩 BETWEEN 60 AND 70 THEN 1 ELSE 0 END) AS '60分-70分',
  6. sum(CASE WHEN a.成绩<60 THEN 1 ELSE 0 END) AS '不及格'
  7. FROM score AS a
  8. LEFT JOIN course AS b
  9. ON a.课程号=b.课程号
  10. GROUP BY a.课程号
  11. '''
  12. 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包,将警告消除。