MySQL多表查询与python操作MySQL
一、navicate
Navicate是一套可创建多个连接的数据库管理工具,用以方便管理 MySQL、Oracle、PostgreSQL、SQLite、SQL Server、MariaDB 和 MongoDB 等不同类型的数据库,它与阿里云、腾讯云、华为云、Amazon RDS、Amazon Aurora、Amazon Redshift、Microsoft Azure、Oracle Cloud 和 MongoDB Atlas等云数据库兼容。你可以创建、管理和维护数据库,可以充当上述数据库的客户端.主要用于MySQL
直接搜索官网下载即可但是是收费的,可以搞个破解版
1.操作
navicate里都是把mysql语句封装起来了 我们只需要鼠标点击就可以创建出库和表。输入数据的时候也很方便
nzvicate也可以编写mysql语句:
注释有三种:
-- 单行注释
# 单行注释
/* 多行注释*/
二、多表查询
1.数据准备
/* 数据导入: Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 50624 Source Host : localhost Source Database : sqlexam Target Server Type : MySQL Target Server Version : 50624 File Encoding : utf-8 Date: 10/21/2016 06:46:46 AM */ SET NAMES utf8; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for `class` -- ---------------------------- DROP TABLE IF EXISTS `class`; CREATE TABLE `class` ( `cid` int(11) NOT NULL AUTO_INCREMENT, `caption` varchar(32) NOT NULL, PRIMARY KEY (`cid`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of `class` -- ---------------------------- BEGIN; INSERT INTO `class` VALUES ('1', '三年二班'), ('2', '三年三班'), ('3', '一年二班'), ('4', '二年九班'); COMMIT; -- ---------------------------- -- Table structure for `course` -- ---------------------------- DROP TABLE IF EXISTS `course`; CREATE TABLE `course` ( `cid` int(11) NOT NULL AUTO_INCREMENT, `cname` varchar(32) NOT NULL, `teacher_id` int(11) NOT NULL, PRIMARY KEY (`cid`), KEY `fk_course_teacher` (`teacher_id`), CONSTRAINT `fk_course_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`tid`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of `course` -- ---------------------------- BEGIN; INSERT INTO `course` VALUES ('1', '生物', '1'), ('2', '物理', '2'), ('3', '体育', '3'), ('4', '美术', '2'); COMMIT; -- ---------------------------- -- Table structure for `score` -- ---------------------------- DROP TABLE IF EXISTS `score`; CREATE TABLE `score` ( `sid` int(11) NOT NULL AUTO_INCREMENT, `student_id` int(11) NOT NULL, `course_id` int(11) NOT NULL, `num` int(11) NOT NULL, PRIMARY KEY (`sid`), KEY `fk_score_student` (`student_id`), KEY `fk_score_course` (`course_id`), CONSTRAINT `fk_score_course` FOREIGN KEY (`course_id`) REFERENCES `course` (`cid`), CONSTRAINT `fk_score_student` FOREIGN KEY (`student_id`) REFERENCES `student` (`sid`) ) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of `score` -- ---------------------------- BEGIN; INSERT INTO `score` VALUES ('1', '1', '1', '10'), ('2', '1', '2', '9'), ('5', '1', '4', '66'), ('6', '2', '1', '8'), ('8', '2', '3', '68'), ('9', '2', '4', '99'), ('10', '3', '1', '77'), ('11', '3', '2', '66'), ('12', '3', '3', '87'), ('13', '3', '4', '99'), ('14', '4', '1', '79'), ('15', '4', '2', '11'), ('16', '4', '3', '67'), ('17', '4', '4', '100'), ('18', '5', '1', '79'), ('19', '5', '2', '11'), ('20', '5', '3', '67'), ('21', '5', '4', '100'), ('22', '6', '1', '9'), ('23', '6', '2', '100'), ('24', '6', '3', '67'), ('25', '6', '4', '100'), ('26', '7', '1', '9'), ('27', '7', '2', '100'), ('28', '7', '3', '67'), ('29', '7', '4', '88'), ('30', '8', '1', '9'), ('31', '8', '2', '100'), ('32', '8', '3', '67'), ('33', '8', '4', '88'), ('34', '9', '1', '91'), ('35', '9', '2', '88'), ('36', '9', '3', '67'), ('37', '9', '4', '22'), ('38', '10', '1', '90'), ('39', '10', '2', '77'), ('40', '10', '3', '43'), ('41', '10', '4', '87'), ('42', '11', '1', '90'), ('43', '11', '2', '77'), ('44', '11', '3', '43'), ('45', '11', '4', '87'), ('46', '12', '1', '90'), ('47', '12', '2', '77'), ('48', '12', '3', '43'), ('49', '12', '4', '87'), ('52', '13', '3', '87'); COMMIT; -- ---------------------------- -- Table structure for `student` -- ---------------------------- DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `sid` int(11) NOT NULL AUTO_INCREMENT, `gender` char(1) NOT NULL, `class_id` int(11) NOT NULL, `sname` varchar(32) NOT NULL, PRIMARY KEY (`sid`), KEY `fk_class` (`class_id`), CONSTRAINT `fk_class` FOREIGN KEY (`class_id`) REFERENCES `class` (`cid`) ) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of `student` -- ---------------------------- BEGIN; INSERT INTO `student` VALUES ('1', '男', '1', '理解'), ('2', '女', '1', '钢蛋'), ('3', '男', '1', '张三'), ('4', '男', '1', '张一'), ('5', '女', '1', '张二'), ('6', '男', '1', '张四'), ('7', '女', '2', '铁锤'), ('8', '男', '2', '李三'), ('9', '男', '2', '李一'), ('10', '女', '2', '李二'), ('11', '男', '2', '李四'), ('12', '女', '3', '如花'), ('13', '男', '3', '刘三'), ('14', '男', '3', '刘一'), ('15', '女', '3', '刘二'), ('16', '男', '3', '刘四'); COMMIT; -- ---------------------------- -- Table structure for `teacher` -- ---------------------------- DROP TABLE IF EXISTS `teacher`; CREATE TABLE `teacher` ( `tid` int(11) NOT NULL AUTO_INCREMENT, `tname` varchar(32) NOT NULL, PRIMARY KEY (`tid`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of `teacher` -- ---------------------------- BEGIN; INSERT INTO `teacher` VALUES ('1', '张磊老师'), ('2', '李平老师'), ('3', '刘海燕老师'), ('4', '朱云海老师'), ('5', '李杰老师'); COMMIT; SET FOREIGN_KEY_CHECKS = 1;
2.代码操作
在navicate写SQL语句时可以使用Reverse Database to Model 看着表的联系写
2.1查询所有的课程的名称以及对应的任课老师姓名
-- 1、查询所有的课程的名称以及对应的任课老师姓名 # 1.先看要准备几个表 课程表与老师表 # 2.简单看一下课程表和老师表 -- select * from course -- select * from teacher # 3.因为课程名称和老师名称在两个表所以我们可以链表操作 -- select * from course inner join teacher on course.cid = teacher.tid; +-----+--------+------------+-----+-----------------+ | cid | cname | teacher_id | tid | tname | +-----+--------+------------+-----+-----------------+ | 1 | 生物 | 1 | 1 | 张磊老师 | | 2 | 物理 | 2 | 2 | 李平老师 | | 3 | 体育 | 3 | 3 | 刘海燕老师 | | 4 | 美术 | 2 | 4 | 朱云海老师 | +-----+--------+------------+-----+-----------------+ # 3.1然后根据上述得出的表直接拿出课程名称和老师名称 SELECT teacher.tname, course.cname FROM course INNER JOIN teacher ON course.cid = teacher.tid; +-----------------+--------+ | tname | cname | +-----------------+--------+ | 张磊老师 | 生物 | | 李平老师 | 物理 | | 刘海燕老师 | 体育 | | 朱云海老师 | 美术 | +-----------------+--------+
2.2 查询平均成绩大于八十分的同学的姓名和平均成绩
2.查询平均成绩大于八十分的同学的姓名和平均成绩 # 1.我们还是先查看需要几张表 成绩表和学生表 # 2.简单看一下两张表 -- select * from score; -- select * from student # 3.我们可以先按照学生id分组然后计算平均成绩大于80的 -- select student_id,avg(num) as avg_num from score group by student_id having avg(num) > 80; # 把avg(num)改名方便后面取值 # 3.1上述结果有平均成绩但是学生姓名在学生表中 所以就要把两张表拼接起来 -- select * from student inner join (select student_id,avg(num) as avg_num from score group by student_id having avg(num) > 80) as t1 on t1.student_id = student.sid; # 表名也可以重命名 方便取值 +-----+--------+----------+--------+------------+---------+ | sid | gender | class_id | sname | student_id | avg_num | +-----+--------+----------+--------+------------+---------+ | 3 | 男 | 1 | 张三 | 3 | 82.2500 | | 13 | 男 | 3 | 刘三 | 13 | 87.0000 | +-----+--------+----------+--------+------------+---------+ # 3.2然后根据上述拼接的表取值 SELECT student.sname, t1.avg_num FROM student INNER JOIN ( SELECT student_id, avg( num ) AS avg_num FROM score GROUP BY student_id HAVING avg( num ) > 80 ) AS t1 ON t1.student_id = student.sid; +-----+--------+----------+--------+------------+---------+ | sid | gender | class_id | sname | student_id | avg_num | +-----+--------+----------+--------+------------+---------+ | 3 | 男 | 1 | 张三 | 3 | 82.2500 | | 13 | 男 | 3 | 刘三 | 13 | 87.0000 | +-----+--------+----------+--------+------------+---------+
2.3 查询没有报李平老师课的学生姓名
3.查询没有报李平老师课的学生姓名 # 1.我们可以知道需要用到老师表和课程表、成绩表、学生表 # 2.简单查看四个表 # 3.1我们可以先拿到李平老师的课程id号 -- select tid from teacher where tname = '李平老师' # 3.2然后在取课程表中拿到李平老师教授的课程 -- select cid from course where teacher_id = (select tid from teacher where tname = '李平老师') # 3.3那到课程id就可以去成绩表拿到学生id -- select * from score where score.course_id in (select cid from course where teacher_id = (select tid from teacher where tname = '李平老师')) # 3.4 因为有些学生是两门都报了所以只需要去重即可 -- select distinct student_id from score where score.course_id in (select cid from course where teacher_id = (select tid from teacher where tname = '李平老师')) # 3.5然后在学生表中获取学生姓名即可 取反 SELECT sname FROM student WHERE sid NOT IN ( SELECT DISTINCT student_id FROM score WHERE score.course_id IN ( SELECT cid FROM course WHERE teacher_id = ( SELECT tid FROM teacher WHERE tname = '李平老师' )))
2.4 查询没有同时选修物理课程和体育课程的学生姓名(只要选了其中一门的 两门都选和都没选的都不要)
4.查询没有同时选修物理课程和体育课程的学生姓名(只要选了其中一门的 两门都选和都没选的都不要) # 1.还是先看有几个表 课程表、成绩表、学生表 # 2.简单看一下这几个表 # 3.先查看物理和体育课程的id -- select cid from course where cname in ('物理','体育'); # 3.1然后在通过成绩表拿到学生id -- select * from score where course_id in (select cid from course where cname in ('物理','体育')) # 没有选择这两门课程的给去掉了 # 3.2基于上表操作查看每个学生的课程 -- select student_id from score where course_id in (select cid from course where cname in ('物理','体育')) group by student_id having count(course_id) = 1 # 这样就把选择两门的给去掉了 # 3.3 然后就可以去学生表那学生的姓名了 SELECT sname FROM student WHERE sid IN ( SELECT student_id FROM score WHERE course_id IN ( SELECT cid FROM course WHERE cname IN ( '物理', '体育' )) GROUP BY student_id HAVING count( course_id ) = 1 ); +--------+ | sname | +--------+ | 理解 | | 钢蛋 | | 刘三 | +--------+
2.5 查询挂科超过两门(包括两门)的学生姓名和班级
5.查询挂科超过两门(包括两门)的学生姓名和班级 # 1.还是先查看有几张表 成绩表、学生表、班级表 # 2.都先看下三张表里有啥 # 3.先去成绩表中拿到每个学生的成绩 -- select student_id from score where num < 60 group by student_id having count(course_id) >= 2 # 3.1然后因为学生表和班级表在两张表 所以把两张表链接 -- select * from class inner join student on class.cid = student.class_id # 3.2然后根据学生id拿到学生姓名和班级名称 SELECT class.caption, student.sname FROM class INNER JOIN student ON class.cid = student.class_id WHERE sid IN ( SELECT student_id FROM score WHERE num < 60 GROUP BY student_id HAVING count( course_id ) >= 2 ); +--------------+--------+ | caption | sname | +--------------+--------+ | 三年二班 | 理解 | +--------------+--------+
三、python操作MySQL
1.链接数据库
# 1.python想要查找MySQL数据库那么需要导入模块pymysql 我们可以在终端下载 pip3 install pymysql 也可以在pycahrm下载 # 2.操作 import pymysql # 1.链接数据库 conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='', #密码 database='db1', charset='utf8mb4' ) # 2.产生一个游标 cursor = conn.cursor() # 3.编写SQL语句 sql = 'select * from t1' # 4.发送给服务端 cursor.execute(sql) # 5.获取命令的结果 res = cursor.fetchall() print(res) # ((1, 'jason', 18), (2, 'tony', 28), (3, 'kevin', 26), (4, 'jerry', 24)) # 这样获取的结果有点不明确 我们可以让结果以字典显示 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 在括号内输入这行代码即可 # 3.编写SQL语句 sql = 'select * from t1' # 4.发送给服务端 cursor.execute(sql) # 5.获取命令的结果 res = cursor.fetchall() print(res) ''' [{'id': 1, 'name': 'jason', 'age': 18}, {'id': 2, 'name': 'tony', 'age': 28}, {'id': 3, 'name': 'kevin', 'age': 26}, {'id': 4, 'name': 'jerry', 'age': 24}] '''
'''这个时候只能产看数据 增删改都不能执行 因为 产看数据不会影响数据但是增删改都会修改数据 所以会有一个二次确认机制 我们可以在接入数据库的时候在写个参数 autocommit=True ''' import pymysql # 1.链接数据库 conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='密码', database='db1', charset='utf8mb4', autocommit=True # 执行增、改、删操作自动执行conn.commit # 这个写上的时候就能对数据库进行增删改的操作了 ) # 2.产生一个游标 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 3.编写SQL语句 sql = 'select * from t1' # 4.发送给服务端 cursor.execute(sql) # 5.获取命令的结果 res = cursor.fetchall() print(res)
2.获取结果
# 获取结果有三种 fetchall() fetchone() fetchmany() # 1.fetchall() 就是一次性获取全部 import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='密码', database='db1', charset='utf8mb4', autocommit=True # 执行增、改、删操作自动执行conn.commit # 这个写上的时候就能对数据库进行增删改的操作了 ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) sql = 'select * from t1' cursor.execute(sql) res = cursor.fetchall() # 因为第一次给取完了 然后光标就会停末尾 res1 = cursor.fetchall() # 所以第二次就没有值可以取了 所以是一个空列表 print(res) print(res1) ''' 获取结果: [{'id': 1, 'name': 'jason', 'age': 18}, {'id': 2, 'name': 'tony', 'age': 28}, {'id': 3, 'name': 'kevin', 'age': 26}, {'id': 4, 'name': 'jerry', 'age': 24}] [] ''' # fetchone() 就是一次只获取一个值 res = cursor.fetchone() # 一次就获取一个值 光标就会停在第一个值的末尾 res1 = cursor.fetchone() # 第二次就会在从这个光标开始取值 print(res) print(res1) ''' 获取结果: {'id': 1, 'name': 'jason', 'age': 18} {'id': 2, 'name': 'tony', 'age': 28} ''' # fetchmany() 就是一次可以传参按照参数的值取值 res = cursor.fetchmany(2) # 按照括号内的值取多少个值 光标就会停在取得值后面等待 res1 = cursor.fetchmany(1) print(res, res1) ''' 运行结果: [{'id': 1, 'name': 'jason', 'age': 18}, {'id': 2, 'name': 'tony', 'age': 28}] [{'id': 3, 'name': 'kevin', 'age': 26}] '''
3.scroll()
# 有点类似光标的移动 scroll可以传两个参数 第一个参数:移动的位数 整型即可 第二个参数:移动的模式 有两模式:relative、absolute # 1.relative '根据光标当前位置移动' res = cursor.fetchone() # 当我们取一个值的时候 光标就会停在第一个值的后面等待 cursor.scroll(2, mode="relative") # 然后scroll就会按照光标当前位置往后移动两个等待 res1 = cursor.fetchone() # 所以再次取值是在第三个的后面开始取值 print(res, res1) ''' 运行结果: {'id': 1, 'name': 'jason', 'age': 18} {'id': 4, 'name': 'jerry', 'age': 24} ''' # 2.absolute '基于数据集的开头移动' res = cursor.fetchone() # 当我们取一个值的时候 光标就会停在第一个值的后面等待 cursor.scroll(0,mode='absolute') # 然后scroll会基于数据集的开头往后移动0位 就是还在数据集的开头 res1 = cursor.fetchone() # 所以后续取值时还是从第一个开始 print(res, res1) ''' 运行结果: {'id': 1, 'name': 'jason', 'age': 18} {'id': 1, 'name': 'jason', 'age': 18} ''' res = cursor.fetchone() # 当我们取一个值的时候 光标就会停在第一个值的后面等待 cursor.scroll(3, mode='absolute') # 然后scroll会基于数据集的开头把光标往后移动3个 所以光标在第三个数据的后面等待 res1 = cursor.fetchone() # 所以后续取值时 是从第四个开始取值 print(res, res1) ''' 运行结果: {'id': 1, 'name': 'jason', 'age': 18} {'id': 4, 'name': 'jerry', 'age': 24} '''
四、注入问题
# 首先我们编写一个基于MySQL登入代码 # 2.产生一个游标 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 获取用户信息 username = input('username>>:').strip() password = input('password>>>:').strip() # 编写SQL语句 sql = "select * from userinfo where name='%s'and password='%s'" % (username, password) # 发送给服务端 cursor.execute(sql) # 获取命令的结果 res = cursor.fetchall() if res: print('登入成功') print(res) else: print('用户名或密码错误') ''' 正常输入是没有问题 但是如果我们这样输入: username>>:jason' -- dsadisa password>>>: 登入成功 [{'id': 1, 'name': 'jason', 'password': 123}] 还可以这样输入: username>>:xxx' or 1=1 -- dsadasds password>>>: 登入成功 [{'id': 1, 'name': 'jason', 'password': 123}, {'id': 2, 'name': 'kevin', 'password': 321}, {'id': 3, 'name': 'tony', 'password': 741}] 都没输对都可以登入成功 这样肯定是不行 '''
当我们在navicate查看代码时就可以知道 -- 是注释语句 所以where的条件就会变成了true 当where后面的条件变成true的时候就等于没有条件 就会直接执行前面的SQL语句
这就是SQL注入问题
1.解决办法
# 当我们处理敏感数据的时候我们不要自己处理 交给pymysql处理 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 获取用户信息 username = input('username>>:').strip() password = input('password>>>:').strip() # 编写SQL语句 sql = "select * from userinfo where name=%s and password=%s" # 发送给服务端 cursor.execute(sql, (username, password)) # 自动识别%s 并自动过滤各种特殊符号 最后合并数据 # 获取命令的结果 res = cursor.fetchall() if res: print('登入成功') else: print('用户名或密码错误') # 这样就没有问题了
2.插入数据
# 当我们想一次性插入多个数据 可以使用cursor.executemany方法 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) sql = 'insert into userinfo(name,pwd) values(%s,%s)' cursor.executemany(sql,[('jason1',123),('jason2',321),('jason3',222)]) # 可以使用列表套元祖的形式插入多个值 +----+--------+----------+ | id | name | password | +----+--------+----------+ | 1 | jason | 123 | | 2 | kevin | 321 | | 3 | tony | 741 | | 4 | jason1 | 123 | | 5 | jason2 | 321 | | 6 | jason3 | 222 | +----+--------+----------+
3.补充小知识点
1.as语法 给字段起别名、起表名 2.comment语法 给表、字段添加注释信息 create table server(id int) comment '这个server意思是服务器表' create table t1( id int comment '用户编号', name varchar(16) comment '用户名' ) comment '用户表'; """ 查看注释的地方 show create table use information_schema """ 3.concat、concat_ws语法 concat用于分组之前多个字段数据的拼接 select concat (name,':',password) from userinfo; +----------------------------+ | concat (name,':',password) | +----------------------------+ | jason:123 | | kevin:321 | | tony:741 | | jason1:123 | | jason2:321 | | jason3:222 | +----------------------------+ concat_ws如果有多个字段 并且分隔符一致 可以使用该方法减少代码 select concat_ws(':',id,name,password) from userinfo; +---------------------------------+ | concat_ws(':',id,name,password) | +---------------------------------+ | 1:jason:123 | | 2:kevin:321 | | 3:tony:741 | | 4:jason1:123 | | 5:jason2:321 | | 6:jason3:222 | +---------------------------------+ 4.exists语法 select * from userinfo where exists (select * from department where id<100) exists后面的sql语句如果有结果那么执行前面的sql语句 如果没有结果则不执行
五、作业
1.利用pymysql编写用户注册登录功能
import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='591080698', database='db11', charset='utf8mb4', autocommit=True # 执行增、改、删操作自动执行conn.commit # 这个写上的时候就能对数据库进行增删改的操作了 ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) def register(): username = input('username>>>:').strip() password = input('password>>>:').strip() sql = 'select * from user where name=%s ' cursor.execute(sql, (username,)) res = cursor.fetchall() if res: print('用户已存在') return sql1 = 'insert into user(name,pwd) values(%s,%s)' cursor.execute(sql1, (username, password)) print('用户注册成功') def login(): username = input('username>>>:').strip() password = input('password>>>:').strip() sql = 'select * from user where name=%s and pwd=%s' cursor.execute(sql, (username, password)) res = cursor.fetchall() if res: print('登入成功') return print('用户名或密码错误') func_dict = { '1': register, '2': login } def run(): while True: print(""" 1.注册 2.登入 """) choice = input('请输入功能编号>>>:') if not choice.isdigit(): print('功能编号必须是纯数字') if choice in func_dict: func_dict[choice]() else: print('功能编号超出范围') run()