表关系总结:
一对多:在多的表中建立关联字段
多对多:创建第三张表(关联表):id 和 两个关联字段
一对一:在两张表中的任意一张表中建立关联字段(关联字段一定要加 unique 约束)
子查询:一次查询结果作为另一次查询的查询条件
创建模型:
from django.db import models # Create your models here. class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday = models.DateField() telephone = models.BigIntegerField() addr = models.CharField(max_length=64) class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() # 一对一:在任意一张表中添加约束 authordetail = models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE) # authordetail也会被自动添加 _id # 对于Django2.0版本,一对多(models.ForeignKey)和一对一(models.OneToOneField)要加上 on_delete=models.CASCADE 这个属性 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 = models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) # 这句代码做了两件事:1. 生成表的时候在 publish前面自动加了 _id,并在表中生成了一个字段 publish_id;2. 把 publish_id 作为外键关联到了 Publish表的 nid 字段;具体作用用SQL语句表示如下: """ publish_id int, foreign key(publish_id) references publish(id) """ # 多对多:生成第三张表来存多对多对应关系 authors = models.ManyToManyField(to="Author") # 这句代码的作用是创建第三张表来存多对多关系,而不是在该表中创建一个 authors_id 的字段;ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表;作用用SQL语句表示如下: """ create table book_authors( id int primary key auto_increment, book_id int, author_id int, foreign key(book_id) references book(id), foreign key(author_id) references author(id) ); """ # ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
注意:
- 表的名称
myapp_modelName
,是根据 模型中的元数据自动生成的,也可以覆写为别的名称 id
字段是自动添加的- 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
- Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句
- 修改配置文件中的INSTALL_APPSZ中设置,在其中添加
models.py
所在应用的名称 - 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
添加表记录
urls.py
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path(r"add/",views.add) ]
views.py
from django.shortcuts import render,HttpResponse # Create your views here. from app01.models import * def add(request): # 单表插入记录:(单表:没有关联字段的表) pub = Publish.objects.create(name="人民出版社",email="[email protected]",city="北京") # #######################邦定一对多关系#################### # 为一对多有关联字段的表添加记录 # 为book添加出版社;book是多 # 方式一:直接为 publish_id 这个字段添加 出版社的id book_obj1 = Book.objects.create(title="红楼梦",price=200,publishDate="2016-8-8",publish_id=1) # Book中的 publish会在数据库迁移的时候添加 _id 变成 publish_id 字段 print(book_obj1.title) # 方式二:为 publish 赋值,此时赋的值为 Publish的实例模型对象 pub_obj = Publish.objects.filter(nid=1).first() # nid为1的出版社对象 book_obj2 = Book.objects.create(title="西游记", price=200, publishDate="2016-8-8", publish=pub_obj) # publish等于一个 Publish对象 # create操作时,会把 publish翻译成 publish_id,把 pub_obj的主键值取出来,并把 pub_obj的主键值赋值给 publish_id print(book_obj2.price) # book_obj2的价格 print(book_obj2.publish_id) # book_obj2的出版社id print(book_obj2.publish) # 不管是方式一还是方式二,book_obj2.publish 都表示 pub_obj 这个model对象(重点) # book_obj2.publish:与这本书籍关联的出版社对象;所以 book_obj2.publish 后面还能用点语法,如: print(book_obj2.publish.email) # 一对多的查询:查询“西游记”对应出版社的邮箱 book_obj3 = Book.objects.filter(title="西游记").first() # 西游记这本书对象 email = book_obj3.publish.email # book_obj3.publish:西游记这本书对应的出版社对象 print(email) # #################邦定多对多关系############################ book_obj4 = Book.objects.create(title="python全栈开发",price=100,publishDate="2017-8-8",publish_id=1) alex = Author.objects.get(nid=1) egon = Author.objects.get(nid=2) # 为多对多关系表添加记录的接口(添加的前提是先create一条记录) book_obj4.authors.add(alex,egon) # 给python全栈开发这本书籍对象添加作者,作者为alex和egon;邦定多对多关系的API """ book_obj4.authors.add(alex,egon)执行的操作: 找到 book_obj4的主键,找到alex这个作者对象的主键,然后在 Book和authors的关系表中添加一条记录; 找到 book_obj4的主键,找到egon这个作者对象的主键,然后在 Book和authors的关系表中添加一条记录; 所以book_obj4.authors.add(alex,egon)会在 app01_book_authors 这张表中添加两条记录 """ # 另外一种写法:add中直接写对应作者的主键(id),而不是写作者的model对象 book_obj4.authors.add(1,2) # 或者: book_obj4.authors.add(*[1,2]) # 解除多对多关系(解除的前提是先查出来) book_obj5 = Book.objects.filter(nid=3).first() book_obj5.authors.remove(2) # 把 book_id为book_obj4的主键、author_id为2的那条记录从 app01_book_authors 这张关系表中删除 # book_obj5.authors.remove(1,2) # book_obj5.authors.remove(*[1,2]) # 删除所有: clear() book_obj5.authors.clear() # book_obj5.authors.all() :表示与这本书关联的所有作者对象的集合(QuerySet)(重点) print(book_obj5.authors.all()) # 查询所有作者的名字 ret = book_obj5.authors.all().values("name") return HttpResponse("ok")
基于对象的跨表查询
还以上面的表为例: views.py
def add(request): # #################基于对象的跨表查询(基于子查询)############################ # #########1. 一对多 # 查询主键为1的书籍的出版社所在的城市 # 正向查询;正向查询,你需要先知道关联字段在哪个表中,如这个例子中,publish在Book中,通过publish去查找Publish就是正向查询;同理,通过Publish查找Book就是反向查询 # 正向查询号按字段(如Book中的publish),反向查询按表名(如下面的 book_set.all();book就是表名,set就是集合的意思;集合的形式:QuerySet) book_obj = Book.objects.filter(nid=1).first() # 对应书籍对象 press_city = book_obj.publish.city # book_obj.publish:对应出版社对象 print(press_city) # 查询人民出版社出版的所有书籍的名称 publish_obj = Publish.objects.filter(name="人民出版社").first() # 出版社对象 book_objs = publish_obj.book_set.all() # 该出版社对应的所有书籍对象;QuerySet;[book_obj1,book_obj2,....] print(book_objs) # 打印对应所有书籍的名称 for obj in publish_obj.book_set.all(): print(obj.title) # #######2. 一对一 # 正向查询:查询alex的手机号 alex = Author.objects.filter(name="alex").first() phoneno = alex.authordetail.telephone # 正向查询按字段 print(phoneno) # 反向查询:查询号手机号为911的作者名字 authordetail_obj = AuthorDetail.objects.filter(telephone=911).first() authorname = authordetail_obj.author.name # authordetail_obj.author为对应的作者对象 print(authorname) # 查询所有住址在北京的作者的姓名 authorDetail_list = AuthorDetail.objects.filter(addr="北京") for obj in authorDetail_list: print(obj.author.name) # #######3. 多对多 # 正向查询:查询“python全栈开发”所有作者的名字 book = Book.objects.filter(title="python全栈开发").first() author_book = book.authors.all() # 表示与这本书关联的所有作者对象的集合(QuerySet) # 反向查询:查询alex出版过的所有书籍的名称 author_obj = Author.objects.filter(name="alex").first() books_set = author_obj.book_set.all() # 所有书籍对象 print(books_set) for obj in books_set: print(obj.title)
return HttpResponse("ok")
""" # 一对多:
正向按字段:publish
book ------------------> publish
<-----------------
反向按表名:book_set.all()
# 一对一:(反向查询直接按表名,因为本来就是一一对应的关系,不需要 _set.all())
正向按字段:authordetail
author ------------------> authordetail
<-----------------
反向按表名:author
# 多对多:
正向按字段:authors
book ------------------> author
<-----------------
反向按表名:book_set.all()
"""
基于双下划线的跨表查询:(正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表)
def add(request): # #################基于双下划线(QuerySet)的跨表查询(基于join)############################ # 关键点:正向查询按字段,反向查询按表名 # 查询人民出版社出版过的所有书籍的名字与价格(一对多) ret1 = Book.objects.filter(publish__name="人民出版社").values("title","price") # 正向查询:Book ----> Publish, publish是Book中的字段,publish__name是所关联的Publish的name print(ret1) ret2 = Publish.objects.filter(name="人民出版社").values("book__title","book__price") # 反向查询:Publish ----> Book, book__title中book是表名,book__title是Book中的title字段 print(ret2) """ 上述两种方式实现的效果一样,区别在于基表的不同;双下划线的查询方式能自动确认 SQL JOIN 联系 """ # 查询alex出版过的所有书籍的名字(多对多)(可以把双下划线理解成“的”) alex_book1 = Author.objects.filter(name="alex").values("book__title") # 反向查询:Author---->Book 按表名 book__ print(alex_book1) alex_book2 = Book.objects.filter(authors__name="alex").values("title") # 正向查询:Book---->Author 按字段 authors__ print(alex_book2) ##### 混合使用 # 查询人民出版社出版过的所有书籍的名字以及作者的姓名 mix1 = Book.objects.filter(publish__name="人民出版社").values("title","authors__name") # 以book为基表 print(mix1) mix2 = Publish.objects.filter(name="人民出版社").values("book__title","book__authors__name") # 以 publish 为基表 print(mix2) # 手机号以11开头的作者出版过的所有书籍名称以及出版社名称 mix3 = Book.objects.filter(authors__authordetail__telephone__startswith=11).values("title","publish__name") print("mix3",mix3) mix4 = Author.objects.filter(authordetail__telephone__startswith="11").values("book__title","book__publish__name") print(mix4)
return HttpResponse("ok")
聚合查询和分组查询
def add(request): # ###################聚合查询和分组查询################## # 聚合函数:aggregate from django.db.models import Avg price_avg = Book.objects.all().aggregate(Avg("price")) # 所有书籍的平均价格;字典的形式 print(price_avg) # {'price__avg': 166.666667} price_avg2 = Book.objects.all().aggregate(c=Avg("price")) print(price_avg2) # {'c': 166.666667} # 分组函数:annotate() # 统计每本书的作者个数 from django.db.models import Count author_num = Book.objects.all().annotate(c=Count("authors__name")) # authors__name表示author表中的name字段(正向查询);annotate的作用是给前面所有的对象添加一个独立的“注释”(相当于给前面的所有对象添加了一个新的属性) print(author_num) for obj in author_num: print(obj.title,obj.c) # 统计每个作者出版书籍的最高价格 from django.db.models import Max max_price = Author.objects.all().annotate(hp=Max("book__price")).values("name","hp") # book__price:反向查询;.values("name","hp") 链式操作 print(max_price)
return HttpResponse("ok")
aggregate(*args,**kwargs)是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它(kwargs的形式)。
而annotate()函数可以为QuerySet中的每个对象生成一个独立的摘要,输出的结果仍然是一个QuerySet对象,能够调用filter()、order_by()甚至annotate()
总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。