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

news2024/11/15 10:36:55

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

七种较为成熟的渗透测试标准方法

文章目录 前言一、OWASP渗透测试二、渗透测试执行标准(PTES)三、NIST特别出版物800-115四、ISSAF渗透测试框架五、CREST渗透测试方法六、MITRE(ATT&CK)七、OSSTMM开源安全测试方法总结前言 对于网络安全领域的攻击端, 进行渗透测试的方式几乎是无限多的。由于在进行渗…

AI:120-智能监控下的行人交通违法行为自动罚款系统

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

LFU算法

LFU算法 Least Frequently Used(最不频繁使用) Leetcode有原题,之前手写过LRU,数据结构还是习惯于用java实现,实现是copy的评论题解。 题解注释写的很清楚 大致就是说LFUCache类维护一个存放node的map,同…

jmeter之接口测试实现参数化(利用函数助手),参数值为1-9(自增的数字)

1.前言 思考:为什么不用postman,用postman的话就得导入csv文件/json文件 如果不想导入文件,postman是实现不了,因为postman每次只会运行一次 2.jmeter函数助手实现参数化 (1)新建“线程组”--新建“http…

MySQL-删除重复数据

在实际应用中,遇到一个这样的问题,MySQL中存储的数据为资讯类数据,在页面展示时会出现多个平台的新闻报导相同的内容,导致页面会出现重复数据。因为数据是每天定期更新,所以最快捷有效的方式是在更新完数据后增加一个去…

计算机网络-奈氏准则和香农定理(码间串扰 二者区别)

文章目录 失真失真的一种现象-码间串扰奈氏准则(奈溃斯特定理)例题 香农定理例题 奈氏和香农 失真 就是指与原来的不一样了 两种情况 前三个是正相关,最后一个是负相关 码元传输速率越快,失真程度越严重的原因可能包括以下几点…

世强硬创获凌讯微电子授权代理,助力功率半导体核心器件国产替代

近年来,国产功率半导体已在众多领域应用,特别是中高端产品,如超结MOSFET、IGBT、碳化硅等,市场逐渐从依赖进口向国内自给自足转变。 为服务更多硬科技企业实现国产化替代,功率半导体器件制造商广东凌讯微电子有限公司…

【Go 快速入门】安装 Go 语言 | 开发工具 Goland | 第一个 Go 语言程序

文章目录 前言安装 Go 语言编译器 Goland运行 Go 程序补充 前言 本系列教程,目的是帮助一个有其他编程基础的 Go 语言小白快速入门 Go 语言,而非启发式学习。每篇幅保证不说废话,尽可能精炼总结,为上手后续的 Go 相关项目打下基础…

渲染与创造之美:互为表里的艺术

在五彩斑斓的艺术世界中,渲染与创造是两股不可或缺的力量。它们之间的关系,恰如弓与箭,互为表里,共同塑造出无数令人叹为观止的视觉景象。创造之美是指通过创新思维和创造力,将想象具象化为现实,创造出新的…

Spyder安装与使用

Spyder是一个Python的集成开发环境(IDE),由科学家、工程师和数据分析师设计。它提供了强大的编辑、调试和分析功能,以及数据探索和可视化工具,特别适合科学计算和数据分析。 Spyder的主要特点包括: 编辑器…

仿双色球抽奖大转盘

仿双色球抽奖大转盘 前言1、页面搭建2、JS逻辑编写3、Css样式编写4、编译发布5、往期回顾总结: 前言 需求来时奋笔疾书,需求变时追风逐电 ! 年低了,接到部门需求,2天时间要开发一个仿双色球抽奖大转盘,于是…

malloc()和free()

一、malloc() //格式: void *malloc(size_t str);//举例 double *ptd; ptd (double *) malloc(30*sizeof(double));//30个double类型的值请求内存空间 //并设置ptd指向该位置 malloc函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说&#xff…

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(4.2) —— 中断函数中使用队列

前言 (1)FreeRTOS是我一天过完的,由此回忆并且记录一下。个人认为,如果只是入门,利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后,再去学习网上的一些其他课程也许会简单很多。 (2&am…

Android HIDL概述与绑定模式的实现

一、前言 Android O(8.0) 版本之后,底层实现有了比较大的变化,最显著的一个方面就是 HIDL 机制的全面实施。本文对于理解系统源码中 Gnss、Usb、Camera 等模块的工作原理有极大帮助。 二、HIDL 设计目的 在 Android O(8.0) 之前系统的升级牵扯多方协作…

安全用电管理平台方案介绍——Acrelcloud-6000

安全用电管理平台是一个针对电力系统安全管理的平台,旨在提供对电力设备和用电行为进行监控、分析和管理的解决方案。该平台结合了物联网技术、数据分析和远程监控等技术手段,能够实时监测、分析和预警电力系统的安全状况,以便及时采取措施防…

再谈Android View绘制流程

一,先思考何时开始绘制 笔者在这里提醒读者,Android的View是UI的高级抽象,我们平时使用的XML文件也好,本质是设计模式中的一种策略模式,其View可以理解为一种底层UI显示的Request。各种VIew的排布,来自于开…

KernelGPT: LLM for Kernel Fuzzing

KernelGPT: Enhanced Kernel Fuzzing via Large Language Models 1.Introduction2.Background2.1.Kernel and Device Drivers2.2.Kernel Fuzzing2.2.1.Syzkaller规约2.2.2.规约生成 3.Approach3.1.Driver Detection3.2.Specification Generation3.2.1.Command Value3.2.2.Argum…

Vue2学习之第六、七章——vue-router与ElementUI组件库

路由 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。前端路由:key是路径,value是组件。 1.基本使用 安装vue-…

springboot快速写接口

1. 建proj形式 name会变成文件夹的名字,相当于你的项目名称 基础包 2. 基础依赖 3. 配置数据库 这里要打开mysql,并且创建数据库 方法: 安装好数据库,改好账号密码用navicat来建表和账号配置properties.yml文件即可 4.用res…

读AI3.0笔记07_游戏与推理

1. 始于游戏,不止于游戏 1.1. 开发超人类的游戏程序并不是人工智能的最终目的 1.2. AlphaGo所有的版本除了下围棋,其他什么也不会 1.2.1. 其最通用的版本AlphaGo Zero也一样 1.3. 这些游戏程序中没有一个能够将其在一款游戏中学到的知识迁移到其他游…