django模型层之多表操作 增删改查
多表操作之创建模型
这边以书为中心创建一个模型
作者模型:一个作者有姓名和年龄.
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
模型建立如下: models.py
from django.db import models # Create your models here. class Author(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) age=models.IntegerField() # 与AuthorDetail建立一对一的关系 authorDetail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE) class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday=models.DateField() telephone=models.BigIntegerField() addr=models.CharField( max_length=64) 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建立一对多的关系,外键字段建立在多的一方 publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors=models.ManyToManyField(to='Author',)
会有如下的结构图,并不完全按上面的去创建,有略微差别
这里可以看到,自己为命名,表格命名是以 应用名_类名 作为表名:book_author
多对多的表格创建是以 应用名_第一张表名_第二张表名 作为表名: book_book_author
这边要注意的是:
- 表的名称
myapp_modelName
,是根据 模型中的元数据自动生成的,也可以覆写为别的名称------在models.py文件class Meta: db_table='XXX' - id字段是自动添加的(如果没有设置id字段的话)
- 对于外键字段,Django默认的会以字段_id作为数据表的表头 : 上图book_book表中中pubulish,自动添加为publish_id
- 这个例子中的
CREATE TABLE
SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。 - 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加
models.py
所在应用的名称。 - 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。(就是说一张表的数据,有些可以没有外键)
在这整个模型中,你应该明确的是:
关于表之间的关系:
1. 一对多:双方一个一对多,一个一对一(一本书由一个出版出版,出版社可以出版多本书)
关联字段在'多'的一方
book <-----------> pulish
book
id title pulish
pulish
id name city
2. 多对多:双方都是一对多(一本书可以有多个作者,一个作者可以写多本书)
book <------------> author
book
id title
aothor
id name
book2author
id book_id authorid
3. 一对一:双方都是一对一 (一个作者只有对应唯一一个作者详情,一个作者详情也只能对应一个作者)
关联字段在双方任意一方,但是必须unique约束
auhor <------------> authordetail
author
id name
aothordetail
id addr email author_id
建立外键的约束的常用语句
字段名=models.ForeignKey(to='Publish',to_field='id',on_delete=models.CASCADE)
字段名=models.OneToOneField('Author',on_delete=models.CASCADE)
字段名=models.ManyToManyField("Author",db_table='book2author')
#Djang会自动以字段名_id命名来作为那个表头
#关于ForeignKey和oneTooneField的差别:只有当ForeignKey有Unique时,两者效果是一样的
多表操作之增删改查
添加数据
#一对多:书和出版社的关系
1.定义添加的路径,分发视图函数:
方式1:
models.Book.objects.create(title='xx',price=100,publishDate='2012-12-22',publish_id=1)
#这边要注意的是:外键字段我们命名是publish,而Django会自动添加_id,所以我们在添加参数也应该这么写
#还有就是这是一对多,这边book是一,他外键关联到publish,所以publish里面应该要先有参数让他关联,要不然会报错
#就是说publish表格里面必须先有相应的id值
#方式2
2.pub_obj=models.objects.filter(title='深圳出版社').first()
models.Book.objects.create(title='xx',price=100,publishDate='2012-12-22',publish_id=pub_obj)
#多对多:书和作者的关系
#其实在在关系表中添加记录(第三张表)
#这边要注意的是,数据库中虽然有创建第三表,但是,我们去而不能引用,因为第三张并不是我们自己创建的,
#就是说models里面没有我们的第三张表的模型,我们没办法直接从medels直接拿到第三张表
#要给第三张表加数据,首先得保证,两张要关联的表必须有要添加的数据
#拿到书籍对象
linux=models.Book.objects.filter(title='linux').first()
#拿到作者对象
alex=models.Author.objects.filter(name='alex').first()
egon=models.Author.objects.filter(name='sb').first()
#操作,添加,删除等
linux.author.add(alex,egon) #会直接找到第三张关系表,添加数据
linux.author.add(1,2) #根据作者id(主键)添加,同时添加两个
linux.author.clear() #清空
linux.author.remove(1,2) #删除
linux.author.set([2,]) #先清空再增加
linux.author.add(*[1,2,3,4,5])#从前端拿过来的数据就是列表,这样打散后传进去,就不需要一个个去拿值了(常用)
#key:
#拿到关联属性author,会自己拿到第三张表
#第一个表名对象.关联属性.操作(第二张表名对象)
# 正向操作按字段,反向操作按表名小写
#给作者绑定两本书
#拿到书对象
go=models.Book.objects.filter(title='go').first()
linux=models.Book.objects.filter(title='linux').first()
#拿到数对象
alex=models.Author.objects.filter(id=1).first()
egon=models.Author.objects.filter(name='sb').first()
alex.book_set.add(linux,go) #这边需要注意的是,每个作者有可以多本书,是一个集合,所以需要用_set来表示
跨表查询
基于对象查询(先找到对象,再查找他的相连属性)
#先记住这一句,正向查询按字段,反向查询按表名小写+选择性的_set,具体是不是_set看他查询出来的个数 #基于对象的查询,所以首先必须先拿到相应的对象才可以进行查询 def query(request): #基于对象的跨表查询 #(1)一对多 书籍和出版社的关系 # 正向按字段 #Book ----------->Publish 正向 从关联属性所在的表到另一张表 # 反向 #Publish <---------Book 反向 从关联字段所在的表到另一张表 # 反向按表名小写_set pubish查找出来的可能多个,所以要加_set # 查询linux这本书出版社的地址(正向) # book=models.Book.objects.filter(title="linux").first() # print(book.publish.city) #查询深圳出版社出版的所有书籍(反向) # shenzhen=models.Publish.objects.filter(name='深圳').first() # shenzhen.book_set.all() #(2)多对多查询 # 正向查询按字段 Book.Author.all() # Book对象 -------------------> Author对象 # Author对象 <---------------- Book 对象 # 反向查询按小写表名_set.all() #查询linux书籍的所有作者(正向) # linux=models.Book.objects.filter(title='linux').first() # queryset=linux.author.all() #(这边是一个queryset,所以不用加set) # print(queryset) #查询alex出版过的所有书 (反向) # alex=models.Author.objects.filter(name='alex').first() # queryset=alex.book_set.all() # print(queryset) #(3)一对一 作者和作者详情的关系 # 反向 小写表名_set.all() # -------------> AuthorDetail # 正向 按字段 # Author <--------> AuthorDetail #查询alex的手机号 (正向) # alex=models.Author.objects.filter(id=1).first() # print(alex.authordetail.telephone) # print(alex.authordetail.addr) #查询手机号为456的作者的名字 authord=models.AuthorDetail.objects.filter(telephone=456).first() print(authord.ad.name) return HttpResponse('查询成功')
关于orm转换成sql语句的过程 想要看他的转换,需要到settings.py配置
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
基于双下划线的跨表查询(join)使用频率更高,更使用
##记住这两句话,遇到跨表,正向用 字段__,反向用 小写表名__
#要看清基表是那一个
#一对多
#查询linux对应出版社的地址
# linux=models.Book.objects.filter(titile="linux").first()
#sql语句
''' #select后面要查询的相当于value
SELECT "book_book"."price", "book_publish"."city" FROM "book_book" INNER JOIN #字段名__相当于inner
"book_publish" ON ("book_book"."publish_id" = "book_publish"."id")
WHERE "book_book"."title" = 'linux' LIMIT 21; args=('linux',) #顾虑条件,对应filter
'''
#正向,按字段连接
# queryset= models.Book.objects.filter(title='linux').values__list('price','publish__city') #price是Book(基表)中的,不用加字段
# print(queryset)
#反向 小写表名__
'''
"book_publish"."city" FROM "book_publish" INNER JOIN "book_book"
ON ("book_publish"."id" = "book_book"."publish_id")
WHERE "book_book"."title" = 'linux'
'''
# queryset=models.Publish.objects.filter(book__title='linux').values('city')
# print(queryset)
#多对多
#查询linux的所有作者 正向
# queryset=models.Book.objects.filter(title='linux').values('author__name')
# queryset=models.Book.objects.filter(title__startswith='l').values('author__name')
# print(queryset)
#反向 小写表名__ 过滤条件是不变的,正常先写格式再慢慢去补全
# queryset=models.Author.objects.filter(book__title='linux').values('name')
# print(queryset)
#查询alex手机号
#反向
# queryset=models.Author.objects.filter(name='alex').values('authordetail__telephone')
#正向
# queryset=models.AuthorDetail.objects.filter(ad__name='alex').values('telephone')
# print(queryset)
基于双下划线的连续跨表查询
#上面的跨表查询都是有直接关系的(外键约束,而基于没有直接关联的怎么库表查询呢)
#查询深圳人出版社出版过得的所有书籍名字以及作者名字(Author表和pubish没有直接关联,这就需要用到连续跨表了) # queryset=models.Book.objects.filter(publish__name='深圳出版社').values_list('title','author__name') # queryset=models.Author.objects.filter(book__publish__name='深圳出版社').values_list('book__title','name') # print(queryset) #上面两种写法效果是一样的,推荐第一种,
#手机号以12开头的作者出版过的书籍名称以及出版社名称 # queryset=models.Book.objects.filter(author__authordetail__telephone__startwith='12').values_list('title',"publish__name") queryset = models.Book.objects.filter(author__authordetail__telephone__contains='12').values_list('title',"publish__name") print(queryset)
聚合查询和分组查询
聚合查询
aggregate(*args,**kargs)
#计算所有的图书平均价格 (把所有的图书聚合成一组) ###在控制台操作
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
>>> Book.objects.aggregate(average_price
=
Avg(
'price'
))
>>>{'avg__price': 999.99}
如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
Book.objects.all().aggregate(Max('price'),Min('price'),Avg('price'))
{'price__max': Decimal('999.990000000000'),
'price__min': Decimal('999.990000000000'),
'price__avg': 999.99}
分组查询
单表分组
###################################--单表分组查询--####################################################### 查询每一个部门名称以及对应的员工数 emp: id name age salary dep 1 alex 12 2000 销售部 2 egon 22 3000 人事部 3 wen 22 5000 人事部 sql语句: select dep,Count(*) from emp group by dep; ORM: emp.objects.values("dep").annotate(c=Count("id") ##values的作用相当于group by ,就是以values里面的参数进行分组,annotate里面的参数W为我们要查询的内容
多表分组
###把多张相关的表进行join后再进行单表查询
############### 多表分组查询
emp: id name age salary dep 1 alex 12 2000 销售部 2 egon 22 3000 人事部 3 wen 22 5000 人事部
class Emp(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() salary=models.DecimalField(max_digits=8,decimal_places=2) dep=models.CharField(max_length=32) province=models.CharField(max_length=32)
# 1 查询每一个出版社的名字和出版过的书籍的平均价格
'''
-- sql语句:
SELECT app01_publish.name,AVG(app01_book.price) from app01_book LEFT JOIN app01_publish on
app01_book.publish_id = app01_publish.id
group by app01_publish.id,app01_publish.name
'''
#方法1
# queryset=models.Publish.objects.values("id","name").annotate(avg_price=Avg("book__price"))
# queryset=models.Publish.objects.values("id","name","email","city").annotate(avg_price=Avg("book__price"))
# [{"id":1,"name":"苹果出版社","eamil":"123","city":"beijing",'avg_price': 119.0},{"id":1,"name":"橘子出版社","eamil":"123","city":"beijing",'avg_price': 155.333333.0}]
#方法2
# queryset=models.Publish.objects.all().annotate(avg_price=Avg("book__price"))
# print(queryset) #<QuerySet [<Publish: 苹果出版社>, <Publish: 橘子出版社>]>
# for obj in queryset:
# print(obj.name,obj.avg_price)
#上面两种写法的效果是等价的,只不过第一种有指定分组的字段,字段就会显示分组用的字段,以及要查询的字段
#第二种没有指定分组的字段,就会返回对象(对象的具体个数就是all当中的对象个数),对象的属性会增加,就是annotate里面的参数
#所以需要for循环把值拿出来我们要的
# 2 查询每一个作者的名字以及出版书籍的个数
queryset=models.Author.objects.annotate(c=Count('book')).value('name',c)
print(queryset)
# 3 查询每一个书籍的名称以及作者的个数
querysetmodels.Book.objects.annotate(c=Count('authors')).values('title',c)
print(queryset)
# 4 查询作者个数大于1 的每一本书籍的名称和作者个数
queryset=models.Book.objects.annotate(c=Count("authors")).filter(c__gt=1).values('title','c')
print(queryset)
# 5 查询书籍名称包含"h"的书籍名称和作者个数
queryset=models.Book.objects.filter(title__contains='h').annotate(c=Count('authors')).values('title','c')
print(queryset)
F查询和Q查询
F查询
###########models
class Article(models.Model):
title=models.CharField(max_length=32)
comment_num=models.IntegerField()
poll_num=models.IntegerField()
price=models.IntegerField()
def __str__(self):
return self.title
#######
#1. 查询评论数大于100的文章
models.Article.objects.filter(comment_num>100)
#2. 查询评论数大于点赞数的文章
models.Article.objects.filter(comment_num>Poll_num) #会报错,涉及到名称空间,不知道poll_num是什么
#正确写法
from django.db.models import F,Q,Avg
models.Article.objects.filter(comment_num>F('poll_num'))
3. #查询点赞数大于两倍评论数
models.Article.objects.filter(poll_num__gt=F('comment_num')*2)
4.将所有的文章提高100元
model.Article.objects.all().update(price=F('price')+100)
Q查询
## 基本语法 & | ~ (与或非)
#######
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)
#1 查询价格大于200 或者名称以p开头
models.Book.objects.filter(Q(price__gt=200)|Q(title__startwith='p'))
#2 查询价格大约300 或者2019年1月出版的书籍
models.Book.objects.filter(Q(price__gt=200)|Q(pub_date__year=2019)& Q(pub_date__month=1))