Django模型层 orm多表操作
多表之间的关系
一对多(多对一)
比如:书和出版社
key:必须在 “多” 的表中加关联字段,外加约束(外键)
约束和关联的区别?
约束是建立 foreignkey 外键,一般情况下还是要加约束的,这样删除记录的时候可以设置级联删除,这样,当删除一中表的内容,多种的表也会删除,而删除多的表中的内容,由于别人在占用,别人就不能删除,可以防止误删。
关联是建立一个字段,用来与别的表连接,但是该字段不一定是外键,此时两张表没有关联,不存在级联删除的情况。
一对一
比如:作者和作者详情
key:可以再任意表中设置关联字段,要加唯一约束
多对多
比如:作者和书籍
key:通过创建第三张关系表,且关联字段在可以任意表中,在 orm 中有语法可以自动生成第三关系表
authors = models.ManyToManyField(to="Author")
模型的创建
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
模型的建立如下:

from django.db import models class Book(models.Model): nid=models.AutoField(primary_key=True) title=models.CharField(max_length=32) price=models.DecimalField(max_digits=8,decimal_places=2) # 999999.99 pub_date=models.DateTimeField() # "2012-12-12" publish=models.ForeignKey(to="Publish",on_delete=models.CASCADE) # 级联删除,在数据库中默认是publish_id authors=models.ManyToManyField(to="Author") #自动创建第三张表, def __str__(self): return self.title class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField(max_length=32) email=models.CharField(max_length=32) def __str__(self): return self.name class Author(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField(max_length=32) age=models.IntegerField() email=models.CharField(max_length=32) ad=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE) #建立一对一的关系 #books=models.ManyToManyField("Book") def __str__(self): return self.name class AuthorDetail(models.Model): addr=models.CharField(max_length=32) tel=models.IntegerField() #author=models.OneToOneField("Author",on_delete=models.CASCADE) def __str__(self): return self.addr
建立的表结构和生成的字段表明如下图:
关联字段会自动再后面加 _id,
注意事项:
1. 新建django项目启动之前需要的准备工作
setting __init__() 创建数据库 数据库迁移(makemigrations migrate)
2. 如果表结构都已经创建好,工作途中想要添加字段,需要给该字段设置默认值,
但是如果是临时加外键的时候是不能给默认值得,因此在数据库迁移的时候,可以将之前建立的表给关掉(注释掉)或设为空
3. 表名 app01_book_authors,是根据模型中的元数据自动生成的,也可以覆写为别的名称
4. id 字段也可以是自动添加的,此时可以不用xie
5. 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
6. 这个例子中的CREATE TABLE
SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。详见单表操作中的setting设置
7. 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py
所在应用的名称。
8. 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
添加表记录
之前学过的对于没有关联字段的表添加记录直接用关键字传参:
book = Book.objects.create(title="python",price="100",pub_date="2012-12-12")
解释:添加记录返回的 book 为刚刚添加的这本书的记录
一对多
方式一:(常用)
publish_id = 1
book = Book.objects.create( title="python", price=120, pub_date="2012-12-12", publish_id=1 #直接等于某一个值,对应 publish 表中的id,此时添加的是 publish_id字段 )
方式二:可以按照对象的方式添加
publish = publish_obj
pub_obj=Publish.objects.filter(name="橙子出版社").first() book=Book.objects.create( title="python", price=120, pub_date="2012-12-12", publish=pub_obj #先取出 橙子出版社 的一个对象,然后此时添加的是 publish字段 )
注意:book.publish与book.publish_id是什么?
book.publish:是这本书的出版社对应的整个记录(对象)
book.publish_id:是这本书出版社对应的 id 值
多对多
核心:为第三张表中添加记录,第三张表可以用 book_obj.authors 书对象.关联字段
# 先为Book表添加其他字段以后, book_obj = Book.objects.create(title="追风筝的人", price=200, pub_date="2012-11-12", publish_id=1) # 方式1 为书籍绑定的做作者对象 alex=Author.objects.filter(name="alex").first() egon=Author.objects.filter(name="egon").first() book_obj.authors.add(alex,egon) #可以将作者的整个对象添加到第三张表中 # 方式2: book_obj.authors.add(1,2) #也可以将主键 1,2 添加 # 方式3: book_obj.authors.add(*[1,2]) #或者主键放入列表中,需要打散
注意:book_obj.authors.all()是什么?
# 查询与这本书关联的所有queryset的集合
多对多中其他常用的一些API方法:
book_obj.authors.add(1,2) #为这本书添加的作者为 1 2 book_obj.authors.remove(1) #删除作者1的 解除 book_obj.authors.clear() #删除所有的 book_obj.authors.set(3) #重置作者的id 解除再绑定
查询记录
跨表查询
基于对象跨表查询(利用子查询)
正向查询:关联属性所在的表查询关联表记录
反向查询:与之相反,这里的反向查询式,关联字段都是没有设置 teleted_name
key:
正向查询按字段,多个对象的时候加 .all()
反向查询,得到一个对象,按表名小写
得到多个对象,按表名小写_set.all()
1、
一对多: 正向查询按字段:book.publish Book -----------------------------------------> Publish <--------------------------------------- 反向查询表名小写_set.all():pub_obj.book_set.all()
练习:
# 1 查询python这本书出版社的名字和邮箱 book=Book.objects.filter(title="python").first() pub_obj=Publish.objects.filter(nid=book.publish_id).first() print(pub_obj.name) print(pub_obj.email) ####################### book = Book.objects.filter(title="python").first() print(book.publish) # 与book这本书关联出版社对象 print(book.publish.name) # 与book这本书关联出版社对象 print(book.publish.email) # 与book这本书关联出版社对象
# 2 查询苹果出版社出版的所有的书籍的名称 pub_obj=Publish.objects.get(name="苹果出版社") print(pub_obj.book_set.all()) # queryset print(pub_obj.book_set.all().values("title")) # queryset
2、
多对多: 正向查询按字段 book.authors.all() Book -------------------------------------->Author <-------------------------------------- 反向查询按表名小写_set.all(): alex.book_set.all()
练习:
# 查询python这本书籍的作者的年龄 book=Book.objects.filter(title="python").first() ret=book.authors.all().values("age") # 与这本书关联的左右作者的queryset的集合 print(ret)
# #查询alex出版过的所有的书籍名称 alex=Author.objects.filter(name="alex").first() print(alex.book_set.all())
3、
一对一: 正向查询安字段:alex.ad Author -----------------------------------------> AuthorDetail <------------------------------------------ 反向查询按表名小写 ad.author
练习:
# 查询alex的手机号 alex = Author.objects.filter(name="alex").first() print(alex.ad.tel)
# 查询手机号为110的作者的名字 ad=AuthorDetail.objects.filter(tel=110).first() print(ad.author.name)
补充:
你可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改:
publish = ForeignKey(Book, related_name='bookList')
那么接下来就会如我们看到这般:
# 查询 人民出版社出版过的所有书籍 publish=Publish.objects.get(name="人民出版社") book_list=publish.bookList.all() # 与人民出版社关联的所有书籍对象集合
基于上下划线的跨表查询(核心是基于 join实现 拼表)
将多张表拼成一张表,然后按照单表操作,(该方法 常用)
有直接关系的直接拼表,没有直接关系的要连续拼表
key: 正向查询按字段,反向查询按表名小写, 三种对应关系均满足该规律
示例: book_title
一对多:
# 1 查询python这本书出版社的名字 ret=Book.objects.filter(title="python").values("price") ret=Book.objects.filter(title="python").values("publish__name") print(ret) ret=Publish.objects.filter(book__title="python").values("name") print(ret) # 2 查询苹果出版社出版的所有的书籍的名称 ret=Publish.objects.filter(name="苹果出版社").values("book__title") ret=Book.objects.filter(publish__name="苹果出版社").values("title") 多对多: # 3 查询python这本书籍的作者的年龄 ret=Book.objects.filter(title="python").values("authors__age") print(ret) ret=Author.objects.filter(book__title="python").values("age") # 4 查询alex出版过的所有的书籍名称 ret1=Book.objects.filter(authors__name="alex").values("title") ret2=Author.objects.filter(name="alex").values("book__title") print(ret1,ret2)
一对一: # 5 查询alex的手机号 ret=Author.objects.filter(name="alex").values("ad__tel") ret=AuthorDetail.objects.filter(author__name="alex").values("tel") # 6 查询手机号为110的作者的名字 ret=AuthorDetail.objects.filter(tel=110).values("author__name") ret=Author.objects.filter(ad__tel=110).values("name")
进阶练习(连续跨表)
book_author__name
# 查询苹果出版社出版过的所有书籍的名字以及作者的姓名 ret=Publish.objects.filter(name="苹果出版社").values("book__title","book__authors__name") ret=Book.objects.filter(publish__name="苹果出版社").values("title","authors__name") ret=Book.objects.filter(publish__name="苹果出版社").values("title","authors__name") print(ret) # 手机号以110开头的作者出版过的所有书籍名称以及出版社名称 # 方式1: ret=Author.objects.filter(ad__tel__startswith=110).values_list("book__title","book__publish__name") print(ret) # 方式2: ret=AuthorDetail.objects.filter(tel__startswith=110).values("author__book__title","author__book__publish__name") # 方式3: ret=Book.objects.filter(authors__ad__tel__startswith=110).values("title","publish__name")
聚合查询和分组查询
在使用聚合函数之前需要在 views 视图函数中 引入: 聚合函数一般会和分组结合使用
from django.db.models import Avg,Max,Sum,Min,Count
聚合查询
aggregate(*args,**kwargs)
from django.db.models import Avg,Max,Sum,Min,Count # 查询所有书籍的平均价格 ret=Book.objects.all().aggregate(priceAvg = Avg("price")) print(ret) # {'priceAvg': 142.0} # 查询所有书籍的个数 ret=Book.objects.all().aggregate(c = Count(1)) #字段的名字为c print(ret) # {'c': 4}
单独的使用聚合 aggregate() 是对整个表(queryset) 求平均值、总数、最大值、最小值、个数等。
将整个表作为一个组,进行分析。
分组查询
1、分组结构图及解释

###################################--单表分组查询--####################################################### 查询每一个部门名称以及对应的员工数 emp: id name age salary dep alex 12 2000 销售部 egon 22 3000 人事部 wen 22 5000 人事部 sql语句: select dep,Count(*) from emp group by dep; ORM: emp.objects.values("dep").annotate(c=Count("id") ###################################--多表分组查询--########################### 多表分组查询: 查询每一个部门名称以及对应的员工数 emp: id name age salary dep_id alex 12 2000 1 egon 22 3000 2 wen 22 5000 2 dep id name 销售部 人事部 emp-dep: id name age salary dep_id id name alex 12 2000 1 1 销售部 egon 22 3000 2 2 人事部 wen 22 5000 2 2 人事部 sql语句: select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id ORM: dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c")
orm构建表结构模型
class Emp(models.Model): name=models.CharField(max_length=32) age=models.IntegerField(default=20) dep=models.CharField(max_length=32) pro=models.CharField(max_length=32) salary=models.DecimalField(max_digits=8,decimal_places=2)
2、annotate()
语法:
Book.objects.annotate( ... ).values( ... )
annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。
3、单表分组查询
示例:
annotate()前values哪一个字段就按哪一个字段group by
# 查询书籍表每一个出版社id以及对应的书籍个数 # key: annotate()前values哪一个字段就按哪一个字段group by ret=Book.objects.values("publish_id").annotate(c=Count(1)) #以publish_id分组,查询每组的个数 print(ret) # 查询每一个部门的名称以及对应员工的平均薪水 ret=Emp.objects.values("dep").annotate(avg_salary=Avg("salary")) print(ret) # [{'dep': '教学部', 'avg_salary': 2500.0}, {'dep': '保洁部', 'avg_salary': 3500.0}, {'dep': '保安部', 'avg_salary': 4000.0}]> # 查询每一个省份的名称以及对应的员工最大年龄 ret=Emp.objects.values("pro").annotate(max_age=Max("age")) print(ret) # <QuerySet [{'pro': '山东省', 'max_age': 123}, {'pro': '河南省', 'max_age': 23}, {'pro': '河北省', 'max_age': 56}]> # 单表按主键分组没有意义 # Emp.objects.values("id").annotate()
4、跨表分组查询
#SQL语句
select app01_publish.name,COUNT(1) from app01_book INNER JOIN app01_publish ON app01_book.publish_id=app01_publish.nid GROUP BY app01_publish.nid
示例:
# 1 查询每一个出版社的名称以及对应的书籍平均价格 # 方式1: ret=Publish.objects.values("name","email").annotate(avg_price=Avg("book__price")) print(ret) # <QuerySet [{'name': '苹果出版社', 'avg_price': 117.0}, {'name': '橙子出版社', 'avg_price': 112.0}, {'name': '西瓜出版社', 'avg_price': 222.0}]> # 方式2: ret=Publish.objects.all().annotate(avg_price=Avg("book__price")).values("name","email","avg_price") print(ret) # <QuerySet [<Publish: 苹果出版社>, <Publish: 橙子出版社>, <Publish: 西瓜出版社>]> # 方式3:(常用的方式) ret=Publish.objects.annotate(avg_price=Avg("book__price")).values("name","email","avg_price") print(ret) # <QuerySet [<Publish: 苹果出版社>, <Publish: 橙子出版社>, <Publish: 西瓜出版社>]> # 2 查询每一个作者的名字以及出版的书籍的最高价格 ret=Author.objects.values("pk","name").annotate(max_price=Max("book__price")) print(ret) ret=Author.objects.annotate(maxprice=Max("book__price")).values("name","maxprice") print(ret) # 3 查询每一个书籍的名称以及对应的作者的个数 ret=Book.objects.values("title").annotate(c=Count("authors")) print(ret) # <QuerySet [{'title': 'python', 'authors__count': 2}, {'title': 'linux', 'authors__count': 1}, {'title': 'go', 'authors__count': 1}, {'title': 'java', 'authors__count': 0}]> ret=Book.objects.annotate(c=Count("authors")).values("title","c") print(ret) # 4 查询作者数不止一个的书籍名称以及作者个数 ret=Book.objects.annotate(c=Count("authors__name")).filter(c__gt=1).values("title","c") print(ret) # 5 根据一本图书作者数量的多少对查询集 QuerySet进行排序 ret=Book.objects.annotate(c=Count("authors__name")).order_by("c") # 6 统计每一本以py开头的书籍的名称以及作者个数 ret=Book.objects.filter(title__startswith="py").annotate(c=Count("authors__name"))
F 与 Q 查询
需要引入:
from django.db.models import F,Q
F查询
涉及到其他字段(条件中有两个以上字段的)要用 F
# 查询评论数大于100的所有的书籍名称 ret=Book.objects.filter(comment_count__gt=100).values("title")
# 查询评论数大于2倍点赞数的所有的书籍名称 ret=Book.objects.filter(comment_count__gt=F("poll_count")*2) #两个字段之间进行比较,第二个要用F包住 print(ret) # 给每一本书籍的价格提升100 Book.objects.all().update(price=100+F("price")) #字段用于计算也要用F
Q查询
1、基本使用
逻辑: ( & 且,也可以用逗号)( | 或)( ~ 非)
涉及到 且 或 非 需要用 Q包住各条件,再进行逻辑判断
ret=Book.objects.filter(price__gt=300,comment_count__gt=3000) #价格大于300且评论数大于3000 print(ret) ret = Book.objects.filter(Q(price__gt=300)|Q(comment_count__gt=3000)) #价格大于300评或者论数大于3000 print(ret) # 判断一下三种:如果用逗号表示且,不加Q的条件必须放在后面 ret = Book.objects.filter(Q(Q(price__gt=300) | ~Q(comment_count__gt=3000))&Q(poll_count__gt=2000)) ret = Book.objects.filter(Q(Q(price__gt=300) | ~Q(comment_count__gt=3000)),poll_count__gt=2000) ret = Book.objects.filter(poll_count__gt=2000,Q(Q(price__gt=300) | ~Q(comment_count__gt=3000))) #错误的 print(ret)
2、Q() 实例化使用♥
Q() 实例化之后可以利用字段的字符串作为筛选条件,可以解决当字段为字符串时,如何进行筛选:
Book.objects.filter(price=100) # 这里的price是字段,并非字符串,如果获取到字段的字符串形式,可以使用Q()实例化的形式
示例一:
字符串可以当做字段用,q.children.append(("name","alex")) 里面是元组的形式,可以多次append添加,也可以同时添加多个元组。
q=Q() # 实例化一个Q的对象q,我们可以给它加条件 q.children.append(("name","alex")) # 添加筛选条件,即name字段为alex的记录 q.children.append(("gender","男")) # 再添加一个条件,即gender字段为男,与上一个筛选条件的关系默认是"且",即叫alex的男同学
Student.objects.filter(q) # 过滤name为alex的男同学
示例二:
字符串时也可以模糊查询,直接拼 __ 即可,例如:price__gt=100
q=Q() # 实例化一个Q的对象q q.children.append(("name__contains","赵")) # 添加筛选条件 Student.objects.filter(q) # 过滤name字段中包含"赵"的同学
示例三:
当append多个是的时候,默认是且的关系,如果更改,设置,q.connertor="or" 即可
# 改为"或"的关系 q=Q() # 实例化一个Q的对象q q.connertor="or" # 改为"或"的关系 q.children.append(("name","alex"),("gender","男")) Student.objects.filter(q) # 过滤name为alex或者性别是男的所有同学 # 注意:上面示例中都是确定字段,即name字段为alex,gender字段为男,最重要的是q对象中条件元组中除了字符串,也可以是变量。
多表操作参考链接: