ORM 多表操作
ORM 多表操作
判断对应关系
分别从一张表中取出一条记录与另外一张表比对
- 如果双方只能对应对方一条记录,一对一
- 有一方能对应对方多条记录,一对多
- 双方都能对应对方多条记录, 多对多
数据表改动
每次删除或改动表与字段 都要做数据库迁移,数据库迁移的时候会通过改动同步到数据库
-
删除表:
- 注释表的类或删除类
-
改动字段:
- 在已有的表中添加字段,需要给字段添加一个默认值 default=xxxx
- 如果添加的是关联字段,则不能添加default=xxxx,应为关联字段不能重复
- 应在开发时规划好,把全部表建立好后再迁移
关系表创建
-
实例:我们来假定下面这些概念,字段和关系
- 作者模型:一个作者有姓名和年龄。
作者详细模型:
- one-to-one:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:
- 出版商有名称,所在城市以及email。
书籍模型:
-
many-to-many: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many),django自动创建第三张表,存放外键书籍id,作者id
-
one-to-many: 一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。在多的一方创建关联字段
-
注意事项:
- 表的名称
myapp_modelName
,是根据 模型中的元数据自动生成的,也可以覆写为别的名称 id
字段是自动添加的- 对于外键字段,Django 会在字段名上添加
"_id"
来创建数据库中的列名
- 对于外键字段,Django 会在字段名上添加
- 这个例子中的
CREATE TABLE
SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。 - 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加
models.py
所在应用的名称。 - 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
- 级联删除:publish_id = models.ForeignKey("Publish", on_delete=models.CASCADE)
- Publish表删除一条记录,与此条记录关联的表中的记录都会被删除
- django1.11: 默认级联删除
- django2.2 : ForeignKey 必须加参数on_delete=models.CASCADE
- 表的名称
-
models.py
from django.db import models # Create your models here. class Author(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) age=models.IntegerField() # 与AuthorDetail建立一对一的关系 # 在app01_author表中添加 authordetail_id字段, 小写关联表名+_id # AuthorDetail 记录删除,Author记录也删除, 级联删除 authorDetail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE) class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday=models.DateField() telephone=models.BigIntegerField() addr=models.CharField( max_length=64) class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) city=models.CharField( max_length=32) email=models.EmailField() class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField( max_length=32) publishDate=models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2) # 与Publish建立一对多的关系,外键字段建立在多的一方 # app01_book表中添加publish_id publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 # app01_book_authors 表结构: id book_id author_id # 表名拼接方法: app应用名称 + 小写表名 + 所在表属性(authors) : app01_book_authors authors=models.ManyToManyField(to='Author',)
-
models.ForginKey:
sql语句:
create table book(title varchar(32),......, publish_id INT, foreignkey references Publish(id))
参数:django源码
def __init__(self, to, on_delete=None, related_name=None, related_query_name=None, limit_choices_to=None, parent_link=False, to_field=None, db_constraint=True, **kwargs):
- to: 关联那张表
- to_field: 关联哪个字段,默认关联主键id
多表记录操作
-
添加记录
-
create方法 与 save方法 区别:
用于创建新的对象,两者都可以。如果用于更新现有对象,只能用save
- save方法:
- 先执行类实例化具体对象的操作,执行save方法后才将对象存储到数据库中
- 不仅用于创建对象,还能用于更新对象的现有数据。在数据库层总是先执行update,找不到具体对象再insert
sunny = Author(name='sunny', age='20') sunny.save()
- create方法:
- 只能用于创建新的对象。在数据库层总是执行insert操作
- save方法:
-
没有关联的表的记录的添加与单表操作一样:
sunny_author_detail = Book.object.create(name='sunny', age='10')
-
一对多,多的表中添加关联字段:
-
一对一,与一对多添加一致
# 书籍:多 出版社:一 # 第一种方法,字段+_id直接赋值,推荐:直观 python_book = Book.object.create(title='python', price='100', publish_id=1) # 第二种方法,先在publish表中查询出model对象,再赋值 # 语法: # publish = pub_obj pub_obj = Publish.object.filter(title='python').first() # 取得model对象 python_book = Book.object.create(title='python', price='100', publish=pub_obj) # django会多出翻译的过程 # 赋值关联字段记录,关联属性publish会被翻译成publish_id, pub_obj会取出主键id值 赋值给publish_id
-
多对多:
多对多,orm自动创建的第三张表,如 书籍 与 作者 book <---> author, 第三张表:app01_book_authors
添加方式:
- 方式一:表的关联属性.add(第一张表记录对象, 第二张表记录对象)
- 方式二: 表的关联属性.add(第一张表记录的主键id, 第二张表记录的主键id),book.authors.add(1, 2)
- 方式三: 表的关联属性.add(*[1, 2]), 参数打散方式, 使用最多, book.authors.add(*[1,2])
# 先查询需要添加记录的表记录 book_python = Book.objects.filter(title='python').first() author_sunny = Author.objects.filter(name='sunny').first() author_vickey = Author.objects.filter(name='ethan').first() # 第三张表添加关联字段 # 第一种方式: book_python.authors.add(author_sunny, author_vickey) # 第二种方式: book_python.authors.add(1, 2) # 第三种方式: 使用最多,因为绑定键值一般是用户指定 book_python.authors.add(*[1, 2]) ''' book_python.authors 会找到Book与Author的关系表 app01_book_authors: orm 会自动将 book_python的id 添加到 app01_book_authors 中的book_id 自动将 author_sunny author_vickey的id 添加到 app01_book_authors 中的 author_id '''
解除方式:
-
解除单个键:表的关联属性.remove(关联表的记录对象或id)
# 解除作者:sunny 与 book:python 的绑定关系, 会在第三张表中删除此记录 author_sunny = Author.objects.filter(name='sunny').first() book_python = Book.objects.filter(title='python').first() # book_python = Book.objects.filter(nid=1).first() book_python.authors.remove(author_sunny) # book_python.authors.remove(1)
-
解除该记录所有绑定: 表的关联属性.clear()
book_python = Book.objects.filter(nid=1).first() # 删除书籍python在第三张表中所有绑定的记录 book_python.authors.clear()
-
解除再绑定: 表的关联属性.set(id)
book_python = Book.objects.filter(nid=1).first() book_python.authors.set(2) """ 等价于: book_python.authors.clear() book_python.authors.add(2) """
-
-
查询记录:
关联属性所在的表 发起的查询为 正向查询
基于对象的跨表查询(基于子查询)
先进行父查询,再对父查询的结果进行子查询
在SQL语言中,一个SELECT-FROM-WHERE语句称为一个查询块。当获得一个查询的答案需要多个步骤的操作,首先必须创建一个查询来确定用户不知道但包含在数据库中的值,将一个查询块嵌套在另一个查询块的WHERE字句或HAVING短语的条件中查询块称为子查询或内层查询
-
一对多:
正向查询: 通过关联属性所在的表("多") 查 关联表("一")记录 (book_obj.publish)
按属性(字段)查:Book对象.关联属性(字段)
Book -----------------------------------------------> Publish
<-----------------------------------------------
反向查询: 按关联属性(字段)所在的表名小写+_set.all(): Publish对象.book_set.all()- 返回QuerySet对象
-
多对多:
正向查询: 按属性(字段) book_obj.authors.all() (多对多可能包含多个记录)
Book ---------------------------------------------------------------------> Author
<---------------------------------------------------------------------
反向查询: 关联属性所在的表名小写+_set.all() author_obj.book_set.all() queryset 对象 -
一对一:
正向查询:按属性(字段) author_obj.author_detail (因为是唯一的记录 不加.all())
Author --------------------------------------------------------------------> AuthorDetails
<--------------------------------------------------------------------
反向查询: 关联属性所在的表名小写 authordetails_obj.author.age (唯一记录,不加_set)
def query(request): # # ############### 基于对象的跨表查询 #################### # 先进行父查询,在对父查询的结果进行子查询 # 一对多: # 基于对象的查询, 多的表中的关联属性 即对应的表的对象 # # 1. 查询python这本书的出版社和邮箱 ------> 正向查询 python_book = Book.objects.filter(title='python').first() # python_book.publish 与这本书关联的出版社对象 # 即 publish_obj = Publish.objects.filter(nid=python_book_id).first() 对象 print(python_book.publish) print(python_book.publish.name) print(python_book.publish.email) # # 2. 查询苹果出版社所有书籍的名称 -------> 反向查询 pub_obj = Publish.objects.get(name='苹果出版社') print(pub_obj.book_set.all()) # QuerySet对象 book_obj_lst = pub_obj.book_set.all().values('title', 'price', 'pub_date') for book in book_obj_lst: for k, v in book.items(): print(f'{k}:{v}') # 多对多: # # 1. 查询python这本书的作者的年龄 -------> 正向查询 python_book = Book.objects.filter(title='python').first() ret = python_book.authors.all() # 与这本书关联的所有作者的QuerySet对象 ret = python_book.authors.all().values('age') print(ret) # # 2. 查询sunny出版过的所有书籍名称 --------> 反向查询 sunny_author = Author.objects.filter(name='sunny').first() ret = sunny_author.book_set.all() print(ret) # 一对一: # # 1. 查询作者名字为sunny的手机号 ----------> 正向查询 sunny = Author.objects.get(name='sunny') sunny_tel = sunny.author_detail.tel print(sunny_tel) # # 2. 查询手机号为10086的作者名字 ---------> 反向查询 tel_obj = AuthorDetails.objects.get(tel='10086') author_obj = tel_obj.author.name print(author_obj) # return HttpResponse(publish_python.name + publish_python.email, charset='gbk') return HttpResponse('ok')
-
基于双下划线的查询(基于sql jion实现 即 表的拼接 )
将多张表拼接成单张表,再进行相关的查询
从基表出发,按关联关系进行拼表, 先拼接有关系的表
拼表后会按"多"的表记录进行显示,如Book表的一本书有2个作者,拼表后会有2条同样书籍对象但作者不一样的记录
-
正向查询:按子段
-
反向查询: 按表名小写
# ############### 关联表查询 ################## # filter: sql语句中where过滤的条件, values: sql语句中select要显示的字段 # 一对多: # 1. 查询python这本书的出版社和邮箱 # 以Book为基表 拼接 Publish表 ret = Book.objects.filter(title='python').values('publish__name', 'publish__email') # 以Publish为基表 拼接 Book表 ret = Publish.objects.filter(book__title="python").values('name', 'email') # 2. 查询苹果出版社所有书籍的名称 ret = Publish.objects.filter(name='苹果出版社').values('book__title') ret = Book.objects.filter(publish__name='苹果出版社').values('title') # 多对多: 连接3张表, Book Author, orms自动拼接第三张表 book_authors # 1. 查询python这本书的作者的年龄 ret = Book.objects.filter(title='python').values('authors__age') # 正跨: 通过book属性authors连接第三张表 ret = Author.objects.filter(book__title='python').values('age') # 反跨: 通过表名小写自动连接第三张表 # 2. 查询少商出版过的所有书籍名称 ret = Author.objects.filter(name='少商').values('book__title') ret = Book.objects.filter(authors__name='少商').values('title') # 一对一: # # 1. 查询作者名字为少商的手机号 ret = Author.objects.filter(name='少商').values('author_detail__tel') ret = AuthorDetails.objects.filter(author__name='少商').values('tel') # 2. 查询手机号为10086的作者名字 ret = Author.objects.filter(author_detail__tel='10086').values('name') ret = AuthorDetails.objects.filter(tel='10086').values('author__name') # ############# 连续跨表查询 #################### # 查询苹果出版社出版过的所有书籍的名字以及作者的姓名 ret = Book.objects.filter(publish__name='橘子出版社').values('title', 'authors__name') ret = Publish.objects.filter(name='橘子出版社').values('book__title', 'book__authors__name') ret = Author.objects.filter(book__publish__name='橘子出版社').values('name', 'book__title') ret = AuthorDetails.objects.filter(author__book__publish__name='橘子出版社').values('author__book__title', 'author__name') # 手机号以100开头的作者出版过的所有书籍名称以及出版社名称 ret = Book.objects.filter(authors__author_detail__tel__startswith=100).values('title', 'publish__name') ret = Author.objects.filter(author_detail__tel__startswith=200).values('book__title', 'book__publish__name') ret = Publish.objects.filter(book__authors__author_detail__tel__startswith=200).values('book__title', 'name') # author__book__title 按关联关系进行拼接 ret = AuthorDetails.objects.filter(tel__startswith=100).values('author__book__title', 'author__book__publish__name')
聚合与分组查询(跨表基于join)
-
聚合函数: Count, Sum, Avg, Max, Minfrom django.db.models import Count, Sum, Avg, Max, Min**
支持聚合函数的方法:
- aggregate(c=Count(1)) 返回字典
- annotate(c=Count(1)) 返回QuerySet
from django.db.models import Count, Sum, Avg, Max, Min # 导入聚合函数 # ############ 聚合查询 ############ # 1. Avg: 查询所有书籍的平均价格 # sql: select avg(price) from book ret = Book.objects.all().aggregate(Avg('price')) # 返回字典, {'price__avg': Decimal('88.000000')} # 起别名 ret = Book.objects.all().aggregate(priceAvg=Avg('price')) # {'priceAvg': Decimal('88.000000')} # 2. Count: 查询所有书籍的个数 ret = Book.objects.all().aggregate(c=Count('nid')) # Count(1) 也可以 print(ret)
-
分组查询:
支持分组函数的方法: annotate() 返回QuerySet即 对象的列表,如果是values返回包装成字典的对象列表
语法: Book.objects.values('title').annotate(bookCount=Count(1))
sql: select title, count(1) from book group by title
ps: 单表按主键分组没有意义
语法解析: values(): select与group by的字段, annotate():group by统计(分组统计)
# ############ 分组查询 annotate############ # ######### 单表分组查询 # 1. 查询书籍表每一个出版社id以及对应的书籍的个数 # values(): select & group by. annotate():统计 # 返回QuerySet # sql: select publish_id, count(1) from book GROUP BY publish_id ret = Book.objects.values('publish_id').annotate(c=Count(1)) # <QuerySet [{'publish_id': 1, 'c': 2}, {'publish_id': 2, 'c': 2}, {'publish_id': 3, 'c': 1}]> # emp员工表查询 # 1. 查询每一个部门的名称以及对应员工的平均薪水 ret = Emp.objects.values('dep').annotate(depAvg=Avg('salary')) # group by dep # 2. 查询每一个省份的名称以及对应的员工人数 ret = Emp.objects.values('pro').annotate(empCount=Count(1)) # 3. 查询每一个省份的名称以及对应的员工最大年龄 ret = Emp.objects.values('pro').annotate(maxAge=Max('age')) # ############# 跨表分组查询 ################## # 相关表join成一张表,再group by # 1. 查询每个出版社的名称以及对应的书籍的个数 ret = Book.objects.values('publish__name').annotate(bookCount=Count(1)) ret = Publish.objects.values('name').annotate(bookCount=Count('book__title')) # 2. 查询每个作者的名字以及出版的书籍的最高价格 # 作者可能有重复的名称且不是一个人,所以要按主键和name进行分组 # 如果按多个字段分组, 如果多个字段的值完全相同则放入同一个组,有一个字段不相等则放入一个新组 ret = Author.objects.values('pk', 'name').annotate(maxPrice=Max('book__price')) # 3. 查询每个作者出版的书籍的名称和个数 ret = Book.objects.values('title').annotate(Count('authors'))
-
常用方法及其注意事项:
# #### 重要 ### # 如果基表字段有重复值, 分组会将2个相同字段分为一个组 即 values按字段分组会可能会有不符合预期的逻辑错误 # all() 即按主键分组,即每行表记录都是一个分组的依据 # 如:Book表的title有一个重复的书籍名称'python学习手册',group by title 会把重复的书籍汇总到一个组 ret = Book.objects.values('title').annotate(Count('authors')) # 4条记录: <QuerySet [{'title': 'python学习手册', 'authors__count': 4}, {'title': 'python标准库', 'authors__count': 3}, # {'title': 'JAVA编程思想', 'authors__count': 1}, {'title': 'linux私房菜', 'authors__count': 2}]> print(ret) ret = Book.objects.all().annotate(Count('authors')) # 5条记录: <QuerySet [<Book: python学习手册>, <Book: python标准库>, <Book: JAVA编程思想>, <Book: linux私房菜>, <Book: python学习手册>]> print(ret) # ################ 分组查询常用方法 ################### # 用all()方法返回的model对象的列表(QuerySet),在model对象自动加入了统计字段authors_count,可用对象.统计字段显示 # 并且可在此QuerySet对象基础上用其他QuerySet可调用的方法进行级联操作 ret = Book.objects.all().annotate(Count('authors')) # 简写: ret = Book.objects.annotate(Count('authors')) # 显示书籍作者大于2个的书籍名称 ret = Book.objects.annotate(Count('authors')).filter(authors__count__gt=2).values('title')
-
F 与 Q查询
# ######## F 与 Q 查询
from django.db.models import F, Q
# ------------ F 查询:(Field) -----------
# 运算符右边可以是字段: 可以两个字段比较 comment=F("thumbs_up")
# 1. 书籍评论数等于点赞数
ret = Book.objects.filter(comment=F("thumbs_up")).values('title', 'comment', 'thumbs_up')
# 2. 书籍评论数大于点赞数
ret = Book.objects.filter(comment__gt=F("thumbs_up")).values('title', 'comment', 'thumbs_up')
# 3. 修改名称 加上版号: 如 python(第二版)
from django.db.models.functions import Concat # Concat 连接
from django.db.models import Value # Value 常量
Book.objects.filter(title__startswith="py").update(title=Concat(F("title"), Value("第二版"), Value("中文版")))
# 4. price提高2倍
# Book.objects.filter(nid=2).update(price=F("price") * 2)
Book.objects.filter(nid=2).update(price=F("price") * Value(2))
# ------------ Q 查询:(Field) -----------
# filter 参数之间都是与操作.
# Q 查询可以进行其他逻辑查询, 并且可以嵌套使用
# & : 与
# 书籍评论数与点赞数都大于100的书籍
ret = Book.objects.filter(Q(comment__gt=100) & Q(thumbs_up__gt=100)).values('title')
# |: 或
# 书籍评论数小于500或点赞数大于800的书籍
ret = Book.objects.filter(Q(comment__lt=500) | Q(thumbs_up__lt=800)).values('title')
# ~: 非
# 书籍评论数不等于1000的书籍
ret = Book.objects.filter(~Q(comment=1000)).values('title')
# 查询函数可以混合使用Q 对象和关键字参数。
# 所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。
# 但是,如果出现Q 对象,它必须位于所有关键字参数的前面。
ret = Book.objects.filter(~Q(comment=1000) | Q(comment__lt=100), title__contains='py').values('title')