Django模型层实现多表关系创建和多表操作


Posted in Python onJuly 21, 2021

前言

继续上面一篇文章的内容,本文介绍多表操作。使用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)
...

到此这篇关于Django模型层实现多表关系创建和多表操作的文章就介绍到这了,更多相关Django 多表操作内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木! 

Python 相关文章推荐
深入Python解释器理解Python中的字节码
Apr 01 Python
python获取文件扩展名的方法
Jul 06 Python
详解Python的collections模块中的deque双端队列结构
Jul 07 Python
python自动12306抢票软件实现代码
Feb 24 Python
详解如何为eclipse安装合适版本的python插件pydev
Nov 04 Python
Python面向对象类编写细节分析【类,方法,继承,超类,接口等】
Jan 05 Python
python实现简单图片物体标注工具
Mar 18 Python
利用Python半自动化生成Nessus报告的方法
Mar 19 Python
Python 的AES加密与解密实现
Jul 09 Python
Python面向对象程序设计之类和对象、实例变量、类变量用法分析
Mar 23 Python
jupyter notebook快速入门及使用详解
Nov 13 Python
PyQt5中QSpinBox计数器的实现
Jan 18 Python
Python基本数据类型之字符串str
Jul 21 #Python
Python中22个万用公式的小结
Jul 21 #Python
python字典的元素访问实例详解
Jul 21 #Python
Opencv实现二维直方图的计算及绘制
python scrapy简单模拟登录的代码分析
Jul 21 #Python
python异步的ASGI与Fast Api实现
Jul 16 #Python
Python实现PIL图像处理库绘制国际象棋棋盘
You might like
PHP FOR MYSQL 代码生成助手(根据Mysql里的字段自动生成类文件的)
2011/07/23 PHP
php获取指定范围内最接近数的方法
2015/06/02 PHP
php关闭warning问题的解决方法
2016/05/17 PHP
php PDO实现的事务回滚示例
2017/03/23 PHP
PHP排序二叉树基本功能实现方法示例
2018/05/26 PHP
PHP实现会员账号单唯一登录的方法分析
2019/03/07 PHP
javascript网页关闭时提醒效果脚本
2008/10/22 Javascript
JavaScript 内置对象属性及方法集合
2010/07/04 Javascript
javascript动态的改变IFrame的高度实现自动伸展
2013/10/12 Javascript
JS Attribute属性操作详解
2016/05/19 Javascript
Jquery中map函数的用法
2016/06/03 Javascript
js实现自定义进度条效果
2017/03/15 Javascript
jQuery设置图片等比例缩小的方法
2017/04/29 jQuery
JS滚动到指定位置导航栏固定顶部
2017/07/03 Javascript
JavaScript设计模式之原型模式分析【ES5与ES6】
2018/07/26 Javascript
Weex开发之WEEX-EROS开发踩坑(小结)
2019/10/16 Javascript
Node.js 实现抢票小工具 &amp; 短信通知提醒功能
2019/10/22 Javascript
node.js 微信开发之定时获取access_token
2020/02/07 Javascript
详谈Object.defineProperty 及实现数据双向绑定
2020/07/18 Javascript
python利用有道翻译实现&quot;语言翻译器&quot;的功能实例
2017/11/14 Python
Python实现将一个正整数分解质因数的方法分析
2017/12/14 Python
分析Python读取文件时的路径问题
2018/02/11 Python
终端命令查看TensorFlow版本号及路径的方法
2018/06/13 Python
python将控制台输出保存至文件的方法
2019/01/07 Python
TensorFlow梯度求解tf.gradients实例
2020/02/04 Python
Python requests模块基础使用方法实例及高级应用(自动登陆,抓取网页源码)实例详解
2020/02/14 Python
PyQt实现计数器的方法示例
2021/01/18 Python
AE美国鹰日本官方网站: American Eagle Outfitters
2016/12/10 全球购物
澳大利亚最受欢迎的女士度假服装:Kabana Shop
2020/10/10 全球购物
财务副总经理工作职责
2013/11/25 职场文书
畜牧兽医本科生的自我评价
2014/03/03 职场文书
房地产财务部员工岗位职责
2014/03/12 职场文书
会计人员岗位职责
2014/03/19 职场文书
学校交通安全责任书
2014/08/25 职场文书
中学生旷课检讨书500字
2014/10/29 职场文书
「偶像大师 MILLION LIVE!」七尾百合子手办开订
2022/03/21 日漫