当前位置: 首页 > news >正文

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 Listclass 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 sessionmodel: 自定义的ORM类defaults: 有默认值的字段kwargs: 其他字段(必须包含主要字段)返回值:instance: 返回的实例"""instance = session.query(model).filter_by(**kwargs).first()if instance:print("instance already exists", instance)return instanceelse: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 tablecompany_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 annotationsfrom 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 relationshipclass 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) 

相关文章:

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

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

嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM平台编程第五天-kernel配置(物联技术666)

链接:https://pan.baidu.com/s/1eb94AaDM-cIZsbr929Isbw?pwd1688 提取码:1688 上午:linux内核介绍 徐登伟老师 下午:linux的配置 教学内容: 一、基本kernel的制作: 1、去开源社区下载原…...

Java笔记(死锁、线程通信、单例模式)

一、死锁 1.概述 死锁 : 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法往下执行。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进…...

DAY11_(简易版)VUEElement综合案例

目录 1 VUE1.1 概述1.1.1 Vue js文件下载 1.2 快速入门1.3 Vue 指令1.3.1 v-bind & v-model 指令1.3.2 v-on 指令1.3.3 条件判断指令1.3.4 v-for 指令 1.4 生命周期1.5 案例1.5.1 需求1.5.2 查询所有功能1.5.3 添加功能 2 Element2.0 element-ui js和css和字体图标下载2.1 …...

【Kafka】开发实战和Springboot集成kafka

目录 消息的发送与接收生产者消费者 SpringBoot 集成kafka服务端参数配置 消息的发送与接收 生产者 生产者主要的对象有: KafkaProducer , ProducerRecord 。 其中 KafkaProducer 是用于发送消息的类, ProducerRecord 类用于封装Kafka的消息…...

【C语言】(1)初识C语言

什么是C语言 C语言是一种广泛应用的计算机编程语言,它具有强大的功能和灵活性,使其成为系统编程和底层开发的首选语言。C语言的设计简洁、高效,且不依赖于特定的硬件或系统,因此在各种计算平台上都能稳定运行。 C语言的特点 高…...

SpringCloudStream整合MQ(待完善)

概念 Spring Cloud Stream 的主要目标是各种各样MQ的学习成本,提供一致性的编程模型,使得开发者能够更容易地集成消息组件(如 Apache Kafka、RabbitMQ、RocketMQ) 官网地址:Spring Cloud Stream 组件 1. Binder 2…...

【Java 数据结构】包装类简单认识泛型

包装类&简单认识泛型 1 包装类1.1 基本数据类型和对应的包装类1.2 装箱和拆箱1.3 自动装箱和自动拆箱 2 什么是泛型3 引出泛型3.1 语法 4 泛型类的使用4.1 语法4.2 示例4.3 类型推导(Type Inference) 5 泛型如何编译的5.1 擦除机制5.2 为什么不能实例化泛型类型数组 6 泛型…...

第139期 做大还是做小-Oracle名称哪些事(20240125)

数据库管理139期 2024-01-25 第139期 做大还是做小-Oracle名称哪些事(20240125)1 问题2 排查3 扩展总结 第139期 做大还是做小-Oracle名称哪些事(20240125) 作者:胖头鱼的鱼缸(尹海文) Oracle A…...

驱动开发--多路复用-信号

一、多路复用 每个进程都有一个描述符数组,这个数组的下标为描述符, 描述符的分类: 文件描述符:设备文件、管道文件 socket描述符 1.1 应用层:三套接口select、poll、epoll select:位运算实现 监控的描…...

LeetCode 2859. 计算 K 置位下标对应元素的和【位操作】1000

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...

composer安装hyperf后,nginx配置hyperf

背景 引入hyperf项目用作微服务,使用composer 安装hyperf后,对hyperf进行nginx配置。 配置步骤 因为hyperf监听的是端口,不像其他laravel、lumen直接指向文件即可。所有要监听端口号。 1 配置nginx server {listen 80;//http&#xff1a…...

Flink对接Kafka的topic数据消费offset设置参数

scan.startup.mode 是 Flink 中用于设置消费 Kafka topic 数据的起始 offset 的配置参数之一。 scan.startup.mode 可以设置为以下几种模式: earliest-offset:从最早的 offset 开始消费数据。latest-offset:从最新的 offset 开始消费数据。…...

TryHackMe-Umbrella

靶场介绍 Breach Umbrella Corp’s time-tracking server by exploiting misconfigurations around containerisation. 利用集装箱化的错误配置,破坏Umbrella公司的时间跟踪服务器。 Task 1 What is the DB password? 数据库的密码是多少? 端口扫描&am…...

Excel导出警告:文件格式和拓展名不匹配

原因描述: Content-Type 原因:Content-Type,即内容类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定文件接收方将以什么形式、什么编码读取这个文件,这就是经常…...

kafka集群和Filebeat+Kafka+ELK

一、Kafka 概述 1.1 为什么需要消息队列(MQ) 主要原因是由于在高并发环境下,同步请求来不及处理,请求往往会发生阻塞。比如大量的请求并发访问数据库,导致行锁表锁,最后请求线程会堆积过多,从…...

golang map真有那么随机吗?——map遍历研究

在随机选取map中元素时,本想用map遍历的方式来返回,但是却并没有通过测试。 那么难道map的遍历并不是那么的随机吗? 以下代码参考go1.18 hiter是map遍历的结构,主要记录了当前遍历的元素、开始位置等来完成整个遍历过程 // A ha…...

详细分析对比copliot和ChatGPT的差异

Copilot 和 ChatGPT 是两种不同的AI工具,分别在不同领域展现出了强大的功能和潜力: GitHub Copilot 定位与用途:GitHub Copilot 是由GitHub(现为微软子公司)和OpenAI合作开发的一款智能代码辅助工具。它主要集成于Visu…...

TENT:熵最小化的Fully Test-Time Adaption

摘要 在测试期间,模型必须自我调整以适应新的和不同的数据。在这种完全自适应测试时间的设置中,模型只有测试数据和它自己的参数。我们建议通过test entropy minimization (tent[1])来适应:我们通过其预测的熵来优化模型的置信度。我们的方法估计归一化…...

研发日记,Matlab/Simulink避坑指南(五)——CAN解包 DLC Bug

文章目录 前言 背景介绍 问题描述 分析排查 解决方案 总结 前言 见《研发日记,Matlab/Simulink避坑指南(一)——Data Store Memory模块执行时序Bug》 见《研发日记,Matlab/Simulink避坑指南(二)——非对称数据溢出Bug》 见《…...

机器人3D视觉引导半导体塑封上下料

半导体塑封上下料是封装工艺中的重要环节,直接影响到产品的质量和性能。而3D视觉引导技术的引入,使得这一过程更加高效、精准。它不仅提升了生产效率,减少了人工操作的误差,还为半导体封装技术的智能化升级奠定了坚实的基础。 传统…...

(十二)Head first design patterns代理模式(c++)

代理模式 代理模式:创建一个proxy对象,并为这个对象提供替身或者占位符以对这个对象进行控制。 典型例子:智能指针... 例子:比如说有一个talk接口,所有的people需要实现talk接口。但有些人有唱歌技能。不能在talk接…...

C++从零开始的打怪升级之路(day21)

这是关于一个普通双非本科大一学生的C的学习记录贴 在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料 那么开启正题 今天分享的是关于vector的题目 1.删除有序数组中的重复项 26. …...

《设计模式的艺术》笔记 - 观察者模式

介绍 观察者模式定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。 实现 myclass.h // // Created by yuwp on 2024/1/12. //#ifndef DESIGNPATTERNS_MYCLASS_H #define DESIGNPATTERNS_MYCLA…...

Java如何对OSS存储引擎的Bucket进行创建【OSS学习】

在前面学会了如何开通OSS,对OSS的一些基本操作,接下来记录一下如何通过Java代码通过SDK对OSS存储引擎里面的Bucket存储空间进行创建。 目录 1、先看看OSS: 2、代码编写: 3、运行效果: 1、先看看OSS: 此…...

ModuleNotFoundError: No module named ‘half_json‘

问题: ModuleNotFoundError: No module named ‘half_json’ 原因: 缺少jsonfixer包 解决方法: pip install jsonfixerjson修正包地址: https://github.com/half-pie/half-json...

深入探究 Android 内存泄漏检测原理及 LeakCanary 源码分析

深入探究 Android 内存泄漏检测原理及 LeakCanary 源码分析 一、什么是内存泄漏二、内存泄漏的常见原因三、我为什么要使用 LeakCanary四、LeakCanary介绍五、LeakCanary 的源码分析及其核心代码六、LeakCanary 使用示例 一、什么是内存泄漏 在基于 Java 的运行时中&#xff0…...

Linux CentOS使用Docker搭建laravel项目环境(实践案例详细说明)

一、安装docker # 1、更新系统软件包: sudo yum update# 2、安装Docker依赖包 sudo yum install -y yum-utils device-mapper-persistent-data lvm2# 3、添加Docker的yum源: sudo yum-config-manager --add-repo https://download.docker.com/linux/cen…...

第六课:Prompt

文章目录 第六课:Prompt1、学习总结:Prompt介绍预训练和微调模型回顾挑战 Pre-train, Prompt, PredictPrompting是什么?prompting流程prompt设计 课程ppt及代码地址 2、学习心得:3、经验分享:4、课程反馈:5、使用Mind…...

网络安全(初版,以后会不断更新)

1.网络安全常识及术语 资产 任何对组织业务具有价值的信息资产,包括计算机硬件、通信设施、IT 环境、数据库、软件、文档 资料、信息服务和人员等。 漏洞 上边提到的“永恒之蓝”就是windows系统的漏洞 漏洞又被称为脆弱性或弱点(Weakness)&a…...

天河建网站公司/磁力搜索

一、comm命令 comm命令可以用于两个文件之间的比较(必须是已经排序的两个文件,可以使用sort命令先进行排序),它有一些选项可以用来调整输出,以便执行交集、求差、以及差集操作。 交集:打印出两个文件所共有…...

阿里云怎么wordpress/河南今日头条新闻最新

1. 系统信息 1.1 操作系统类型 查看操作系统类型命令为 uname 例: [roothost-134 ~]# uname Linux定义变量 os_type$(uname)1.2 操作系统版本号 查看操作系统版本号命令为 cat /etc/redhat-release 例: [roothost-134 ~]# cat /etc/redhat-release…...

游戏网站网页设计/seo引流什么意思

jacob.jar的结构: jacob包括两个部分:com.jacob.activeX: ActiveXComponent类 com.jacob.com: 其它类和元素 Jacob类: Jacob的结构很简单,包含以下几个类:ActiveXComponent:封装了Dispatch对象,用于创建一个封装了COM组件对象的Java Object Dispatch:用于指向封装后…...

怎么做qq代刷网站/抖音广告代运营

这是一个实际的应用场景案例,某零件供应商提供了不同工况下零件性能的数据,因为需要比较不同工况下零件性能的差异,该供应商提供了一份数据和可视化的图。因为供应商没有提供计算防范,本着研究的精神想复现一下供应商提供数据的可…...

ps做图游戏下载网站有哪些/自己如何开网站

可能中病毒了 rpc服务意外终止,系统重启问题 1.第一种情况,可能是中毒引起的,杀毒、打上冲击波、振荡波的补丁 2.第二种情况,一般都是IE组件在注册表中注册信息被破坏,可以按下面的方法去解决该问题: (1).在"开始…...

大连做网站开发的公司/百度推广seo是什么意思

高级 KML 文档 本段描述了部分必须用文本编辑器编写的 KML 元素,如几何图形的共享样式、地标的突出显示图标和屏幕叠加层。手动编写 KML 比用 Google 地球界面创建和修改地图项略微难一些,但只需稍加练习,多数用户都能自如地编辑 KML 文件以添…...