一文搞懂表关系和ORM

什么是表关系

我们在存储数据的时候,往往需要分成几张表来存储,因为如果只用一张表的话会导致大量的数据冗余,表的结构会很复杂且混乱,同时不便于我们的修改。
那么分表过后我们如何将两张表联系起来呢?这时候就需要建立表和表直接的关系,也就是所谓的表关系。
如何在物理上实现表的关联
答案是使用外键约束

表关系的分类

表关系一般有一下几种:

  • 一对一:通过外键+对外键字段唯一约束
  • 一对多:通过外键
  • 多对多:通过建立第三张表,表中两个字段分别外键关联两张表的主键

什么是级联

虽然我们将两张表建立了联系,但是当我们修改主表后,必须得去修改从表来使得数据对应,而想要删除主表中的一行,必须先去删除从表中对应的那一行,这无疑是很不方便的,那么这时候就需要用到级联。

  1. 级联更新
    主表更新时,从表同步更新
  2. 级联删除
    主表删除时,从表同步删除

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

在这里插入图片描述
日子还在继续,张三和李四转到了新的学院,也有很多同学进入了学校,他们一起幸福快乐地学习着~~


什么?你还想知道他们在一起了没?
这是姐弟恋啊!
虽然也挺好的(小声嘀咕…)
他们的幸福生活就由你们自己脑补吧~

猜你喜欢

转载自blog.csdn.net/weixin_42494845/article/details/106276437