SqlAlchemy使用教程(六) -- ORM 表间关系的定义与CRUD操作

在这里插入图片描述

  • SqlAlchemy使用教程(一) 原理与环境搭建
  • SqlAlchemy使用教程(二) 入门示例及编程步骤
  • SqlAlchemy使用教程(三) CoreAPI访问与操作数据库详解
  • SqlAlchemy使用教程(四) MetaData 与 SQL Express Language 的使用
  • SqlAlchemy使用教程(五) ORM API 编程入门

本章内容,稍微有些复杂,建议腾出2小时空闲时间,冲杯咖啡或泡杯茶 😃 , 慢慢看,在电脑上跑下代码,可以加深理解.

六、表间关系的定义与CRUD操作

表间关系主要包括:一对多,一对一,多对多。其中一对多关系中也隐含了多对一关系。
表间关系是数据库操作中的重要技术点,非常有必要理解与掌握。

1、 一对多表间关系的定义

一对多表间关系实现语法

以一对多关系为例,

  • 子表侧,与父表是一对多关系,
  • 父表侧,可以反向查询子表数据,与子表是多对一关系。
class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Child"]] = relationship(back_populates="parent")


class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
    parent: Mapped["Parent"] = relationship(back_populates="children")

说明:
1)在子表中添加外键字段,以及relationship()引用,
2)在父表中添加relationship()引用,用于反向查询。

示例代码

父表:company, 子表 person, 表结构类定义如下。

from sqlalchemy.orm import DeclarativeBase, Session
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy import ForeignKey
from sqlalchemy import String, Integer
from typing import List

class Base(DeclarativeBase):
    pass
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    company_name: Mapped[str] = mapped_column(String(30), index=True)
    persons: Mapped[List['Person']] = relationship(
        back_populates="company", cascade="all, delete-orphan")

    def __repr__(self) -> str:
        return f"Company(id={self.id}, company_name={self.company_name})"

class Person(Base):
    __tablename__ = "person"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    age: Mapped[int] = mapped_column(Integer)
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped['Company'] = relationship(back_populates="persons")

    def __repr__(self) -> str:
        return f"Person(id={self.id}, name={self.name})"

说明:
1)从子表视角看,1个人只属于1个Company; 但1个Company 对应多个人,因此在父表则,relationship() 左侧的类型注解为 List[‘Person’], 也可以用Set[‘Person’]
2)父表中添加删除依赖,cascade=“all, delete-orphan”,即子表中不存在对父表记录的引用时,才能删除,以保证数据的完整性。
3)当前版本可能存在bug, 官方文档中的示例中有的字段使用简化写法(右侧未给出mapped_column()),sqlite3运行是没有问题的,但mysql, postgresql创建表时会丢弃简化写法的字段,导致后续insert等操作失败。 因此请严格请勿采有简化写法。

多对一关系的实现语法

当不需要反向查询时,则父表与子表形成Many to One 多对一关系, 在父表则添加子表的外键与relationship()引用,子表无须做额外配置

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child_id: Mapped[int] = mapped_column(ForeignKey("child_table.id"))
    child: Mapped["Child"] = relationship()


class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)

如果允许 child_id空值,则将改字段的类型注解修改为

from typing import Optional
......
child_id: Mapped[Optional[int]] = mapped_column(ForeignKey("child_table.id"))

对于3.10+版本,类型注解支持 | 操作符

child_id: Mapped[int | None] = mapped_column(ForeignKey("child_table.id"))
child: Mapped[Child | None] = relationship(back_populates="parents")

2、 插入数据与多表联合查询

1)在数据库创建表

# 创建数据库连接引擎对象
engine = create_engine(
    "mysql+mysqlconnector://root:Admin&123@localhost:3306/testdb")
# 将DDL语句映射到数据库表,如果数据库表不存在,则创建该表
Base.metadata.create_all(engine)
# 打印创建创建的表
Print(Base.metadata.tables) 

2)插入数据

基本步骤包括:

(1)为测试方便,先写1个get_or_create()函数,如果插入对象在数据库中已存在则不插入,以方便连续测试。
(2)创建session对象
(3)先创建父表对象并插入
(4)创建子表对象并插入
(5)用多表查询方法检查结果

Step-1: 自定义create_or_create()函数

官方提供的upsert方法不通用。下面函数是通用的,

def get_or_create(session, model, defaults=None, **kwargs):
    """如果不存在则创建,如果存在则返回
    输入参数:
        session: sqlalchemy session
        model: 自定义的ORM类
        defaults: 有默认值的字段
        kwargs: 其他字段(必须包含主要字段)
    返回值:
        instance: 返回的实例
    """
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        print("instance already exists", instance)
        return instance
    else:
        params = dict((k, v) for k, v in kwargs.items()
                      if not isinstance(v, ClauseElement))
        if defaults:
            params.update(defaults)
        instance = model(**params)
        session.add(instance)
        session.commit()
        print("instance inserted", instance)
        return instance
Step-2: 向两个关联表插入数据

用with 语句创建session对象,插入操作顺序,先父表再子表

with Session(engine) as session:
    # 插入数据
    get_or_create(session, Company, company_name="蜀汉")
    get_or_create(session, Company, company_name="曹魏")
    get_or_create(session, Company, company_name="东吴")
    
    stmt = select(Company)
    results = session.scalars(stmt)
    print(results.all())

    # insert data in person table
    company_shu = session.scalars(select(Company).where(
        Company.company_name == "蜀汉")).first()
    get_or_create(session, Person, name="刘备",
                  age=42, company=company_shu)
    get_or_create(session, Person, name="关羽",
                  age=40, company=company_shu)
    get_or_create(session, Person, name="张飞", age=38, company=company_shu)
    company_wei = session.scalars(select(Company).where(
        Company.company_name == "曹魏")).first()
    get_or_create(session, Person, name="张辽", age=40, company=company_wei)
    get_or_create(session, Person, name="曹操", age=38, company=company_wei)
    company_wu = session.scalars(select(Company).where(
        Company.company_name == "东吴")).first()
    get_or_create(session, Person, name="周瑜", age=30, company=company_wu)

3) 多表联合查询

   # select with Join 多表查询
    stmt = select(Person).join(Person.company).where(
        Company.company_name == "蜀汉").order_by(Person.age)
    results = session.scalars(stmt)
    # 遍历结果
    for r in results:
        print(r.name, r.age, r.company.company_name)

Output:

instance inserted Company(id=1, company_name=蜀汉)
instance inserted Company(id=2, company_name=曹魏)
instance inserted Company(id=3, company_name=东吴)
[Company(id=1, company_name=蜀汉), Company(id=2, company_name=曹魏), Company(id=3, company_name=东吴)]
instance inserted Person(id=1, name=刘备)
instance inserted Person(id=2, name=关羽)
instance inserted Person(id=3, name=张飞)
instance inserted Person(id=4, name=张辽)
instance inserted Person(id=5, name=曹操)
instance inserted Person(id=6, name=周瑜)
张飞 38 蜀汉
关羽 40 蜀汉
刘备 42 蜀汉

删除数据
当删除父表记录时,子表中应无对此数据的引用,否则无法删除。

3、 一对一关系

从外键角度看,一对一关系也是一对多关系。实现时,

  • 在父表中收集子表数据时,类型注解不使用集合类型即可。
  • 子表中relationship()方法中添加single_parent=True.
class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
child: Mapped["Child"] = relationship(back_populates="parent")   
    # 一对多时,使用child: Mapped[List["Child"]] 


class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
    parent: Mapped["Parent"] = relationship(back_populates="child",single_parent=True)

4、 多对多关系

多对多关系特点:
1)父表与子表,子表与父表之间均为多对多关系。
2)通常使用1张中间表, 与父表、子表均实现1对多关系。
(当然有的ORM模型将中间表的创建隐藏起来,但在数据库中还是可以看到)

多对多关系定义示例

from __future__ import annotations

from sqlalchemy import Column
from sqlalchemy import Table
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


# note for a Core table, we use the sqlalchemy.Column construct,
# not sqlalchemy.orm.mapped_column
association_table = Table(
    "association_table",
    Base.metadata,
    Column("left_id", ForeignKey("left_table.id")),
    Column("right_id", ForeignKey("right_table.id")),
)


class Parent(Base):
    __tablename__ = "left_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List[Child]] = relationship(
        secondary=association_table, back_populates="parents"
    )


class Child(Base):
    __tablename__ = "right_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List[Parent]] = relationship(
        secondary=association_table, back_populates="children"
    )

多对多关系的查询、插入操作与一对多查询相似。 需要注意的是删除操作。

从多对多关系中删除数据

用SQL来实现时,需要先从父表与子表删除数据,再从中间表删除。ORM API 可以自动完成这个过程。 如要删除子表的某条记录。

myparent.children.remove(somechild)

注:通过session.delete(somechild)时,MySql可能会报错,我遇到的原因有多种,不建议使用。
同样,如果要删除父表中的1条记录:

mychild.parent.remove(someparent) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/349008.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC-对静态资源的访问

1.工程中加入静态资源 在webapp下创建static文件夹,此文件夹专门放入静态资源 2.使项目可以处理静态资源的请求 在SpringMVC配置文件中添加以下语句 1.引入命名空间 xmlns:mvc"http://www.springframework.org/schema/mvc" xsi:schemaLocation“http…

Laravel 10.x 里如何使用ffmpeg

原理上很简单,就是使用命令行去调用ffmpeg,然后分析一下输出是不是有错误。 安装 首先安装 symfony/process,主要用于包装一下,用来代替 exec, passthru, shell_exec and system 。 composer require symfony/process composer…

75 C++对象模型探索。C++关于 虚函数表指针位置分析

如果一个类中,有虚函数,针对这个类会产生一个虚函数表。 生成这个类对象的时候,会有一个虚函数表指针,这个指针会指向这个虚函数表的开始地址。 我们本节就研究这个vptr指针。注意,vptr指针在 类对象中的位置。 证明…

【算法】糖果(差分约束)

题目 幼儿园里有 N 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。 但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候&#xff…

echarts 绘制垂直滚动热力图

问题1:提示功能无效 问题2:值筛选无效 效果 在线浏览 下载echarts官网例子(heatmap Examples - Apache ECharts) 稍作改动: generateData 入参改为长度和宽度noise.perlin2(i / 40, j / 20) Math.random() * 5y轴倒置指定zlevel为2 通过定…

链表--226. 翻转二叉树/medium 理解度A

226. 翻转二叉树 1、题目2、题目分析3、复杂度最优解代码示例4、适用场景 1、题目 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 1: 输入:root [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1]示例 2&…

python:socket基础操作(3)-《udp接收消息》

收跟发基本核心思想差不多,只不过收信息需要去绑定一下端口,如果我们发信息没有绑定端口,那系统会随机分配一个,如果是收信息,那我们必须要求自己绑定端口才行 基础的接收数据 import socketudp_socket socket.socke…

华清远见作业第三十三天——C++(第二天)

思维导图: 题目: 自己封装一个矩形类(Rect),拥有私有属性:宽度(width)、高度(height), 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数…

如何使用 WebRTC 与 Kurento 建立视频会议 App

本文作者 WebRTC Ventures 工程师。在 RTC 2018 实时互联网大会上,WebRTC Ventures 的资深软件工程师,将围绕 WebRTC 开发带来经验分享。欢迎访问RTC 开发者社区,与更多WebRTC开发者交流经验。 了解 WebRTC 如何工作的一种简单方式是通过学习…

安全防御综合组网实验

题目 要求 生产区在工作时间可以访问服务器区,仅可以访问http服务器。办公区全天可以访问服务器区,其中10.0.2.20 可以访问FTP服务器和http服务器。10.0.2.10仅可以ping通10.0.3.10。办公区在访问服务器区时采用匿名认证的方式进行上网行为管理。办公区…

20.云原生之GitLab集成Runner

云原生专栏大纲 文章目录 GitLab RunnerGitLab Runner 介绍GitLab Runner分类GitLab Runner工作流程 Gitlab集成Gitlab RunnerGitLab Runner 版本选择Runner在CitLab中位置专用Runner在gitlab中位置群组Runner在gitlab中位置共享Runner在gitlab中位置 GitLab部署Gitlab Runner…

QT 官方例程阅读: XML Patterns 相关

标签用于在qt creator 中查询相关工程 一、标签 Schema Validator 模式验证器 就是根据 已知的XML 模式,验证输入的XML 文件格式是否匹配,不匹配可以输出不匹配位置 如下,,首先定义了contact 元素 的子元素列表,&…

【Redis】list以及他的应用场景

介绍 :list 即是 链表。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 LinkedList,但是 C 语言并没有实现…

64、ubuntu使用c++/python调用alliedvisio工业相机

基本思想:需要使用linux系统调用alliedvisio工业相机完成业务,这里只做驱动相机调用,具体不涉及业务开发 Alvium 相机选型 - Allied Vision 一、先用软件调用一下用于机器视觉和嵌入式视觉的Vimba X 软件开发包 - Allied Vision VimbaX_Set…

解决在pycharm中无法进入conda环境的问题

问题原因: pycharm中使用的是Windows PowerShell 解决方法: setting -> Terminal中将shell path修改为win的即可--注意需要重启

Java技术栈 —— 手写Java数据库连接池

Java技术栈 —— 手写Java数据库连接池 一、连接池的作用二、讲解1.1 类图结构1.2 ConnectionPoolManager1.3 DataSourceConfig1.4 ConnectionPool与IConnectionPool1.5 ConnEntry 三、收获3.1 CopyOnWriteArrayList累的使用(对本文代码的一点建议和指正)3.2 AtomicInteger类的…

【嵌入式学习】C++QT-Day2-C++基础

笔记 见我的博客:https://lingjun.life/wiki/EmbeddedNote/19Cpp 作业 自己封装一个矩形类(Rect),拥有私有属性:宽度(width)、高度(height), 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度…

css display 左右对齐 技巧

.list_number{ display: flex; } .list_name_number{ width:100px; } //左边固定width .list_name_type{ //右边给flex:2 自动撑开 flex:2; }

使用antd design pro 如何设置不使用全局基础模板,开发开放公共页面。

修改config目录下的routes, 在指定需要开放不使用全局模版的路径,多个路径可单独添加或者直接按照分级添加模式: 这样添加了还不行,因为模版本身除了user模块以外,其他路径都需要登陆后才能访问,但一般做p…

《微信小程序开发从入门到实战》学习九十三

7.1 视图容器组件 7.1.3 swiper与swiper-item组件 swiper组件的显示效果如下图所示: indicator-dots、indicator-color和indicator-active-color三个属性用于设置swiper组件下方的指示点。设置指示点的颜色时,可以使用HexColor,也可以使用r…