Skip to content

多表关系 - 一对多


简介

在 SQLAlchemy 中,一对多(One-to-Many)关系是一种常见的数据库关系模型,其中一个表的每个记录(行)对应于另一个表中多个记录。

实际上,建立多表关联关系的本质是要通过关联关系建立额外的信息查询。比如在 SQL 语句中经常要用到 join 进行关联查询,那么在 ORM 中就可以使用多表关系,使查询更加优雅。


一对多场景

在现实世界的数据模型中,经常会遇到一个实体与多个相关实体之间的关系。

使用一对多关系有助于更好地组织和存储数据,使数据模型更贴近实际情况。这种关系可以减少数据冗余,提高数据的一致性,并支持更高级的查询和分析。一对多关系在数据库设计和应用程序开发中是非常有用和常见的。

  1. 在组织中,部门通常包含多名员工。使用一对多关系,可以轻松地将员工与其所属部门关联起来。
  2. 在博客、新闻或论坛等内容管理系统中,一篇文章通常包含多个评论。使用一对多关系,可以将评论与其相关的文章连接起来。
  3. 学校可以包括多个班级,每个班级可以包含多名学生。通过一对多关系,可以管理学校、班级和学生的层次结构。

定义使用一对多关系

下面演示如何在 SQLAlchemy 中定义和使用一对多关系。


建立一对多关系

例如有两张表:一张是 ClassInfo(班级),另一个是 StudentInfo(学生)。一个班级可以有多个学生,但每个学生只属于一个班级。这就是一对多关系。

一对多关系通常通过在"多"的一方的记录中引入外键来建立。这个外键指向"一"的一方的主键,这样就可以建立两者之间的关系。


以下是定义一对多关系的代码实现。

from sqlalchemy import *
from sqlalchemy.orm import declarative_base

Base = declarative_base()

# 班级表
class ClassInfo(Base):
    """班级"""

    __tablename__ = "class_info"

    # ID 编号
    id = Column(Integer, primary_key=True)
    # 班级名称
    name = Column(String(30))

    def __repr__(self):
        return f"<Class id: {self.id}, name: {self.name}>"


# 学生表
class StudentInfo(Base):
    """学生"""

    __tablename__ = 'student_info'

    # ID编号
    id = Column(Integer,primary_key=True)
    # 名称
    name = Column(String(30))
    # 班级编号
    class_id = Column(Integer, ForeignKey("class_info.id"))

    def __repr__(self):
        return f"<Student id: {self.id}, name: {self.name},
        class_id: {self.class_id}>"

在这个例子中,StudentInfo 模型有一个名为 class_id 的属性,它通过 ForeignKey 定义了与 ClassInfo 模型的一对多关系。

接下来,使用这些模型进行操作。


创建表

首先,需要创建数据库连接,然后在数据库中创建表。

# 创建数据库连接
engine = create_engine('sqlite:///school.db')
DBSession = sessionmaker(bind=engine)
db_session: Session = DBSession()

if __name__ == '__main__':
    # 创建表
    Base.metadata.create_all(engine)
    # 删除表
    # Base.metadata.drop_all(engine)

添加数据

  • 分别向学生表和班级表添加数据:
    • add_all() 将数据添加到 session
    • commit() 提交到数据库
    • close() 关闭连接
# Class 班级表:添加两条数据
class1 = ClassInfo(id=1, name="测开21期")
class2 = ClassInfo(id=2, name="测开22期")
db_session.add_all([class1, class2])
db_session.commit()

# Student 学生表:添加四条数据
student1 = StudentInfo(id=1, name="学生一", class_id=1)
student2 = StudentInfo(id=2, name="学生二", class_id=1)
student3 = StudentInfo(id=3, name="学生三", class_id=2)
student4 = StudentInfo(id=4, name="学生四", class_id=2)
db_session.add_all([student1, student2])
db_session.add_all([student3, student4])
# 提交操作
db_session.commit()
# 关闭连接
db_session.close()

查询数据 - 多查一

在 SQLAlchemy 中,从多表关系的一侧查询对应的一表记录,通常被称为“多查一”(Many-to-One)查询。在一对多关系中,通常是从“多”的一方(即具有外键的表)查询与其关联的“一”的一方(即被引用的表)。

要实现多查一,首先需要为 StudentInfo 模型与 ClassInfo 模型构建关系。

StudentInfo 中名为 class_id 的属性已经定义好了外键。如果要这两张表想要进行关联查询,还需要通过 relationship 构建表之间的关联关系。

# 学生表
class StudentInfo(Base):
    """学生"""

    __tablename__ = 'student_info'

    # ID编号
    id = Column(Integer,primary_key=True)
    # 名称
    name = Column(String(30))
    # 班级编号
    class_id = Column(Integer, ForeignKey("class_info.id"))

    # 反射
    class_info = relationship("ClassInfo", backref='student_info')

    def __repr__(self):
        return f"<Student id: {self.id}, name: {self.name},
        class_id: {self.class_id}>"

relationship() 中第一个参数需要传入要关联的模型的类名,这样就可以通过 StudentInfo 表获取 ClassInfo 表中的信息。


然后可以使用 backref 来指定反射的关系。backref 的值是当前表的表名。

这样定义之后,就可以进行查询操作了。


下面演示如果从“多”(StudentInfo)查找关联的“一”(ClassInfo)。

# 通过 Student 查询 Class 对象
# 多查一:id 为 3 的学生 对应的班级 id 和 name
stu_info = db_session.query(StudentInfo).filter_by(id=3).first()
# 直接通过属性获取 class_id
print(stu_info.class_id)

# 反向关联之后,获取类对象的属性
print(stu_info.class_info.id)
print(stu_info.class_info.name)

查询数据 - 一查多

在 SQLAlchemy 中,一对多(One-to-Many)关系中的“一查多”操作是指从“一”的一侧查询关联的“多”的一侧。

# 一查多  id为1的班级里的所有学生
my_class = db_session.query(ClassInfo).filter_by(id=1).first()
print(my_class.student_info)

修改数据

  • 先查询,再修改
  • 修改之后要 commit()
# 由一改多 数据修改
# 班级-- 找到要修改的学生 -- 修改学生属性
my_class = db_session.query(ClassInfo).filter_by(id=1).first()
print(my_class.student_info[0].name)
my_class.student_info[0].name = "学生一修改名字"

db_session.commit()
db_session.close()

# 由多改一
# 学生 -- 找到对应的班级 -- 修改班级属性
stu = db_session.query(StudentInfo).filter_by(id=1).first()
print(stu.class_info.name)
stu.class_info.name = "测开21期修改1"
db_session.commit()
db_session.close()

删除数据

  • 删除一个学生,不影响班级
  • 删除一个班级下的所有学生
  • 这样就实现了一对多的多表关系定义与操作。
# 删除用的很少,几乎不用,了解即可
# 1. 删除一个学生 ,正常删除,不影响class
stu = db_session.query(StudentInfo).filter_by(id=1).first()
db_session.delete(stu)
db_session.commit()
db_session.close()

# 删除一个班级下所有的学生
my_class = db_session.query(ClassInfo).filter_by(id=2).first()
print(my_class.stu_infos)

db_session.query(StudentInfo).filter_by(class_id =my_class.id).delete()
db_session.commit()
db_session.close()

总结

在本章节中,介绍了多表关系中一对多的相关概念和 SQLAlchemy 的一对多关系的使用。

  1. 一对多简介
  2. 一对多场景
  3. 定义一对多模型类
  4. 创建表
  5. 添加数据
  6. 查询数据 - 多查一
  7. 查询数据 - 一查多
  8. 修改数据
  9. 删除数据