Django - ORM 数据库操作 - 多表操作

目录

一、建立多表关系结构

二、一对多 and 一对一 表关系的记录操作

1、 增(两种方式)

方式一、create方式创建,指定字段名传输数据

方式二、获取外键表对象id,给类定义的关系变量,赋值查询到的对象

2、改(两种方式)

方式一、获取对象,修改后save方法保存

方式二、update方法更新

三、多对多 表关系的记录操作

1、增 add()

2、删除 remove()

3、清空 clear()

4、先清空后创建 set() -- 必须传值列表

 四、基于对象的跨表查询

1、一对一的跨表查询

1)正向查询(类内联系变量名查询)

2)反向查询(表名小写查询)

2、一对多的跨表查询

1)正向查询(类内联系变量名查询)

2)反向查询(表名小写_set.all())

3、多对多的跨表查询

1)正向查询(类内联系变量名.all())

2)反向查询(表名小写_set.all())

4、连续跨表查询

五、基于双下划线的查询:联系表__联系表内字段名

1、一对一的查询

2、一对多的查询

3、多对多的查询

4、连续跨表查询

六、聚合查询(aggregate() + 聚合函数(Avg,Max,Min…))

七、分组查询(annotate() + 聚合函数(Avg,Max,Min…))

- filter、annotate、values的使用顺序,及不同

八、F查询(对同一个model实例中的两个不同字段进行比较)

- models新增(book标下的阅读数字段和评论数字段)

 - 同一个model实力类中的字段比较

- 字段值的修改(加减乘除取模等操作)

九、Q查询(操作复杂的逻辑运算(与或非))


一、建立多表关系结构

总结:

  • 一对一关系:OneToOneField
  • 一对多关系:ForeignKey
  • 多对多关系(自动生成中间表):ManyToManyField
  • 用了OneToOneField和ForeignKey,模型表的外键字段,默认末尾添加_id
  • 表的名字 myapp_modelName 自动生成,但是可以覆写为其他名字。
  • 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
  • 主键字段使用primary_key,调用时可以使用简写‘pk’
from django.db import models


# 出版社数据表
class Publish(models.Model):

    # id如果不写,会自动生成,名字叫nid,并且自增
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()


# 作者表,与作者详细信息表建立一对一的关系(OneToOneField)
class Author(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    # 数字类型
    sex = models.IntegerField()
    # 可以用 ForeignKey(如下),但是得设置唯一性约束,会报警告,不建议用,建议用OneToOneField
    # authordetail= models.ForeignKey(unique=True)

    # to='AuthorDetail'  使用引号,表存在即可;不适用引号,数据表类必须在此类之前定义,才能被找到
    # 与表 AuthorDetail 的 id字段建立一对一的关系
    authordetail = models.OneToOneField(to='AuthorDetail', to_field='id')

    def __str__(self):
        return self.name


# 作者信息详细表,被作者表建立一对一的关系
class AuthorDetail(models.Model):
    id = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)


# 书籍表,和作者表建立多对多的关系(ManyToManyField) - 自动生成中间表 book_authors
# 与出版社表建立一对多关系(ForeignKey)
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5, decimal_places=2)

    # 一对多
    publish = models.ForeignKey(to=Publish, to_field='id')

    # 多对多; to 后不加引号,指引类必须在此类前定义
    authors = models.ManyToManyField(to=Author)

    # 重写此类被print的输出内容
    def __str__(self):
        return self.name

 

二、一对多 and 一对一 表关系的记录操作

注意:一对多和一对一的数据记录操作方式相同

1、 增(两种方式)

方式一、create方式创建,指定字段名传输数据

# creat创建数据,将值传输给表内字段名publish_id
ret=Book.objects.create(name='红楼梦',price=34.5,publish_id=1)

方式二、获取外键表对象id,给类定义的关系变量,赋值查询到的对象

# 获取出版社表,id为1的对象
publish=Publish.objects.get(id=1)

# 同上获取 出版社表对象 pk是主键,通过主键查找
publish=Publish.objects.get(pk=1)

# 获取出版社表对象,主键id=2的第一条数据对象,因为filter返回queryset对象
publish = Publish.objects.filter(pk=2).first()


# creat对象,将获取的出版社表对象赋予 book类内提前建立表关系的变量名
# publish = models.ForeignKey(to=Publish, to_field='id')
ret = Book.objects.create(name='西游记', price=34.5, publish=publish)
print(ret.name)

2、改(两种方式)

方式一、获取对象,修改后save方法保存

# 获取book对象
book=Book.objects.get(pk=1)

# book.publish=出版社对象 --- 同下
book.publish_id=2
book.save()

方式二、update方法更新

book=Book.objects.filter(pk=1).update(publish = 出版社对象)
book=Book.objects.filter(pk=1).update(publish_id = 1)

三、多对多 表关系的记录操作

1、增 add()

# 为红楼梦这本书新增一个叫n1,n2的作者

# 获取author表内的数据对象 n1 对应id = 1,n2 对应id =2
n1=Author.objects.filter(name='n1').first()
n2=Author.objects.filter(name='n2').first()

# 获取书籍对象
book=Book.objects.filter(name='红楼梦').first()

# add 添加单个对象
# book类.authore变量(建立多对多联系的变量).add(对象1)
book.authors.add(n1)

# add 添加多个对象
# book类.authore变量(建立多对多联系的变量).add(对象1,对象2)
book.authors.add(n1,n2)

# add 添加作者id
# book类.authore变量(建立多对多联系的变量).add(准确内容)
book.authors.add(1,2)

2、删除 remove()

#可以传对象,可以传id,可以传多值(,分割),不建议混用
book.authors.remove(n1)
book.authors.remove(2)
book.authors.remove(1,2)

3、清空 clear()

book.authors.clear()

4、先清空后创建 set() -- 必须传值列表

# set,先清空,再新增
# 必须传一个列表 --  列表内数据 -- [id, 对象,]
book.authors.set([n1,])

# 不允许*[n1,]进行传值
# 打散了传过去了,相当于book.authors.set(n1)
book.authors.set(*[lqz,])

 四、基于对象的跨表查询

总结:

  • 表A类内-关联a 建立与 表B-id字段的关系
  • 正向查询:通过表A查询表B内字段内容
  • 反向查询:通过表B查询表A内字段内容
  • 基于对象的查询,是子查询也就是多次查询

1、一对一的跨表查询

1)正向查询(类内联系变量名查询)

# 查询n1作者的手机号 

# 获取指定作者对象
author=Author.objects.filter(name='n1').first()

# author.authordetail -- 上方指定对象.类内联系变量名 -- 作者表对应详细信息表的对象
# author类内的authordetail联系变量名
authordetail=author.authordetail
# 通过对象获取对象内字段信息
print(authordetail.phone)

2)反向查询(表名小写查询)

# 查询地址是 :山东 的作者名字 
# 获取AuthorDetail详情表内的指定对象
authordetail=AuthorDetail.objects.filter(addr='山东').first()

# authordetail.author  -- 上方指定对象.关联表名小写 -- 获取关联作者对象
author=authordetail.author
print(author.name)

2、一对多的跨表查询

1)正向查询(类内联系变量名查询)

# 查询红楼梦这本书的出版社邮箱
# 获取指定书籍对象
book=Book.objects.filter(name='红楼梦').first()

# book.publish  -- 上方获取指定对象.类内联系名 --就是出版社对象
pulish=book.publish
# 出版社对象.字段名
print(pulish.email)

2)反向查询(表名小写_set.all())

# 查询地址是北京 的出版社出版的图书
# 获取指定地址对象
publish=Publish.objects.filter(addr='北京').first()

# publish.book_set.all()  拿出所有的图书
# 获取指定对象 建立联系的表内容
books=publish.book_set.all()
# 统计一下条数
books=publish.book_set.all().count()
print(books)

3、多对多的跨表查询

1)正向查询(类内联系变量名.all())

# 查询红楼梦这本书所有的作者
# 获取指定书本的对象
book=Book.objects.filter(name='红楼梦').first()
# 获取指定对象联系表的所有数据 指定对象.类内联系变量名
book.authors.all()  #是所有的作者,是一个queryset对象,可以继续点
print(book.authors.all())

2)反向查询(表名小写_set.all())

# 查询n1写的所有书
# 获取n1对象
n1=Author.objects.filter(name='n1').first()
# n1.联系表名小写_set.all() 获取对应联系表内数据
books=n1.book_set.all()
print(books)

4、连续跨表查询

# 查询红楼梦这本书所有的作者的手机号
# 获取对象
book=Book.objects.filter(name='红楼梦').first()
# 获取所有联系表对象
authors=book.authors.all()
# 循环取联系表对象
for author in authors:
    # 获取联系表对象对应详细信息表对象 
    authordetail=author.authordetail
    print(authordetail.phone)

'''
book表  -- authors表 -- authordetail表
'''

五、基于双下划线的查询:联系表__联系表内字段名

总结:

  • 三者表联系,对于基于上下划线的查询本质不变:跨表使用下划线
  • 从纯生sql代码来看,只是from的表不同

1、一对一的查询

# 查询n1的手机号(一个作者对应一个具体信息)
    
    # 正向查询(作者表对象,跨表字段)
    ret=Author.objects.filter(name="n1").values("authordetail__telephone")

    # 反向查询(作者详细信息跨表对象,本地字段)
    ret=AuthorDetail.objects.filter(author__name="n1").values("telephone")

2、一对多的查询

# 查询出版社为北京出版社出版的所有图书的名字,价格(一个出版社对应多个图书信息)

# 正向 (出版社表对象跨表字段)
ret=Publish.objects.filter(name='北京出版社').values('book__name','book__price')
print(ret)

# 反向 (图书表跨表对象的本地字段)
ret=Book.objects.filter(publish__name='北京出版社').values('name','price')
print(ret)

# 查询北京出版社出版的价格大于19的书 (一个出版社对应多个图书信息)

# 正向(出版社表对象的跨表字段)
ret=Publish.objects.filter(name='北京出版社',book__price__gt=19).values('book__name','book__price')
print(ret)

3、多对多的查询

# 查询红楼梦的所有作者名字 (一本图书可能对应多个作者)

# 正向(图书表对象,跨表字段)
ret=Book.objects.filter(name='红楼梦').values('authors__name')
print(ret)

# 反向(作者表跨表对象,本地字段)
ret=Author.objects.filter(book__name='红楼梦').values('name')
print(ret)

# 查询图书价格大于30的所有作者名字(一本图书对应多个作者)

# 正向(图书表对象,跨表字段)
ret=Book.objects.filter(price__gt=30).values('authors__name')
print(ret)

4、连续跨表查询

# 基于双下划綫的 “连续跨表”

# 查询北京出版社出版过的所有书籍的名字以及作者的姓名
# 出版社表 - 书籍表 - 作者表

# 正向(出版社表对象,书本跨表字段,书本下作者跨表字段)
ret=Publish.objects.filter(name='北京出版社').values('book__name','book__authors__name')
print(ret)

# 反向(书本表跨表出版社对象,本地书名字段,跨表作者表字段)
ret=Book.objects.filter(publish__name='北京出版社').values('name','authors__name')
print(ret)


# 手机号以151开头的作者出版过的所有书籍名称以及出版社名称
# 作者详细信息表(手机号) - 作者表 - 书籍表(书名) - 出版社表(出版社表)

# 正向(作者信息表对象,作者表-书籍表字段,作者表-书籍表-出版社表字段)
ret=AuthorDetail.objects.filter(phone__startswith='13').values('author__book__name','author__book__publish__name')
print(ret)

# 反向(书籍表跨表 作者表-详细信息表 对象,本地字段,跨表出版社字段)
ret=Book.objects.filter(authors__authordetail__phone__startswith='13').values('name','publish__name')
print(ret)
    

六、聚合查询(aggregate() + 聚合函数(Avg,Max,Min…))

总结:

  • aggregate():QuerySet 的一个终止子句,它返回一个包含一些键值对的字典。
  • 键的名称是聚合值的标识符(自动生成或者手动命名),值是计算出来的聚合值。
  • key(自动生成):‘字段名_聚合函数名’ 
  • value:聚合函数计算返回值
# 导入所需 聚合函数
from django.db.models import Avg,Count,Max,Min,Sum

# 计算所有图书的平均价格
ret=Book.objects.all().aggregate(Avg('price'))
print(ret)
# {'price__avg': 202.896}

# 计算图书的最高价格 (自定义命名key)
ret=Book.objects.all().aggregate(my_max = Max('price'))
print(ret)
# {'my_max': Decimal('99.00')}

# 计算图书的最高价格,最低价格,平均价格,总价

ret=Book.objects.all().aggregate(Max('price'),Min('price'),Avg('price'),Sum('price'))
print(ret)
# {'price__max':Decimal('81.20'),'price__min':Decimal('12.99'),'price__avg':34.35,'price__sum': Decimal('121.00'),}

七、分组查询(annotate() + 聚合函数(Avg,Max,Min…))

总结:

  • annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。

  • 跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。 

# 统计每一本书作者个数
ret=Book.objects.all().annotate(c=Count('authors'))
print(ret)
# 返回的是一个queryset对象
# <QuerySet [<Book:红楼梦>,<Book:西游记>,<Book:水浒传>]>
# 循环取出列表内对象
for r in ret:
    print(r.name,'-->',r.c)
# 红楼梦--> 1
# 红楼梦--> 2
# 红楼梦--> 3

ret=Book.objects.all().annotate(c=Count('authors')).values('name','c')
print(ret)
# 返回queryset对象
# <QuerySet [{'name':红楼梦,'c':1},{'name':西游记,'c':2},{'name':水浒传,'c':3}]>

# 统计每一个出版社的最便宜的书(以谁group by分组 就以谁为基表)
# 出版社表跨表对象(双下划线的使用)
ret=Publish.objects.all().annotate(m=Min('book__price')).values('name','m')
print(ret)
# <QuerySet [{'name':北京出版社,'m':Decimal('1.00')},]>
ret=Publish.objects.all().annotate(m=Min('book__price')).values('name','book__name','m')
print(ret)
# <QuerySet [{'name':北京出版社,'book_name':'红楼梦','m':Decimal('1.00')},]>

- filter、annotate、values的使用顺序,及不同

总结:

  • filter:在前表示where条件删选,在后表示having
  • values:在前表示group by分组,在后表示取对应字段的值
  • annotate:取分组
# 统计每一本以py开头的书籍的、作者个数
# filter:where筛选,annotate:join,values:取值
ret1=Book.objects.all().filter(name__startswith='py').annotate(c=Count('authors')).values('name','c')
print(ret1)
# <QuerySet [{'name':'python','c':2}]>

# 统计每一本以py开头的书籍的作者个数--套用模板
# valuse:group by分组,fitter:having限定条件,annotate:join,values:取值
ret2=Book.objects.all().values('name').filter(name__startswith='py').annotate(c=Count('authors')).values('name','c')
print(ret2)
# <QuerySet [{'name':'python','c':2}]>

# 查询各个作者出的书的总价格
ret=Author.objects.all().values('name').annotate(s=Sum('book__price')).values('name','s')
ret=Author.objects.all().annotate(s=Sum('book__price')).values('name','s')
print(ret)
# <QuerySet [{'name':'n1','s':Decimal('123')}]>

# 查询名字叫n1作者书的总价格 ,pk同主键
ret=Author.objects.all().values('pk').filter(name='n1').annotate(s=Sum('book__price')).values('name','s')
print(ret)

# 查询所有作者写的书的总价格大于30
ret=Author.objects.all().values('pk').annotate(s=Sum('book__price')).filter(s__gt=2).values('name','s')
ret=Author.objects.all().annotate(s=Sum('book__price')).filter(s__gt=30).values('name','s')
print(ret)

# 统计不止一个作者的图书
ret = Book.objects.all().values('pk').annotate(c=Count('authors')).filter(c__gt=1).values('name', 'c')
# ret = Book.objects.annotate(author_num=Count("authors")).filter(author_num__gt=1).values('name','author_num')
print(ret)

八、F查询(对同一个model实例中的两个不同字段进行比较)

总结:

  • F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
  • 支持F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
  • 支持F()对字段值的修改操作

- models新增(book标下的阅读数字段和评论数字段)

class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32,db_index=True)
    price = models.DecimalField(max_digits=5, decimal_places=2)

    # 阅读数
    reat_num=models.IntegerField(default=0)
    # 评论数
    commit_num=models.IntegerField(default=0)
    publish = models.ForeignKey(to=Publish, to_field='id',on_delete=models.CASCADE)

    authors = models.ManyToManyField(to=Author)


    # test=models.PositiveSmallIntegerField()
    def __str__(self):
        return self.name

 - 同一个model实力类中的字段比较

from django.db.models import F

# 查询评论数大于阅读数的书

# 错误方式
# ret = Book.objects.all().filter(commit_num__gt=reat_num)
# 正确方式 评论数 >(gt) 阅读数
ret = Book.objects.all().filter(commit_num__gt=F('reat_num'))
print(ret)

- 字段值的修改(加减乘除取模等操作)

from django.db.models import F
# 把所有书的评论数加1

# 错误方式1
# ret = Book.objects.all().update(commit_num += 1)
# 错误方式2
# ret = Book.objects.all().update(commit_num=commit_num + 1)

# 正确方式
ret = Book.objects.all().update(commit_num=F('commit_num') + 1)
print(ret)



# 把python这本书的阅读数减5
ret = Book.objects.all().filter(name='python').update(reat_num=F('reat_num') - 5)
print(ret)

九、Q查询(操作复杂的逻辑运算(与或非))

总结:

  • and (&):Q(字段名1=‘查询名1’) & Q(字段名1=‘查询名1’)
  • or(|):Q(字段名1=‘查询名1’) | Q(字段名1=‘查询名1’)
  • not(~):Q(字段名1=‘查询名1’) ~ Q(字段名1=‘查询名1’)
  • 当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
  • 可以组合& 和|  操作符以及使用括号进行分组来编写任意复杂的Q 对象。
from django.db.models import Q

# 查询作者名字是n1或者名字是n2的书
# 错误方式1(使用的是and)
# ret=Book.objects.all().filter(authors__name='n1',authors__name='n2')
# 错误方式 同上
# ret = Book.objects.all().filter(Q(authors__name='n1') & Q(authors__name='n2'))
# print(ret)

# 正确方式
ret=Book.objects.all().filter(Q(authors__name='n1')|Q(authors__name='n2'))
print(ret)



# 查询作者不是n1的书(~ 非)

ret=Book.objects.filter(~Q(authors__name='n1'))
print(ret)

# 构建很复杂的逻辑,需要用括号来区分优先级
ret = Book.objects.filter((Q(name='红楼梦') & Q(price__gt=100)) | Q(pk__gt=2))
print(ret)

猜你喜欢

转载自blog.csdn.net/qq_33961117/article/details/84029343