多表查询
一, 创建数据库
- from django.db import models
- # Create your models here.
- """
- 你在写 orm 语句的时候 跟你写 sql 语句一样
- 不要想着一次性写完
- 写一点查一点看一点
- """
- class Book(models.Model):
- """
- 1. 一本书对应一个出版社, 出版社与书, 一对多关系,
- 2. 一本书可以多个作者, 一个作者可出多本说 作者与书 多多的关系
- """
- title = models.CharField(max_length=50)
- price = models.DecimalField(max_digits=8, decimal_places=2)
- publish_data = models.DateField(auto_now_add=True) # 出版时间
- """
- auto_now: 每次修改数据的时候 都会自动更新时间
- auto_now_add: 在创建数据的时候 会自动将当前时间记录下来
- 后期如果你不认为修改的话 数据不变
- """
- # 出版社与书, 一对多关系,
- publish = models.ForeignKey(to='Publish') # 外键
- # 作者与书 多多的关系
- authors = models.ManyToManyField(to='Author')
- """
- authors 虚拟字段
- 1. 告诉 orm 自动帮你创建第三张关系表
- 2.orm 查询的时候 能够帮助你更加方便的查询
- """
- def __str__(self):
- return self.title
- class Publish(models.Model):
- name = models.CharField(max_length=50)
- add = models.CharField(max_length=100)
- def __str__(self):
- return self.name
- class Author(models.Model):
- name = models.CharField(max_length=100)
- age = models.IntegerField()
- # 作者与信息一对一关系
- # author_detail = models.ForeignKey(unique=True,to='AuthorDetail')
- author_detail = models.OneToOneField(to='AuthorDetail')
- def __str__(self):
- """return 返回的数据必须是字符串类型"""
- return self.name
- class AuthorDetail(models.Model):
- phone = models.BigIntegerField()
- add = models.CharField(max_length=100)
- def __str__(self):
- return self.add
二, 一对多字段增删改查
主键在书籍中, 外键操作
- # 插入数据
- #1 方式一: 插入数据 (方式一指定外键)
- models.Book.objects.create(title='三国演义', price=123.23, publish_id=1) # publish_id 直接传出版社主键值
- # 2. 方式二: 插入数据, 根据对象
- publish_obj = models.Publish.objects.filter(pk=2).first()
- print(publish_obj)
- models.Book.objects.create(title='水浒传', price=3333, publish=publish_obj)
- # 查
- book_obj = models.Book.objects.filter(pk=4).first()
- """
- 查找到 id=4 的这本书, 通过创建的外键获取对应的出版社,
- 获取的是出版社对象这条记录对象
- """
- print(book_obj)
- print(book_obj.publish) # 获取到当前所对应的出版社对象
- print(book_obj.publish_id) # 获取当前对象的出版社外键
- # 改
- # 1. 修改外键第一种方式, 直接修改
- models.Book.objects.filter(pk=4).update(publish_id=3)
- # 2. 方式二: 通过对象的方式, 修改 (推荐使用)
- publish_obj = models.Publish.objects.filter(pk=4).first()
- models.Book.objects.filter(pk=5).update(publish=publish_obj)
- # 删除, 默认级联删除,(删除多一方, 只会自己的, 删除一的一方, 则会与其相关联的表记录都别删除)
- models.Publish.objects.filter(pk=3).delete()
三, 多对多字段的增删改查
对键的操作
- # 多对多字段的增删改查
- # 1. 给主键为 4 的书籍添加连个作者 3,4 关系
- book_obj = models.Book.objects.filter(pk=4).first()
- print(book_obj.authors) # 相当于, 已经在书籍和作者的关系表中
- print(book_obj.authors.all())
- # 方式一: add 添加 (直接写对应 id),
- book_obj.authors.add(1)
- book_obj.authors.add(3,4)
- # 方式二: add 添加 (通过对象的方式添加)
- author_obj = models.Author.objects.filter(pk=1).first()
- author_obj1 = models.Author.objects.filter(pk = 3).first()
- book_obj.authors.add(author_obj)
- book_obj.authors.add(author_obj, author_obj1)
- """
- 总结;
- add(): 括号内可以传数字, 也可以传数据对象, 并且支持多个数据对象
- """
- # 修改关系
- # 方式一: set 修改 (直接写对应 id),
- book_obj = models.Book.objects.filter(pk=4).first()
- book_obj.authors.set([3, ]) # 修改一个
- book_obj.authors.set([3, 4, ]) # 修改多个
- # 方式二: set 修改 (通过对象的方式),
- author_obj = models.Author.objects.filter(pk=3).first()
- author_obj1 = models.Author.objects.filter(pk=1).first()
- book_obj.authors.set((author_obj,))
- book_obj.authors.set((author_obj,author_obj1))
- """
- 总结:
- set(): 括号内可以传数字也可以穿对象, 并且支持多个对象
- 需要注意的是, 括号内必须是可迭代对象, 使用直接修改的关系
- """
- # 删除对应关系
- book_obj = models.Book.objects.filter(pk=4).first()
- # 方式一: remove 修改 (直接写对应 id),
- book_obj.authors.remove(4) # 移除一个
- book_obj.authors.remove(4, 5) # 移除多个
- # 方式二: remove 修改 (通过对象的方式)
- author_obj = models.Author.objects.filter(pk=1).first()
- author_obj1 = models.Author.objects.filter(pk=3).first()
- book_obj.authors.remove(author_obj) # 通过对象删除一个
- book_obj.authors.remove(author_obj, author_obj1) # 通过对象删除两个
- """
- 总结:
- remove() 括号内 既可以传数字也传对象, 并且也是支持传多个的
- """
- # 清空对应关系 clear()
- book_obj = models.Book.objects.filter(pk=4).first()
- book_obj.authors.clear()
- """
- 总结:
- clear(): 括号内不需要传任何参数, 直接清空当前书籍对象的所有记录
- """
总结:
add(): 括号内可以传数字, 也可以传数据对象, 并且支持多个数据对象
set(): 括号内可以传数字也可以穿对象, 并且支持多个对象, 需要注意的是, 括号内必须是可迭代对象, 使用直接修改的关系
remove() 括号内 既可以传数字也传对象, 并且也是支持传多个的
clear(): 括号内不需要传任何参数, 直接清空当前书籍对象的所有记录
四, 子查询和连表查询
- """
- ORM 跨表查询:
- 1. 子查询
- 2. 连表查询
- 正反向的概念: 查询是外键字段在那个表中, 有外键表查询则是正向, 否则反向
- 书籍对象 查 出版社 外键在书籍中 查询到方式是正向查询
- 出版社 查 书籍 外键在书籍中 查询的方式是反向查询
- 正向查询根据设置关系字段查询
- 反向查询按照表名小写的方式查询
- """
- # 1. 基于对象跨表查询, 子查询, 正向查询
- # 1> 查询书籍是 python 入门的出版社名称 (正向查询)(一对多)
- book_obj = models.Book.objects.filter(title='python 入门').first()
- print(book_obj.publish.name) # 根据建的关系, 正向查询按字段
- print(book_obj.publish.add) # 根据建的关系, 正向查询按字段
- # 2>. 查询书籍主键是 5 的作者姓名 (多对多)
- book_obj = models.Book.objects.filter(pk=5).first()
- # 根据建的关系, 正向查询按字段
- print(book_obj.authors) # app01.Author.None
- print(book_obj.authors.all()) # <QuerySet [<Author: randy>, <Author: laowang>]>
- # 3>. 查询作者是 randy 的手机号 (一对一关系)
- autor_obj = models.Author.objects.filter(name='randy').first()
- print(autor_obj.author_detail.phone)
- print(autor_obj.author_detail.add)
- """
- 总结正向查询:
- 当查找字段有多个数据时, 需要通过 .all() 获取数据
- 或者. 外键字段直接就能够拿到数据对象
- """
- # 2. 基于对象跨表查询, 子查询, 反向查询
- # 1>. 查询出版社是安徽出版社出版过的书籍 (一对多)
- publish_obj = models.Publish.objects.filter(name='安徽出版社').first()
- print(publish_obj.book_set) # app01.Book.None 基于类名小写查询
- print(publish_obj.book_set.all()) # 获取值
- # 2>. 查询作者是 randy 写过的所有的书 (多对多)
- author = models.Author.objects.filter(name='randy').first()
- print(author.book_set) # app01.Book.None 基于类名小写查询
- print(author.book_set.all())
- # 3>. 查询手机号是 124 的作者 (一对一)
- author_detail_obj = models.AuthorDetail.objects.filter(phone=124).first()
- print(author_detail_obj.author) # app01.Book.None 基于类名小写查询
- print(author_detail_obj.author.name)
- print(author_detail_obj.author.age)
- # 4>. 查询书籍是 python 入门的作者的手机号
- book_obj = models.Book.objects.filter(title='python 入门').values('authors__author_detail__phone')
- print(book_obj)
- """
- 反向查询总结:
- 1. 按照表名小写;
- 2. 在一对多和多对多的时候使用表名_set, 获取多个值需要表名_set.all()
- 3. 在一对一时候不需要表名_set, 只需要表名就可以获取值, 不需要 all() 获取, 直接表名小写即可
- """
- # 3. 基于双下划的跨表查询 连表查询
- """
- MySQL
- left join
- inner join
- right join
- union
- """
- # 1. 查询书籍是 python 入门的出版社名称 (一对多)
- book_obj = models.Book.objects.filter(title='python 入门').fiirst() # 子查询增产改查 rst()
- # 正向查询, 按照外键__字段名
- book_obj = models.Book.objects.filter(title='python 入门').values('publish__name')
- print(book_obj)
- # 反向查询, 按照类名小写_字段名查找
- publish_obj = models.Publish.objects.filter(book__title='python 入门').values('name')
- print(publish_obj)
- # 2. 查询作者是 randy 的手机号码 (一对一关系)
- # 正向查询方法, 通过关键字
- author = models.Author.objects.filter(name='randy').values('author_detail__phone')
- print(author)
- # 反向查询, 按照类名小写_字段名查找
- autoor_detail = models.AuthorDetail.objects.filter(author__name='randy').values('phone')
- print(autoor_detail)
- # 3. 查询手机号是 124 的作者姓名 (一对关系)
- # 反向查询, 通过表名
- author_detail = models.AuthorDetail.objects.filter(phone='124').values('author__age')
- print(author_detail)
- # 正向查询方法, 通过关键字
- autor_obj = models.Author.objects.filter(author_detail__phone=124).values('name')
- print(autor_obj)
- # 4. 查询出版社是安徽出版社出版的书籍名称 (多对多关系)
- # 反向查询
- publish_obj = models.Publish.objects.filter(name='安徽出版社').values('book__title')
- print(publish_obj)
- # 正向查询方法, 通过关键字,(多对多不能实现正向查询)
- book_obj = models.Book.objects.filter(publish__book__title='安徽出版社').values('title')
- print(book_obj)
- # 5. 查询作者是 randy 的写过的书的名字和价格 (一对多的关系)
- autor_obj = models.Author.objects.filter(name='randy').values('book__title', 'book__price')
- print(autor_obj)
子查询总结 (给谁先查谁):
正向查询: 当查找字段有多个数据时, 需要通过 .all() 获取数据, 或者. 外键字段直接就能够拿到数据对象 (正向查询按字段)
反向查询总结:
按照表名小写;
在一对多和多对多的时候使用表名_set, 获取多个值需要表名_set.all()
在一对一时候不需要表名_set, 只需要表名就可以获取值, 不需要 all() 获取, 直接表名小写即可
连表查询总结:
正向查询: 正向查询按照字段, 先给什么, 就从那里查通过 values 获取值, 通过表中外键双下划 -- 获取与之关联的表
反向查询:
表名小写, 双下划线获取字段名, 只要表中还有关键字段还可以一直查询下去
五, 聚合查询
- """
- 聚合查询
- 关键字: aggregate
- """
- # 导包
- from django.db.models import Avg, Max, Min, Sum, Count
- # 使用 aggregate 关键字
- sum1 = models.Book.objects.all().aggregate(Sum('price'))
- print(sum1)
- print(int(sum1.get("price__sum")))
- avg1 = models.Book.objects.all().aggregate(Avg('price'))
- print(avg1)
- max1 = models.Book.objects.all().aggregate(Max('price'))
- print(max1)
- min1 = models.Book.objects.all().aggregate(Min('price'))
- print(min1)
- count1 = models.Book.objects.all().aggregate(Count('price'))
- print(count1)
- res = models.Book.objects.all().aggregate(Sum('price'), Count('price'), Avg('price'))
- print(res)
- # SELECT SUM(`app01_book`.`price`) AS `price__sum`, COUNT(`app01_book`.`price`) AS `price__count`, AVG(`app01_book`.`price`) AS `price__avg` FROM `app01_book`
六, 分组查询
- """
- 分组查询 (根据某一字段进行查询), 与聚合函数一起连用
- 关键字: annotate
- """ # 1. 统计每一本书的作者个数, annotate(author_num=Count('authors')), 按照里面聚合函数里面的参数进行分组
- ## 每一本书, 那么根据书来查
- res = models.Book.objects.annotate(author_num=Count('authors')).values('author_num')
- # sql, 内连接之后排序 (表), 根据外键进行排序
- # SELECT COUNT(`app01_book_authors`.`author_id`) AS `author_num` FROM `app01_book` LEFT OUTER JOIN `app01_book_authors` ON (`app01_book`.`id` = `app01_book_authors`.`book_id`) GROUP BY `app01_book`.`id` ORDER BY NULL LIMIT 21; args=()
- # res = models.Book.objects.annotate()
- #
- print(res)
- #2. 统计出每个出版社卖的最便宜的书的价格
- # 每个出版社, 则查出版社, 在选择字段进行分组
- # 满足反向查询表名小写加字段
- res = models.Publish.objects.annotate(min_price=Min('book__price')).values('min_price')
- # sql: SELECT MIN(`app01_book`.`price`) AS `min_price` FROM `app01_publish` LEFT OUTER JOIN `app01_book` ON (`app01_publish`.`id` = `app01_book`.`publish_id`) GROUP BY `app01_publish`.`id` ORDER BY NULL LIMIT 21; args=()
- # 结果:<QuerySet [{'min_price': Decimal('2222.00')}, {'min_price': None}, {'min_price': Decimal('123.23')}]>
- print(res)
- # 3. 统计不止一个作者的图书
- """
- 1. 统计每本书对应的作者个数
- 2. 基于上面的结果 筛选出作者个数大于 1 的
- """res = models.Book.objects.annotate(authors_num=Count('authors')).filter(authors_num__gt=1).values('authors_num')
- print(res)
- # 4. 查询各个作者出的书的总价格
- res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('sum_price')
- print(res)
总结: 题目根据每个字眼进行分组, 分组都与聚合函数一起连用, 支持正向查询和反向查询的规则, 注意的是, 作为查询都是双下划线.
七, F 与 Q 查询
我们之前在查询数据库的时候条件都是我们自己手写的, 但是现在出现了条件是从数据库里面获取的
导包: from diango.db.models import F, Q
1. F 查询
- """
- F 与 Q 查询:
- 我们之前在查询数据库的时候条件都是我们自己手写,
- 但是现在出现了条件字段是从数据库里面字段获取的
- """
- # 1. 查询出卖出数大于库存数的书籍
- res = models.Book.objects.filter(maichu__gt=F('kucun'))
- print(res)
- # 2. 将所有的书的价格 全部提高 100 块
- res = models.Book.objects.all().update(price=F('price') + 100)
- print(res)
- # 3. 了解 尝试着将所有的书的名字后面都加上 爆款
- res = models.Book.objects.update(title=Concat(F('title'), Value('p')))
- print(res)
注意: 获取的条件是数据库中对应的字段
2. Q 查询
- "Q 查询"
- # # 1. 查询书籍名称是 python 入门或者价格是 544.44 的书
- res = models.Book.objects.filter(title='python 入门 p', price=2422).values('title')
- print(res)
- # # 逗号是 and 关系
- res1 = models.Book.objects.filter(Q(title='python 入门 p'), Q(price=2422)).values('title')
- print('res1', res1)
- #
- # # 用来 Q 之后 就能够支持 | 表示或
- res2 = models.Book.objects.filter(Q(title='水浒传 p') | Q(price='2422'))
- print(res2)
- #
- # # esc 下面那个键 波浪号 表示非
- res3 = models.Book.objects.filter(~Q(title='水浒传 p') | Q(price='2422'))
- print(res3)
- # Q 进阶用法, 用 Q 产生对象, 然后在使用, 主要用于在通过前端获取条件
- q = Q()
- # q.conngdector = 'or'
- # 添加条件
- q.children.append(('title__icontains', 'p'))
- res_q = models.Book.objects.filter(q)
- print(res_q)
- q.children.append(('kucun', 800))
- res_q1 = models.Book.objects.filter(q)
- print(res_q1)
总结:
字段对应数据库中字段, 支持与或非
可以从前端获取条件查询
字符串的左边 跟你的变量名条件书写一模一样
八, 惰性查询
- # 惰性查询
- # 一条 sql 语句
- res = models.Book.objects.all()
- print(res)
- # 一条 sql 语句
- res1 = models.Book.objects.values('title')
- print(res1)
- # 查询指定一个数据库中字段一条语句
- res_only = models.Book.objects.only('title')
- # print(res2)
- for r in res_only:
- # 执行一条语句, 获取数据库中所有字段
- print(r.title)
- # 每次都要到数据库中查询一次
- print(r.price)
- """
- only 会将括号内的字段条件查找的结果, 直接封装到返回给你的对象中,
- 通过. 点的方式获取字段值的时候, 则不需要再走数据库, 在查一遍
- 一旦你. 点了不是括号内的字段, 就会频繁的去到数据库中查询该字段的值
- """
- # defer 和 only 互为反关系
- res_defer = models.Book.objects.defer('title')
- # 有几条数据执行几条 sql 语句, 查询 title 字段
- print(res_defer)
- res_defer2 = models.Book.objects.defer('title')
- for r in res_defer2:
- # 有几条数据执行几条数据
- print(r.title)
- print(r.price)
- """
- defer: 会将括号内的字段之外的所有数据库中字段, 将查询结果返回给结果对象,
- 当通过. 该其他字段的时候, 不需要再去数据库中查询,
- 一旦你点了括号内的字段, 就会频繁的去到数据库中查询
- """
总结:
1.only:
only 会将括号的条件字段查找的结果 (全部数据), 直接封装返回到对象中, 只需要一条 sql 语句
当通过的对象. 点的方式循环获取条件字段本身时候就不会再次查询数据库, 一旦通过. 点的方式循环获取其他字段时候, 就会频繁访问数据库
2.defer:
defer 会将括号内字段中的条件全部查询出来, 有几条数据就要查询数据库几次, 还会额外的将数据库中所有记录查询出来返回给对象
当通过. 点的方式循坏获取条件字段的本身时候会频繁访问数据库, 一旦通过. 点的方式循环获取其他字段, 就不需要去到数据库中去查询
九, select_related 和 prefetch_related
- # select_related 和 prefetch_related
- # publish 外键, 一对多的关系, 连表查询
- res_select1 = models.Book.objects.select_related('publish')
- print(res_select1)
- # author_detail 外键, 一对一关系, 连表查询
- res_select2= models.Author.objects.select_related('author_detail')
- print(res_select2)
- # 多对多的关系, 会报错
- res_select3 = models.Book.objects.select_related('authors')
- print(res_select3)
- for r in res_select2:
- print(r.author_detail) # 对象
- print(r.author_detail.phone)
- print(r.author_detail.add)
- """
- select_related: 会自动根据表中外键做连表操作, 然后将连表之后的数据查询出来封装给对象
- select_related: 括号内只能放外键字段, 并且多对多字段不能存放
- 如果括号内外键字段所有所关联的表中还有外键字段, 还可以继续连表查询
- select_related('外键字段__外键字段__外加字段....')
- 根据外键进行连表查询
- """
- # prefetch_related
- res_prefetch = models.Book.objects.prefetch_related('publish')
- for r in res_prefetch:
- print(r.publish.name)
- """
- prefetch_related, 根据查询语句 sql, 看似是连表查询, 其实是类似子查询,
- prefetch_related: 括号内只能放外键字段, 并且多对多字段不能方法
- 如果括号内外键字段所关联的表中还有外键字段, 可以继续连表查询
- prrefetch_related('外键字段__外键字段__外键字段....')
- """"""
- 两者区别:
- select_related: 内部会自动连表, 消耗的资源就在连表上, 但是走数据库的次数较少
- prefetch_related: 内部不做连表查询, 消耗的资源在查询次数上, 但是给用户的感觉跟连表操作一样
- """
总结:
1.select_related:
会自动根据表中外键字段做连表查询, 然后将连表之后的数据查询出来封装给对象
括号内只能放外键字段, 并且多对多外键不能查询
如若括号内外键字段所在的关联表中还有外键字段, 还可以继续连表查询 select_related('外键字段__外键字段__外加字段....')
根据外键进行连表查询
2.prefetch_related
根据外键查询, 看似是连表查询, 其实是类似子查询
如果括号内外键字段所关联的表中还有外键字段, 可以继续连表查询, rrefetch_related('外键字段__外键字段__外键字段....')
根据外键子查询
3. 两者区别
select_related: 内部会自动连表, 消耗的资源就在连表上, 但是走数据库的次数较少
prefetch_related: 内部不做连表查询, 消耗的资源在查询次数上, 但是给用户的感觉跟量表操作一样
九, 事务
- # 在 django 中开启事务
- from django.db import transaction
- with transaction.atomic():
- # 在该代码块中所写的 ORM 语句, 同属于一个事务
- pass
- # 缩进出来之后自动结束
十, 自定义字段
- # 自定义 char 类型字段
- class MyCharField(models.Field):
- def __init__(self, max_length, *args, **kwargs):
- self.max_length = max_length
- super().__init__(max_length=max_length, *args, **kwargs)
- def db_type(self, connection):
- return 'char(%s)' % self.max_length
来源: http://www.bubuko.com/infodetail-3259953.html