上周内容回顾
-
函数简介
函数就是工具
-
函数的返回值
return
-
函数的参数
位置参数
默认参数
可变长参数(*args,**kwargs)
* ** 在形参中用于接收多余的位置参数和关键字参数然后分别组织成元组和字典赋值给后面的变量名 在实参中用于将列表和字典分别打散成位置参数和关键字参数传入 *[1,2,3,4,5] 1,2,3,4,5 **{'username':'jason','password':123} username='jason',password=123
-
函数对象
函数名可以用多种用途
-
名称空间与作用域
名称空间就是用来存放变量名与变量值关系绑定的地方 内置 全局 局部 函数内部 全局作用域 内置、全局 局部作用域 局部
-
装饰器
""" 能够在不改变被装饰对象 1.源代码 2.调用方式 给被装饰对象添加新的功能 """ def outer(func): def inner(*args,**kwargs): # 执行被装饰对象之前你可以做的操作 res = func(*args,**kwargs) # 执行被装饰对象之后你可以做的操作 return res return inner @outer # home = outer(home) def home(): print('from home') home()
-
三元表达式、列表生成式、生成器表达式
x = 10 y = 100 res = x if x > y else y res = [i for i in range(10)] res = (i for i in range(10)) # 节省内存空间
-
常用内置函数
不需要强行记忆、用的多了你也就会了
-
模块
""" 在python有很多看似复杂的功能 其实都不需要我们自己书写 而是都有现成的别人已经写好的模块 你只需要直接下载导入使用即可 """ import 模块名 from 模块名 import 功能名 # 下载第三方模块 pip3 install django time datetime os sys random 随机模块 利用random生成验证码 json 序列化数据(为了能够实现前后端交互) hashlib 加密模块 加盐处理 动态加盐 logging 日志模块 re 正则表达式
本周内容概要
-
面向对象
-
网络编程
-
并发编程
-
异常处理
-
MySQL数据库
select from where group by having distinct order by limit ...
今日内容详细
面向对象
"""小猿取经博客园:xiaoyuanqujing@666"""
# 1.什么是对象?
对象其实就是数据和功能的结合体
# 2.为什么要用对象
面向对象编程就是要造出一个个的对象,把原本分散开的相关数据与功能'整合'到一个个的对象里,这么做既方便使用,也可以提高程序的解耦合程度,进而提升了程序的可扩展性
类的概念
"""类其实就是对象公共的数据和功能的结合体"""
我们可以把'同一类对象相同的数据与功能存放到类里',而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间。所以,如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器。
类与对象的关系
在程序中必须要'先'有类
然后'才能'利用类产生对象
类的定义与实例化
# 以开发清华大学选课系统
1.先分析学生角色
# 学生的数据有
学校
名字
年龄
性别
# 学生的功能有
选课
2.模拟出多个学生('对象')
# 学生1:
数据:
学校=清华大学
姓名=李建刚
性别=男
年龄=28
功能:
选课
# 学生2:
数据:
学校=清华大学
姓名=王大力
性别=女
年龄=18
功能:
选课
# 学生3:
数据:
学校=清华大学
姓名=牛嗷嗷
性别=男
年龄=38
功能:
选课
3.抽取多个学生公共的数据和功能('类')
数据:
学校=清华大学
功能:
选课
类的定义
1.'定义函数'需要用到关键字def
2.'定义类'需要用到关键字class
在类中定义的函数不叫函数 叫'方法'
类的代码在定义阶段就会执行,因此会产生新的名称空间(类名称空间),来存放类中定义的名值对的绑定关系
class Student: # 1.类的命名(首字母必须大写)
school='清华大学' # 数据
def choose(self): # 功能
print('%s is choosing a course' %self.name)
print(Student.__dict__) # 查看类的数据和功能
对象的创建
#对象
对象就是一个盛放数据与功能的容器 箱子 盒子
面向对象编程就是要造出一个个的对象,把原本分散开的相关数据与功能整合在一起,也就是一个个对象里
#类
类就是一个盛放'多个对象'相同数据与功能的容器
类的返回值就是对象,实例
# 类产生对象只需要类名加括号调用(实例化),即可产生对象
class Student:
school = '清华大学'
def choose(self):
print('正在选课')
obj1 = Student()
obj2 = Student()
obj3 = Student()
# 查看类名称空间中的名字,每次调用,内存地址改变(通过id可以查看名值对在内存空间中的地址)
print(Student.__dict__)
print(id(obj1))
print(id(Student))
{'__module__': '__main__', 'school': '清华大学', 'choose': , '__dict__': , '__weakref__': , '__doc__': None}
1908196892176
1908164725128
# print(id(obj1),id(obj2),id(obj3))
1938230058120 1938230182688 1938230182800
# 访问数据和方法统一采用句点符,查看生成的对象公共的数据
print(obj1.school)
print(obj2.school)
print(obj3.school)
清华大学
清华大学
清华大学
"""
类名只要加括号就会生成对象 并且每一次生成的都不一样(内存地址不一样)
都是不同的对象
类产生的所有的对象都可以访问到类里面的数据和方法
统一采用句点符访问
类跟函数类似,都有定义阶段和调用阶段
"""
# 每个对象除了可以有公共的属性和方法,还应该独有数据和方法
class Student:
school = '清华大学'
# 该方法会在对象产生之后自动执行,专门为对象进行初始化操作(让对象拥有自己独有的属性),在类中定义了对象的独有属性之后,对象的生成必须加上该属性
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def choose(self):
print('正在选课')
obj1 = Student('jason',18,'male')
obj2 = Student('frank',73,'female')
obj3 = Student('haoda',35,'male')
# 访问公共的属性和方法
print(obj1.school)
print(obj2.school)
print(obj3.school)
#查看类名称空间(只能查看到公共的属性)
print(obj1.__dict__)
print(obj2.__dict__)
print(obj3.__dict__)
#访问类中独有的数据和方法,只能通过变量来访问,对象独有的属性和方法
print(obj1.name,obj1.age,obj1.gender) # 访问自己独有的属性jason 18 male
print(obj2.name,obj2.age,obj2.gender) # 访问自己独有的属性frank 73 female
print(obj3.name,obj3.age,obj3.gender) # 访问自己独有的属性haoda 35 male
属性查找
class Student:
school = '清华大学'
def __init__(self,name,school):
self.name = name
self.school = school
def choose(self):
print('正在选课')
obj1 = Student('jason','家里蹲大学')
obj2 = Student('frank','没钱上大学')
#查看类结构,定义后的类结构
print(Student.__dict__)
print(obj1.__dict__)
print(obj2.__dict__)
print(obj1.school)
'''当对象和类里面都有一个相同的属性的时候 对象在查找的时候优先用自己'''
#查看内存地址
print(obj1.choose)
print(obj2.choose)
# 需要自己传参数
print(Student.choose(obj2))
#不需要自己穿参数
obj1.choose()
obj2.choose()
"""
定义在类里面的函数默认都是绑定给对象的(即'对象来调用'会自动将对象当做第一个参数传入)
而类在调用的时候则需要自己手动传入参数
"""
# 类的调用方法小结
class Student:
school = '清华大学'
# 该方法会在对象产生之后自动执行,专门为对象进行初始化操作(让对象拥有自己独有的属性)
def __init__(self,name,age,gender,school):
self.name = name
self.age = age
self.gender = gender
self.school = school
def choose(self):
print('正在选课')
obj1 = Student('jason',18,'male','a')
obj2 = Student('frank',73,'female','b')
obj3 = Student('haoda',35,'male','c')
obj4 = Student('syy',18,'male','d')
#查看类名称空间在内存中的地址
print(id(Student))
#查看变量在内存中的地址
print(id(obj1))
#类的公共数据
print(Student.school)
#查看类结构
print(Student.__dict__)
#查看定义数据后,类结构
print(obj1.__dict__)
#查看类公共数据
print(obj1.school)
#查看类功能的内存地址
print(Student.choose)
#查看独有属性数据
print(obj1.school)
#查看类功能(不需要自己传入参数)
print(obj1.choose())
1694126954536
1694161177680
清华大学
{'__module__': '__main__', 'school': '清华大学', '__init__': , 'choose': , '__dict__': , '__weakref__': , '__doc__': None}
{'name': 'jason', 'age': 18, 'gender': 'male', 'school': 'a'}
清华大学
a
正在选课
Python中一切皆为对象
s = 'hello world'
res=s.split()
l = [1,2,3,4]
l.append('233')
d = {'username':'jason'}
dd=d.get('username')
print(l)
print(res)
print(dd)
[1, 2, 3, 4, '233']
['hello', 'world']
jason
面向对象三大特性
面向对象编程有三大特性:封装 继承 多态
封装:把数据与功能整合到一起
# 封装
封装指的就是把数据与功能都'整合'到一起
并且针对封装了的数据和功能我还可以'控制'他们被访问的方式
# 隐藏属性
python的class中,采用'双下划线'将属性'隐藏'起来,是一种变形操作,
类中所有的双下划线的属性都会在类的调用时自动变成'_类名__属性名'
class Student:
school = '清华大学'
__info = '我没考上'
def index(self):
print('from index')
def __func(self):
print('from __func')
obj = Student()
#查看匿名对象
print(obj._Student__info)
#查看匿名功能
print(obj._Student__func)
#查看私有 公共的属性
print(obj.school)
print(obj._Student__info)
#查看私有属性空间地址
print(obj.index)
#查看类结构
print(Student.__dict__)
我没考上
>
清华大学
我没考上
>
{'__module__': '__main__', 'school': '清华大学', '_Student__info': '我没考上', 'index': , '_Student__func': , '__dict__': , '__weakref__': , '__doc__': None}
隐藏的目的是为了让用户不能直接访问而是需要通过我们提供的'接口''间接'访问
我们可以在接口之上加上一些'额外的操作'
class Teacher:
def __init__(self,name,age): #将名字和年纪都隐藏起来
self.__name=name
self.__age=age
def tell_info(self): #对外提供访问老师信息的接口
print('姓名:%s,年龄:%s' %(self.__name,self.__age))
def set_info(self,name,age): #对外提供设置老师信息的接口,并附加类型检查的逻辑
if not isinstance(name,str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name=name
self.__age=age
obj = Teacher('jason',18)
#访问对象的属性
obj.tell_info()
print(obj.tell_info())
姓名:jason,年龄:18
姓名:jason,年龄:18
None
"""
访问对象的属性 用对象.属性
修改对象的属性
对象.属性 = 新的值
obj1.name = 'frank'
单独的隐藏没有任何意义,隐藏属性对外提供2个接口,通过添加接口,来添加限制条件
"""
class Teacher:
def __init__(self,name,age): #将名字和年纪都隐藏起来
self.__name=name
self.__age=age
def tell_info(self): #对外提供访问老师信息的接口
print('姓名:%s,年龄:%s' %(self.__name,self.__age))
def set_info(self,name,age): #对外提供设置老师信息的接口,并附加类型检查的逻辑
if not isinstance(name,str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name=name
self.__age=age
obj = Teacher('jason',18)
#修改对象的属性
obj.name = 'frank' # 无效
obj.set_info('frank',40) #生效
obj.set_info(40,'hahaha') #报错
obj.tell_info()
#该装饰器的作用是 将方法伪装成一个属性 用户在访问的时候不需要加括号
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
@property #
def bmi(self):
return self.weight / (self.height ** 2)
obj1 = People('jason',75,1.83)
obj2 = People('frank', 80, 1.73)
# res = obj1.bmi()
# print(res)
print(obj2.name)
print(obj1.bmi)
frank
22.395413419331717
继承
在python中一个类可以继承单个或者多个类
1.继承的类称之为子类
2.被继承的类称之为父类、基类、超类
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承
pass
class SubClass2(ParentClass1,ParentClass2): #多继承
pass
# 如何查看类的父类?
print(SubClass1.__bases__) # (,)
print(SubClass2.__bases__) # (, )
print(ParentClass1.__bases__) # (,)
print(ParentClass2.__bases__) # (,)
"""
在python3中
如果一个类没有继承任何的父类 那么默认都是继承object
在python2中
如果一个类没有继承任何的父类 那么就是什么都不继承
经典类
不继承任何的类 只有在python2中才有
新式类
继承object或者其子类的类 在python3中只有新式类
"""
# 在定义类的时候为了兼容python2和python3
class MyClass(object):
pass
父类的概念
'对象'是数据和功能的结合体
'类'是对象公共的数据和功能的结合体
'父类'是类的公共的数据和功能的结合体
class People:
school = '清华大学'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
class Student(People):
def choose(self):
print('%s is choosing a course' % self.name)
class Teacher(People):
def teach(self):
print('%s is teaching' % self.name)
# print(Student.choose)
# print(Teacher.teach)
print(Student.school)
print(Teacher.school)
清华大学
清华大学
属性查找固定规律
1.先从对象自身找
2.再去产生对象的类中找
3.再去类的父类中找
4.报错
class P:
# name = 'jason'
pass
class S(P):
# name = 'frank'
pass
obj = S()
# obj.name = 'haoda'
print(obj.name)
调用父类的方法
class People:
school = '清华大学'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
class Student(People):
# 继承了父类的方法之后 还想再扩展,super
def __init__(self,name,age,sex,hobby):
super().__init__(name,age,sex) # 调用父类的方法
self.hobby = hobby
def choose(self):
print('%s is choosing a course' % self.name)
class Teacher(People):
# 继承了父类的方法之后 还想再扩展
def __init__(self, name, age, sex, level):
super().__init__(name, age, sex) # 调用父类的方法
self.level = level
def teach(self):
print('%s is teaching' % self.name)
stu = Student('frank','female',40,'read')
tea = Teacher('jason','male',18,'金牌讲师')
print(stu.name,stu.hobby)
print(tea.name,tea.level)
多态
一种事物的多种形态
只要是一种事物那么你就可以不需要考虑该事物到底那种形态
直接调用统一的方法
比如
无论是猫、狗、猪都有叫的功能
talk()
len('jason')
len([1,2,3,4])
len({'username':'jason'})
多态性的好处在于增强了程序的灵活性和可扩展性,
比如通过继承Animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.talk()
鸭子类型
只要你看上去像鸭子、走路像鸭子、说话像鸭子那么你就是鸭子
class Txt: #Txt类有两个与文件类型同名的方法,即read和write
def read(self):
pass
def write(self):
pass
class Disk: #Disk类也有两个与文件类型同名的方法:read和write
def read(self):
pass
def write(self):
pass
绑定方法与非绑定方法
在类中定义的函数默认是'绑定给对象的'(对象来调会'自动'将对象当做第一个参数传入)
class MyClass(object):
# 绑定给对象的
def index(self):
print('from index',self)
# 普普通通的函数
@staticmethod
def func(name):
print('from func',name)
# 绑定给类的
@classmethod
def bar(cls):
print('from bar',cls)
obj = MyClass()
obj.index() # from index <__main__.MyClass object at 0x000001C1DB4FC2E8>
print(obj) # <__main__.MyClass object at 0x000001C1DB4FC2E8>
# obj.func()
# MyClass.func()
obj.func('jason') # 都需要传参数
MyClass.func('egon')
obj.bar() #不需要传参
MyClass.bar()
from index <__main__.MyClass object at 0x000001C5D25FDE10>
<__main__.MyClass object at 0x000001C5D25FDE10>
from func jason
from func egon
from bar
from bar
'''
绑定给类的方法 类来调'自动'将类传入
类的对象来调则会将产生对象的类传入
'''
反射
'''利用字符串来操作对象的属性或方法'''
class MyClass(object):
province = '上海'
name = 'jason'
def index(self):
pass
# 你手上只有一个字符串的'province'判断类里面有没有对应的属性或者方法
res = hasattr(MyClass,'province')
print(res)
res1 = hasattr(MyClass,'age')
print(res1)
res2 = hasattr(MyClass,'index')
print(res2)
True
False
True
# 获取对象中的值
ret = getattr(MyClass,'province')
print(ret)
ret1 = getattr(MyClass,'index')
print(ret1)
# ret2 = getattr(MyClass,'gender')
# print(ret2)
上海
#小游戏
class MyClass(object):
province = '上海'
name = 'jason'
def index(self):
pass
# 猜属性和方法的小游戏
while True:
cai = input('请输入MyClass内属性或方法>>>:')
if hasattr(MyClass,cai):
res = getattr(MyClass,cai)
print(res)
else:
print('没有该属性或者方法')
import myfile
print(hasattr(myfile,'name'))
print(hasattr(myfile,'heihei'))
print(getattr(myfile,'name'))
请输入MyClass内属性或方法>>>:name
jason
请输入MyClass内属性或方法>>>:province
上海
请输入MyClass内属性或方法>>>:index
请输入MyClass内属性或方法>>>:age
没有该属性或者方法
异常处理
1.语法错误
不能被允许
2.逻辑错误
可以被允许
try:
被检测的代码块
except 异常类型:
检测到异常,就执行这个位置的逻辑
#异常测试
try:
l = [1,23]
l[100]
# except SyntaxError:
# print('语法错误')
except IndexError as e:
print(e) # 内部自动定义的报错信息
print('索引超出范围') # 自己自定制的报错信息
except NameError:
print('名字错误')
except TypeError:
print('类型错误')
list index out of range
索引超出范围
# 万能异常
try:
# name
# 1+'2'
l = [1,3,4]
l[100]
except Exception as e: # 能够接受任意类型的错误并处理
print(e)
"""
异常处理虽然好用,但是我们要尽可能少的使用它
try代码块里面的被监控的代码应该越少越好
"""
网络编程
1.OSI七层
应用层
表示层
会话层
传输层
网络层 ip协议
数据链路层 规定电信号的分组方式,解析二进制数 ARP协议(解析Mac地址)
物理连接层 网卡 网线
2.精简为五层
应用层
传输层
网络层
数据链路层
物理连接层
1.滚筒式电话
2.大屁股电脑
3.笔记本电脑
#网卡的作用就是取代网线,连接WiFi,连接路由器
#交换机的作用就是集中接口
#如果基于Mac地址通信的话会产生网络风暴
套接字通信(socket)
任何基于网络通信的代码都是使用socket模块写出来的
# c/s架构的程序
'''需要你掌握的代码'''
import socket
"""
服务端需要满足的三个条件
1.必须得有固定的ip和port
2.24小时不间断提供服务
3.支持高并发
"""
# 生成一个通信对象
server = socket.socket() # 默认走的是TCP协议
# 要给服务端绑定ip和port
server.bind(('127.0.0.1',8080))
# 半连接池(缓冲)
server.listen(5)
# 监听(承上启下 )
conn, addr = server.accept()
# 跟客户端交互
res = conn.recv(1024) # 接收客户端发来的消息
print(res.decode('utf-8'))
# 关闭连接
conn.close()
客户端
import socket
client = socket.socket()
# 连接服务端
client.connect(('127.0.0.1',8080))
client.send('快点 等不及了'.encode('utf-8'))
client.close()
聊天功能
服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn, addr = server.accept()
while True:
res = conn.recv(1204)
print(res.decode('utf-8'))
cmd = input('>>>:')
conn.send(cmd.encode('utf-8'))
客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
res = input('>>>:')
client.send(res.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
并发编程
"""
研究并发编程其实就是研究计算机的'发展史'及'计算机硬件'
计算机真正干活的硬件是CPU
"""
进程,正在运行的程序
重要概念介绍
并发与并行
1.并发 '切换+保存状态'
看起来像同时在运行就可以称之为是并发
只有一个cpu多个任务的情况下也可以实现并发效果
当一个任务进入IO操作之后操作系统会自动将cpu拿走给其他任务用,单个cpu在多个任务之间'快速切换'
2.并行
多个任务'同时执行'(必须有多个cpu)
同步与异步
1.同步
提交任务之后原地等待任务的返回结果'期间不做任何事情'
2.异步
提交任务之后不愿地等待任务的返回结果'直接执行下一行代码'
进程的三状态图
1.任何程序要想被执行 都必须先进入'就绪态'
2.处于'运行态'的程序一旦遇到IO操作会立刻丧失CPU的控制权进入'阻塞态'
阻塞
阻塞态
非阻塞
就绪态、运行态
如何开启进程
1.鼠标双击一个应用图标
2.代码来创建进程
from multiprocessing import Process
import time
def index(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
# 在windows中创建进程的代码必须放到__main__下面,if条件下的代码只有在当前文件是被执行的时候才会执行,如果是当做模块导入,则不会执行
if __name__ == '__main__':
p = Process(target=index,args=('frank',))
p.start() # 告诉操作系统创建一个进程
print('主')
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
super().__init__() # 这句话不能少
self.name = name
def run(self):
print('%s is running'%self.name)
time.sleep(3)
print('%s is over'%self.name)
if __name__ == '__main__':
obj = MyProcess('frank')
obj.start()
print('主')
线程
"""
进程其实是资源单位
线程才是真正的执行单位
每个进程下默认都会自带一个线程
如果将内存比喻成工厂
那么进程就是类似于工厂里面的一个个车间
线程就类似于是车间里面的流水线
同一个车间内多个流水线可以共享资源即
同一个进程下的多个线程数据是共享的
多个进程之间数据默认是不共享的
创建进程的开销要远远大于创建线程的开销
创建进程
1.需要申请内存空间
2.拷贝代码
创建线程
上述两部都可以省略
"""
from threading import Thread
import time
def index(name):
print('%s is running'%name)
time.sleep(3)
print('%s is over'%name)
t1 = Thread(target=index,args=('frank',))
t1.start()
print('主')
from threading import Thread
import time
class MyThread(Thread):
def run(self):
print('running')
time.sleep(3)
print('over')
obj = MyThread()
obj.start()
print('主')
多进程还是多线程好
"""
一般情况下我们都是
开设多进程然后在每个进程内再开设多线程
实现效率的最大化
"""
MySQL数据库
navicat简单使用
表与表之间关系
一对多
多对多
一对一
一对多关系
员工表和部门表
多对多关系
图书表和作者表
一对一关系
QQ用户和用户详情
MySQL查询练习题
-- 1、查询所有的课程的名称以及对应的任课老师姓名
SELECT course.cname,teacher.tname FROM course INNER JOIN teacher ON course.teacher_id=teacher.tid
-- SELECT
-- course.cname,
-- teacher.tname
-- FROM
-- course
-- INNER JOIN teacher ON course.teacher_id = teacher.tid;
-- 4、查询平均成绩大于八十分的同学的姓名和平均成绩
-- 先去score表中求出平均成绩大于80分的学生的id号
-- 再去学生表中根据获取到的id号筛选出学生的姓名
SELECT student.sname,t1.score_num FROM student INNER JOIN
(SELECT score.student_id,avg(score.num) as score_num FROM score GROUP BY score.student_id HAVING avg(score.num) > 80) as t1 on student.sid = t1.student_id;
-- SELECT
-- student.sname,
-- t1.avg_num
-- FROM
-- student
-- INNER JOIN (
-- SELECT
-- score.student_id,
-- avg( score.num ) AS avg_num
-- FROM
-- score
-- GROUP BY
-- score.student_id
-- HAVING
-- avg( score.num ) > 80
-- ) AS t1 ON student.sid = t1.student_id;
-- 7、 查询没有报李平老师课的学生姓名
-- 整体思路 查询报了李平老师课程的学生id 然后取反
-- 1.先获取李平老师教授的课程id号
-- 2.根据课程id号去分数表里面获取学生id号
-- 3.最后去学生表里面取反获取没有报李平老师课程的学生姓名
-- SELECT
-- student.sname
-- FROM
-- student
-- WHERE
-- student.sid NOT IN (
-- SELECT DISTINCT
-- score.student_id
-- FROM
-- score
-- WHERE
-- score.course_id IN ( SELECT course.cid FROM course INNER JOIN teacher ON course.teacher_id = teacher.tid WHERE teacher.tname = '李平老师' )
-- );
--
-- 8、 查询没有同时选修物理课程和体育课程的学生姓名
-- 整体思路 先拿到所有报了物理或者体育的学生id
-- 1.先获取物理课程和体育课程id号
-- 2.去分数表里面过滤出所有选了id号的学生id
-- 3.按照学生id分组 筛选掉报了两门的数据
-- SELECT
-- student.sname
-- FROM
-- student
-- WHERE
-- student.sid IN (
-- SELECT
-- score.student_id
-- FROM
-- score
-- WHERE
-- score.course_id IN ( SELECT course.cid FROM course WHERE course.cname IN ( '物理', '体育' ) )
-- GROUP BY
-- score.student_id
-- HAVING
-- count( score.course_id ) = 1
-- );
--
-- 9、查询挂科超过两门(包括两门)的学生姓名和班级
-- 1.先找出所有小于60分的数据
-- 2.根据学生id分组 利用count筛选
SELECT
student.sname,
class.caption
FROM
class
INNER JOIN student ON class.cid = student.class_id
WHERE
student.sid IN (
SELECT
score.student_id
FROM
score
WHERE
score.num < 60 GROUP BY score.student_id HAVING count( score.course_id ) >= 2
);