Django模型层实现多表关系创建和多表操作
作者:程序媛小庄 发布时间:2022-12-01 09:13:46
目录
前言
创建表关系
多表数据操作 - 增删改
一对多&一对一关系 - 增删改
增加数据
删除数据
修改数据
多对多关系 - 增删改
增加多对多关系 - add()
删除多对多关系 - remove()
修改多对多关系 - set()
清空第三张表某个对象的绑定关系 - clear()
多表查询
子查询
正向查询
反向查询
联表查询
正向查询
反向查询
聚合查询
分组查询
F与Q查询
F查询
Q查询
django开启事务
前言
继续上面一篇文章的内容,本文介绍多表操作。使用django ORM可以创建多表关系,并且也支持多张表之间的操作,以创建表关系和查询两部分说明django ORM的多表操作。以作者、图书、出版社和作者信息几张表作为案例进行说明。
创建表关系
注意:在实际开发中不推荐使用外键建立表关系即不使用级联更新和级联删除,而是推荐使用逻辑上的外键关系建立表关系。
上述的四张表中,图书和出版社这两表的关系属于一对多的关系,外键建立在查询频率高的一方。作者和作者详情表属于一对一关系,外键建立在查询频率高的一方,作者和图书属于多对多关系,需要第三张表存储关系,建议将外键建在查询频率高的一方。创建表时一定要执行数据库迁移命令哦~
创建表关系时可以先将表模型创建出来,然后再添加外键字段,另外在使用django ORM创建外键关系时,关联的外键字段会自动在字段后加_id,表与表之间的关系默认以主键作为关联字段。另外在创建表关系时,不建议使用实质的外键进行关联,而是通过使用逻辑上的关系来指定表关系。
class Book(models.Model):
name = models.CharField(max_length=60, verbose_name='图书名')
# 浮点数字段,max_digits表示数字共8位, decimal_place表示小数点占2位
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='图书价格')
inventory_num = models.IntegerField(verbose_name='库存数量')
sell_num = models.IntegerField(verbose_name='卖出数量')
# 一对多关系外键字段创建使用 ForeigenKey(to='表名'),默认关联主键字段, db_constraint=Flase表示不建立实质的外键关系,只是逻辑上的关系
publish = models.ForeignKey(to='Publish', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name='外键关联出版社')
# 多对多关系,使用ManyToManyField(to='表名'),author是一个虚拟的字段,主要是用来告诉ORM,书籍和作者是多对多的关系,而且ORM会自动创建多对多关系的第三张
author = models.ManyToManyField(to='Author', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name='外键关联作者')
class Publish(models.Model):
name = models.CharField(max_length=12, verbose_name='出版社名称')
class Author(models.Model):
name = models.CharField(max_length=10, verbose_name='作者名称')
# 一对一关系使用OneToOneField(to='表名')
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name='外间关联作者详情')
class AuthorDetail(models.Model):
age = models.IntegerField(verbose_name='年龄')
phone = models.CharField(max_length=11, verbose_name='手机号')
另外还需补充一点,多对多的表关系共有三种创建方式,分别是全自动创建、半自动创建和全手动创建:
# 全自动创建 - ManyToManyField,一般这种方式可以满足需求
'''
使用全自动创建多对多关系的优点就是无需手动创建第三张表,非常方便,django ORM直接提供操作第三张表关系的方法
缺点就是无法扩展第三张关系表
'''
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
# 纯手动创建 - ForeignKey手动创建第三张表
'''
第三张表完全取决于手动的扩展,但是需要写的代码较多,而且无法使用ORM提供的简单方法
'''
class Book(models.Model):
name = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')
# 半自动创建,通过ManyToManyField的参数控制第三张表
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(
to='Author', # 告诉ORM不需要自动帮忙创建第三张关系表
through='Book2Author', # 告诉ORM第三张关系表对应的表的外键字段
through_fields=('book','author') # 通过哪两个字段关联表,当前在哪张表,就先写哪个表的关联字段
)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
多表数据操作 - 增删改
首先介绍多表操作的增删改操作,因为多表的查询数据操作稍微麻烦一点,单独另外开小灶。
一对多&一对一关系 - 增删改
一对一和一对多的增删改操作基本是一致的。
增加数据
增加数据有两种方式,一种方式是通过实际字段来添加,另一种方式是通过虚拟字段对象赋值添加。
# 方式1:通过实际字段
book_obj = models.Book.objects.create(name='哈利波特', price=10.2, publish_id=1)
# 方式2,先获取出版社对象,再将书籍和出版社通过出版社对象进行关联
publis_obj = models.Publish.objects.filter(pk=1).first()
book_obj = models.Book.objects.create(name='哈利波特', price=10.2, publish=publis_obj)
删除数据
需要说明一点,在实际项目开发中删除数据并不是真的删除了,而是使用一个布尔类型的字段标识该数据是否删除。
删除数据的时候如果不指定on_delete=models.DO_NOTHING默认是级联更新级联删除的。
models.Publish.objects.filter(pk=1).delete()
修改数据
修改数据同增加数据一样有两种方式。
# 方式1
models.Book.objects.filter(pk=1).update(publish_id=1)
# 方式2
pub_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.filter(pk=1).update(publish=pub_obj)
多对多关系 - 增删改
首先需要明确的是,多对多的增删改是在操作第三张关系表,但是第三张关系表是django自动创建的,如何通过代码进入第三张表呢?多对多的外键关系被建在book表,多对多的外键字段是author字段,因此通过book_obj.author即可操作第三张表了。
对于django自动创建的第三张表的多对多关系,django提供了额外的方法对数据进行操作。
增加多对多关系 - add()
add()方法给第三张关系表添加数据,括号内既可以传数字也可以传对象,并且都支持多个同时操作。
# 方式1,直接添加id值
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.author.add(1) # 增加 1 1记录,即id为1的书绑定id为1的作者
book_obj.author.add(2, 3) # 增加两条记录,1 2 和 1 3
# 方式2,通过对象添加关系
book_obj = models.Book.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj3 = models.Author.objects.filter(pk=3).first()
book_obj.author.add(author_obj1) # 增加1条记录
book_obj.author.add(author_obj2, author_obj3) # 增加2条
删除多对多关系 - remove()
remove()方法用来为第三张表删除数据,同样的,括号内既可以传数字也可以传对象,并且支持多条数据同时操作。
# 方式1:直接删除值
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.author.remove(2) # 删除book_id为1和author_id都为2的记录
book_obj.authors.remove(1, 3) # 删除多条
# 方式2:通过对象删除
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj2 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj1, author_obj2)
修改多对多关系 - set()
set()方法用来修改第三张表,该方法是一个覆盖操作,用新的关系覆盖之前的关系,该方法的参数必须是一个列表或者元组,指出数字或对象,也支持多条数据同时操作。
# 方式1:直接通过值进行修改
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.author.set([2]) # 将book_id为1对应的author_id修改为2
book_obj.authors.set([1, 2]) # 将书的作者设置为id=1 和id=2的作者
# 方式2:通过对象进行修改
author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj3 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj2, author_obj3])
清空第三张表某个对象的绑定关系 - clear()
clear()方法会清空第三张关系表中某个对象的绑定关系。
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.author.clear()
多表查询
在进行多表查询操作前,需要了解一个概念,什么是正向查询和反向查询。
正向查询:外键在哪个表中,查询关联的表就是正向查询,比如通过书籍查询出版社;
反向查询:被关联的表查外键字段所在的表就是反向查询,比如通过出版社查询书籍。
子查询
如果查询比较复杂时可以采用子查询的方式,子查询就是分步骤查询的意思,先查询得到的结果作为后查询的条件。
正向查询
正向查询按字段,如果有多个结果需要外键字段.all(),那么怎么判断查询的结果有多个呢?如果在不加.all()的情况下得到的结果是应用名.模型名.None比如first.Author.None这种情况下说明ORM 语句没有错误,只是查询到的结果有多个,就需要加.all()。当查询结果只有一个时得到的是一个模型对象,如果为多个就是QuerySet对象。
# 一对多关系查询:查询书籍主键为1的书籍由哪个出版社出版
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.publish # Publish object (1)
print(res.name)
# 多对多关系查询:查询数据主键为1的作者
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.author # first.Author.None,说明结果有多个
res_many = book_obj.author.all() # <QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]>
# 一对一关系查询:查询作者lili的年龄
author_obj = models.Author.objects.filter(name='lili').first()
res = author_obj.author_detail
print(res.phone)
反向查询
反向查询如果是一对一的话是表名小写,如果是一对多或者多对多时反向查询是表明小写——set,另外如果有多个结果需要在表明小写后再加_set.all(),判断结果是否有多个的方法与正向查询相同。当查询结果只有一个时得到的是一个模型对象,如果为多个就是QuerySet对象。
# 一对多关系查询,查询出版社是东方出版社出版的书
publish_obj = models.Publish.objects.filter(name='东方').first()
res = publish_obj.book_set.all() # <QuerySet [<Book: Book object (1)>, <Book: Book object (2)>]>
# 多对多关系查询,查询作者是lili写过的书
author_obj = models.Author.objects.filter(name='lili').first()
res = author_obj.book_set # first.Book.None,说明有多个结果
res_many = author_obj.book_set.all()
print(res_many) # <QuerySet [<Book: Book object (1)>]>
# 一对一关系查询,查询手机号是119的作者
author_detail_obj = models.AuthorDetail.objects.filter(phone='119').first()
res = author_detail_obj.author
print(res.name)
联表查询
联表查询就是像MySQ里面SQL语句的联表查询一样,只不过在django的ORM里面使用基于双下划线联表查询(跨表查询)。联表查询的可以使用一行代码查询结果,联表查询也遵循正反向关系。
正向查询
# 一对多关系查询:查询书籍主键为1的出版社名称和书名
# 首先获取书籍对象,书籍是查询publish的基表,因此获取书名直接values('names')即可,而出版社的名字是通过外键字段跳到出版社的表中农,需要通过__找到需要的字段值
res = models.Book.objects.filter(pk=1).values('name', 'publish__name') # <QuerySet [{'name': '哈利波特', 'publish__name': '东方'}]>
# 多对多关系查询,查询书籍主键为1的作者姓名
res = models.Book.objects.filter(pk=1).values('author__name') # <QuerySet [{'author__name': 'lili'}, {'author__name': 'nana'}]>
# 一对一关系查询,查询lili的手机号和姓名
res = models.Author.objects.filter(name='lili').values('name', 'author_detail__phone').first()
print(res.get('name'), res.get('author_detail__phone'))
反向查询
# 一对多关系查询:查询数据主键为1的出版社名称和书的名字
res = models.Publish.objects.filter(book__id=1).values('name', 'book__name') # <QuerySet [{'name': '东方', 'book__name': '哈利波特'}]>
# 多对多关系查询:查询书籍主键为1的作者姓名和书名
res = models.Author.objects.filter(book__id=1).values('name', 'book__name') # <QuerySet [{'name': 'lili', 'book__name': '哈利波特'}, {'name': 'nana', 'book__name': '哈利波特'}]>
# 一对一关系查询:查询作者id是1作者的姓名和手机号
res = models.AuthorDetail.objects.filter(author__id=1).values('author__name', 'phone') # <QuerySet [{'author__name': 'lili', 'phone': '119'}]>
# 综合大查询:查询书籍主键是1的作者的手机号,首先获取书籍对象,书籍关联了作者表,作者表又关联了作者详情表
res = models.Book.objects.filter(pk=1).values('author__author_detail__phone') # <QuerySet [{'author__author_detail__phone': '119'}, {'author__author_detail__phone': '120'}]>
聚合查询
聚合查询通常情况下是配合分组一起使用的,聚合查询就是用一些统计工具,比如最大值,最小值,平均值等,聚合函数的导入方式from django.db.models import Max, Min, Sum, Count, Avg,如果在不分组的情况下使用聚合函数需要在aggregate()方法内使用。
from django.db.models import Min,Max,Sum,Count,Avg
# 统计书的平均价格
res = models.Book.objects.aggregate(Avg('price'))
print(res)
# 可以将这些聚合函数同时使用
res = models.Book.objects.aggregate(Max('price'),Sum('price'),Count('pk'))
print(res)
分组查询
聚合函数通常和分组一起使用,分组查询的方法是annotate,默认以models.分组依据作为分组依据,即表的主键进行分组,如果annotate()方法前面出现了values()那么就会按照values中指定的值进行分组。分组查询支持__跨表查询。
from django.db.models import Sum, Max, Min, Avg, Count
# 1.统计每本书的作者个数
res = models.Book.objects.annotate(author_num=Count('author')).values('name', 'author_num') # author_num是自己定义的字段用来存储统计出来的每本书对应的作者个数,暂时存为表中的字段
# 2.统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
# 3.统计不止一个作者的图书
# 先按照图书分组,算出每本书的作者数量,再过滤出作者数量大于1的数据
res = models.Book.objects.annotate(author_num=Count('author')).filter(author_num__gt=1).values('name', 'author_num')
# 4.查询每个作者出版书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
F与Q查询
F查询
F查询可以获得表中某个字段的数据值,尤其适合表中两个字段之间的比较运算,在操作字符类型的数据时,F不能直接做字符串的拼接,需要借助Concat和Value。
from django.db.models import F
# 1.查询卖出数量大于库存数量的书籍
res = models.Book.objects.filter(sell_num__gt=F('inventory_num'))
# 将所有书籍的价格提升20元
res = models.Book.objects.update(price=F('price')+20)
F查询对于字符串的操作需要借助Concat和Value两个方法:
# 将所有书的名称后面加上爆款两个字
from django.db.models.functions import Concat
from django.db.models import F, Value
models.Book.objects.update(name=Concat(F('name'),Value('爆款')))
Q查询
使用filter()进行条件过滤时,采用的是逻辑与and的操作,如果想要将多个筛选条件更改为or或者not的关系则需要借助Q查询。在Q查询中|表示or的关系,~表示not的关系。
import django.db.models import Q
# 查询卖出数量大于100或者价格小于20的书籍
res = models.Book.objects.filter(~Q(sell_num__gt=100) | Q(price__lt=20))
另外Q查询还有另一个比较高级的用法,就是可以将查询条件的左边也变成字符串的形式。
# 先产生一个Q的实例
q = Q()
# 修改q的连接条件的关系
q.connector = 'or'
q.children.append(('sell_num__gt',100))
q.children.append(('price__lt',200))
res = models.Book.objects.filter(q)
# filter的条件是Q实例化产生的对象,每个条件默认还是and关系,可以修改
print(res)
django开启事务
MySQL为了保证数据的安全有一个事务的机制,django既然能够连接MySQL那么django就可以支持MySQL的事务机制。下述代码就是在django中开启事务:
from django.db import transaction
try:
with transaction.atomic(): # 在with代码快内书写的所有orm操作都属于同一个事务
...
except Exception as e:
print(r)
...
来源:https://juejin.cn/post/6985408898433810446
猜你喜欢
- 前面已经了解了关于PL/SQL编程的基础,本文将结合一个案例来加深对这些知识点的理解。一. 案例介绍 某数据库有两张表,是关于某公司员工资料
- 下面一段代码给大家介绍python 处理微信对账单数据,具体代码如下所示:#下载对账单并存储到数据库@app.route("/bi
- 前言:如何做到,控制多设备并行执行测试用例呢。思路篇我们去想下,我们可以获取参数的信息,和设备的信息,那么我们也可以针对每台设备开启不一样的
- 下面代码的功能是为了管理任务的,大家项目中有需要用到的,尽管拿去吧。 这个东西用于可以管理任务比如:今天我要做什么,做了没有,做了就打钩。&
- 本文为大家分享了pygame游戏之旅的第11篇,供大家参考,具体内容如下定义一个button函数,将文字,颜色等作为参数。def butto
- 本文实例讲述了php输出xml必须header的解决方法。分享给大家供大家参考。具体方法如下:问题描述:最近在做一个xml输出时发现我们直接
- 1. 停应用层的各种程序。 2. 停oralce的监听进程: $lsnrctl stop 3. 在独占的系统用户下,备份控制文件: SQL&
- pytest的setup与teardown1)pytest提供了两套互相独立的setup 与 teardown和一对相对自由的setup与t
- <pre name="code" class="python">creazy.txt文件
- 首先,我的索引结构是酱紫的。
- Scrapy回调函数回调方法示例:yield Request(url=self.base_url + 'QueryInfo'
- 单向链表单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节
- 运行环境:Windows 8.1Python:2.7.6在安装的时候,我使用的pip来进行安装,命令如下:pip install beaut
- 用法本脚本用于批量扫描端口 1.在同目录下创建输入文件,属性inputFile为输入文件名2.属性th为线程数3.属性port为探测的目标端
- 聚集索引,数据实际上是按顺序存储的,数据页就在索引页上。就好像参考手册将所有主题按顺序编排一样。一旦找到了所要搜索的数据,就完成了这次搜索,
- 在学习linear regression时经常处理的数据一般多是矩阵或者n维向量的数据形式,所以必须对矩阵有一定的认识基础。numpy中创建
- 顽固的Select下拉列表,一般很难用css来控制样式下面使用了js来美化select<!DOCTYPE html PUBLIC &q
- 本文主要介绍如何用Python设计一个经典小游戏:猜大小。在这个游戏中,将用到前面我介绍过的所有内容:变量的使用、参数传递、函数设计、条件控
- 本文实例总结了PHP图像处理技术。分享给大家供大家参考,具体如下:1、绘图场景: 验证码、图像水印、图像压缩处理php绘图坐标体系是从0,0
- 1. 导入包我们这次的任务是随机生成一些离散的点,然后用直线(y = w *x + b )去拟合首先看一下我们需要导入的包有torch 包为