多表查询

Posted randysun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多表查询相关的知识,希望对你有一定的参考价值。

多表查询

一、创建数据库

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(): 括号内不需要传任何参数,直接清空当前书籍对象的所有记录
"""

总结:

  1. add():括号内可以传数字,也可以传数据对象,并且支持多个数据对象

  2. set():括号内可以传数字也可以穿对象,并且支持多个对象,需要注意的是,括号内必须是可迭代对象,使用直接修改的关系

  3. remove() 括号内 既可以传数字也传对象,并且也是支持传多个的

  4. 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)

子查询总结(给谁先查谁):

  1. 正向查询: 当查找字段有多个数据时,需要通过 .all()获取数据, 或者.外键字段直接就能够拿到数据对象(正向查询按字段)
  2. 反向查询总结:
    • 按照表名小写;
    • 在一对多和多对多的时候使用表名_set,获取多个值需要表名_set.all()
    • 在一对一时候不需要表名_set,只需要表名就可以获取值,不需要all()获取,直接表名小写即可

连表查询总结:

  1. 正向查询:正向查询按照字段,先给什么,就从那里查通过values获取值,通过表中外键双下划--获取与之关联的表
  2. 反向查询:
    • 表名小写,双下划线获取字段名,只要表中还有关键字段还可以一直查询下去

五、聚合查询


"""
聚合查询
关键字: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

# 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

以上是关于多表查询的主要内容,如果未能解决你的问题,请参考以下文章

python flask(多对多表查询)

MyBatis多表联查

phpcms v9后台多表查询分页代码

thinkphp3.1 多表联合查询代码

mysql 多表联合查询啥用

19.Sqlite多表连接查询及聚合函数的应用