一文搞懂表關係和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()

在這裏插入圖片描述
日子還在繼續,張三和李四轉到了新的學院,也有很多同學進入了學校,他們一起幸福快樂地學習着~~


什麼?你還想知道他們在一起了沒?
這是姐弟戀啊!
雖然也挺好的(小聲嘀咕…)
他們的幸福生活就由你們自己腦補吧~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章