什么是表关系
我们在存储数据的时候,往往需要分成几张表来存储,因为如果只用一张表的话会导致大量的数据冗余,表的结构会很复杂且混乱,同时不便于我们的修改。
那么分表过后我们如何将两张表联系起来呢?这时候就需要建立表和表直接的关系,也就是所谓的表关系。
如何在物理上实现表的关联
答案是使用外键约束
表关系的分类
表关系一般有一下几种:
- 一对一:通过外键+对外键字段唯一约束
- 一对多:通过外键
- 多对多:通过建立第三张表,表中两个字段分别外键关联两张表的主键
什么是级联
虽然我们将两张表建立了联系,但是当我们修改主表后,必须得去修改从表来使得数据对应,而想要删除主表中的一行,必须先去删除从表中对应的那一行,这无疑是很不方便的,那么这时候就需要用到级联。
- 级联更新
主表更新时,从表同步更新 - 级联删除
主表删除时,从表同步删除
Mysql实现级联
下面我们将创建一个学生表和学生详情表,它们一一对应,并实现级联删除和级联更新。
创建数据库
create database school;
use school;
创建学生表
create table student(s_id int primary key auto_increment,s_name varchar(5) not null);
创建学生详情表
最后指定:
on update cascade
级联更新
on delete cascade
级联删除
create table details(id int primary key auto_increment,s_id int unique,sex enum("male","female"),age int,foreign key(s_id) references student(s_id) on update cascade on delete cascade);
创建好表之后我们就来进行测试
插入数据
#学生数据
insert into student value(null,"bob");
#学生详情数据
insert into details value(null,1,"male",20);
查看学生表
select * from student;
查看学生详情表
select * from details;
修改学生s_id,然后再查看学生详情表
update student set s_id=5 where s_id=1;
select * from details;
可以看到修改了主表后从表的数据也自动修改了,这就是级联更新。
删除学生,然后查看学生详情表
delete from student where s_id=5;
select * from details;
主表删除后从表对应的行也跟着删除了,这就是级联删除。
使用了级联后,我们的操作只需要聚焦到主表上,而不必过多关注从表。这极大地方便了我们的操作。
SQLAlchemy
可能有些朋友还是觉得表关系绕来绕去的有点复杂,而且在使用的时候数据库的表是一个二维表,它包含多行多列,可以用一个二维列表来表示一个表,但是用列表表示一行记录很难看出表的结构,那么如果把一张表用一个类来表示,就可以很容易得看出表的结构。
而怎么做到这个映射呢?
大名鼎鼎的ORM(Object-Relational Mapping)技术应运而生。
ORM把关系数据库的表结构映射到一个对象上。
在Python中,最著名的ORM框架就是SQLAlchemy了。
接下来看看SQLAlchemy的使用吧。
环境准备
pip install pymysql -i "https://pypi.doubanio.com/simple/"
pip install mysql-connector-python -i "https://pypi.doubanio.com/simple/"
pip install SQLalchemy -i "https://pypi.doubanio.com/simple/"
首先我们创建一个新的数据库用于测试
create database test;
connect_sql.py
:该文件会创建一个session对象,我们后续操作数据库就需要使用到session,该文件作为导入使用,不需要运行
# @File : connect_sql.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
HOSTNAME = '127.0.0.1' #填写主机IP
PORT = '3306' #填写端口号
DATABASE = 'test' #填写所要连接数据库名(已存在的数据库)
USERNAME = '' #用户名
PASSWORD = '' #密码
db_info = 'mysql+mysqlconnector://{}:{}@{}/{}?charset=utf8mb4'.format(
USERNAME,
PASSWORD,
HOSTNAME,
DATABASE
)
engine = create_engine(db_info)
Base = declarative_base(engine)
Session = sessionmaker(engine)
session = Session()
接下来我们会创建
一个学院表(d_id,d_name)
一个学生表(id,s_name,d_id)
一个学生详情表(id,s_id,sex,age)
一个课程表(c_id,c_name)
一个中间表(student_id,course_id)
一个学生只会有一个详情,它们是一对一关系。
一个学院有很多学生,每个学生只能是一个学院的,它们是一对多关系。
一个学生可以报名多个课程,一个课程也可以有多个学生,它们是多对多关系。
一个中间表用来映射学生课程的多对多关系。
table_rela.py
:该文件运行会创建表,并构建表关系,后续作为导入使用
# @File : table_rela.py
from connect_sql import Base
from sqlalchemy.orm import relationship
from sqlalchemy import Column, Integer, String, Enum, ForeignKey, Table
""""
ForeignKey("student.id",ondelete="RESTRICT")
ondelete可选参数:
RESTRICT(默认就是这种。当父表数据被删除,从表会拒绝删除)
CASCADE (父表数据删除、从表数据也会跟着删除)
SET NULL (父表数据删除,从表外键字段设为NULL)
"""
"""
details = relationship("Details",backref="student",cascade="save-update, merge, delete")
none:在保存,删除或修改当前对象时,不对其附属对象(关联对象)进行级联操作。它是默认值。
save-update:在保存,更新当前对象时,级联保存,更新附属对象(临时对象,游离对象)。
delete:在删除当前对象时,级联删除附属对象。
all:所有情况下均进行级联操作,即包含save-update和delete等等操作。
delete-orphan:删除此对象的同时删除与当前对象解除关系的孤儿对象(仅仅使用于一对多关联关系中)
"""
class Department(Base):
__tablename__ = "department"
d_id = Column(Integer,primary_key=True,autoincrement=True)
d_name = Column(String(20),nullable=False,index=True)
student = relationship("Student",backref="department") #建立ORM关系
def __repr__(self):
return "d_id:{}\nd_name:{}".format(self.d_id,self.d_name)
#中间表
stu_and_cou = Table("stu_and_cou",Base.metadata,
Column("student_id",Integer,ForeignKey("student.id")),
Column("course_id",Integer,ForeignKey("course.c_id")),
)
class Student(Base): #提交到数据库
__tablename__ = 'student' #表格名字
id = Column(Integer, primary_key=True, autoincrement=True, unique=True)
s_name = Column(String(20), nullable=False,index=True)
d_id = Column(Integer,ForeignKey("department.d_id",ondelete="SET NULL"))
details = relationship("Details",backref="student",cascade="save-update,merge,delete")
course = relationship("Course",secondary=stu_and_cou)
def __repr__(self):
return "id:{}\nname:{}\nd_id:{}".format(self.id,self.s_name,self.d_id)
class Details(Base):
__tablename__ = 'details' # 表格名字
id = Column(Integer,primary_key=True,autoincrement=True)
s_id = Column(Integer,ForeignKey("student.id",onupdate="CASCADE"),unique=True,nullable=False)
sex = Column(Enum("男","女"))
age = Column(Integer)
def __repr__(self):
return "id:{}\ns_id:{}\nsex:{}\nage:{}".format(self.id,self.s_id,self.sex,self.age)
class Course(Base):
__tablename__ = "course"
c_id = Column(Integer,primary_key=True,autoincrement=True)
c_name = Column(String(20),nullable=False,index=True)
student = relationship("Student",secondary=stu_and_cou)
def __repr__(self):
return "c_id:{},c_name:{}".format(self.c_id,self.c_name)
if __name__ == '__main__':
#创建表
Base.metadata.create_all()
运行后test数据库下出现了五张表
下面我们通过session对数据库进行操作
operator_sql.py
导入表和session对象
from connect_sql import session
from table_rela import Student,Department,Course,Details
一个学校首先得有学院和课程,下面我们来添加学院和课程
d1=Department(d_name="英语学院")
d2=Department(d_name="软件学院")
d3=Department(d_name="汉语学院")
c1=Course(c_name="专业英语")
c2=Course(c_name="英美文化")
c3=Course(c_name="c++程序设计")
c4=Course(c_name="linux基础")
c5=Course(c_name="文言文基础")
c6=Course(c_name="诗词鉴赏")
session.add_all([d1,d2,d3,c1,c2,c3,c4,c5,c6])
session.commit()
这时候一位学生考上了这所学校~
这是一位名字叫张三的男生,19岁,他选择了英语学院,同时报名了英美文化和专业英语课程。
# 一次全部添加
s=Student(s_name="张三")
s.details.append(Details(age=19,sex="男"))
c1=session.query(Course).get(1)
c2=session.query(Course).get(2)
s.course.extend([c1,c2])
d=session.query(Department).filter_by(d_name="英语学院").first()
d.student.append(s)
session.add(s)
session.commit()
然后一位叫李四的20岁女生也考上了学校,但是她还没有想好报名哪个学院哪个课程。
# 添加一个学生,包含详情信息
s = Student(s_name="李四")
d = Details(age=20,sex="女")
s.details.append(d)
session.add(s)
session.commit()
然后她也选择了英语学院,报名了专业英语
#添加学院
s = session.query(Student).filter_by(s_name="李四").first()
d = session.query(Department).filter_by(d_name="英语学院").first()
d.student.append(s)
# 添加课程
c = session.query(Course).filter_by(c_name="专业英语").first()
s.course.append(c) #或者 c.student.append(s)
session.commit()
一位21岁的名叫王五的男生,加入了汉语学院,并报名了诗词鉴赏。
s=Student(s_name="王五")
s.details.append(Details(age=21,sex="男"))
c=session.query(Course).filter_by(c_name="诗词鉴赏").first()
s.course.append(c)
d=session.query(Department).filter_by(d_name="汉语学院").first()
d.student.append(s)
session.add(s)
session.commit()
但是过了不久,诗词鉴赏这门课程被学校删除了,那么王五自然就没有选择的课程了。
c=session.query(Course).filter_by(c_name="诗词鉴赏").first()
session.delete(c)
session.commit()
可以看到删除课程后中间表中的内容同步修改了。
然后王五又报名了文言文基础
s = session.query(Student).filter_by(s_name="王五").first()
c = session.query(Course).filter_by(c_name="文言文基础").first()
c.student.append(s)
session.commit()
但是王五因为犯了大过,被学校退学了~
s = session.query(Student).filter_by(s_name="王五").first()
session.delete(s)
session.commit()
可以看到实现了级联删除,王五一旦退学,他的详情信息和报名课程信息也会级联删除。
这时候呢,英语学院也被删除了,那么张三和李四的学院id就会设置为NULL
d = session.query(Department).get(1)
session.delete(d)
session.commit()
日子还在继续,张三和李四转到了新的学院,也有很多同学进入了学校,他们一起幸福快乐地学习着~~
什么?你还想知道他们在一起了没?
这是姐弟恋啊!
虽然也挺好的(小声嘀咕…)
他们的幸福生活就由你们自己脑补吧~