django模型层(二)多表操作
一、 创建模型
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)
# 作者表 class Author(models.Model): # nid = models.AutoField(primary_key=True) # 修改主键字段名称 # id 默认 name=models.CharField(max_length=32) # 与AuthorDetail建立一对一的关系,一对一的这个关系字段写在两个表的任意一个表里面都可以 ad = models.OneToOneField(to="AuthorDetail",to_field="id",on_delete=models.CASCADE) # ad = models.OneToOneField("AuthorDetail",) # 等同于上面注释的这个 django低版本 # 作者详细信息表 class AuthorDetail(models.Model): #不常用的放到这个表里面 birthday=models.DateField() # telephone=models.BigIntegerField() telephone=models.CharField(max_length=32) addr=models.CharField(max_length=64) # 出版社表 class Publish(models.Model): name = models.CharField(max_length=32) city = models.CharField(max_length=32) #多对多的表关系,我们学mysql的时候是怎么建立的,是不是手动创建一个第三张表,然后写上两个字段, # 每个字段外键关联到另外两张多对多关系的表,orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以, # 以后的学习我们暂时用orm自动创建的第三张表,因为手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用 #如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。 # 书籍表 class Book(models.Model): title = models.CharField(max_length=32) publishDate = models.DateField() # DecimalField -- Decimal(12,4)类型 -- 完全精度 price = models.DecimalField(max_digits=5,decimal_places=2) # 999.99 # 与Publish建立一对多的关系,外键字段建立在多的一方,字段publishs如果是外键字段,那么它自动是int类型 # to指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete级联删除 publishs = models.ForeignKey(to="Publish",to_field="id",on_delete=models.CASCADE) # publishs = models.ForeignKey("Publish") # 同上等同于上面注释的这个 django低版本 # authors不会生成book表的字段,而是生成book表和作者表的第三张关系记录表,通过这个属性可以操作第三张表 authors = models.ManyToManyField(to='Author',)
1.1关于多对多表的创建方式
方式一:自行创建第三张表
class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book")
方式二:通过ManyToManyField自动创建第三张表
# 通过ORM自带的ManyToManyField自动创建第三张表 class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") books = models.ManyToManyField(to="Book") #自动生成的第三张表我们是没有办法添加其他字段的
方式三:设置ManyTomanyField并指定自行创建的第三张表(称为中介模型)
class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") # 自己创建第三张表,并通过ManyToManyField指定关联 class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book")) # through_fields接受一个2元组('field1','field2'): # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。 class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") #可以扩展其他的字段了 class Meta: unique_together = ("author", "book")
注意:
当我们需要在第三张关系表中存储额外的字段时,就要使用第三种方式,第三种方式还是可以使用多对多关联关系操作的接口(all、add、clear等等)
当我们使用第二种方式创建多对多关联关系时,就无法使用orm提供的set、add、remove、clear方法来管理多对多的关系了。
to
设置要关联的表。
to_field
设置要关联的字段。
on_delete
同ForeignKey字段。
to 设置要关联的表 to_field 设置要关联的表的字段 related_name 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。 related_query_name 反向查询操作时,使用的连接前缀,用于替换表名。 on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。
多对多的参数:
to
设置要关联的表
related_name
同ForeignKey字段。
related_query_name
同ForeignKey字段。
through
在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过
through来指定第三张表的表名。
through_fields
设置关联的字段。
db_table
默认创建第三张表时,数据库中表的名称。
元信息 ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下: class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") class Meta: unique_together = ("author", "book") db_table ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model' index_together 联合索引。 unique_together 联合唯一索引。 ordering 指定默认按什么字段排序。 ordering = ['pub_date',] 只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)
1.2 关于db_column和verbose_name
1.指定字段名: 在定义字段的时候,增加参数db_column=’real_field’;
2.指定表名: 在model的class中,添加Meta类,在Meta类中指定表名db_table
例如在某个models.py文件中,有一个类叫Info:
class Info(models.Model): ''''' 信息统计 ''' app_id = models.ForeignKey(App) app_name = models.CharField(verbose_name='应用名', max_length=32, db_column='app_name2') class Meta: db_table = 'info' verbose_name = '信息统计' verbose_name_plural = '信息统计'
其中db_column指定了对应的字段名,db_table指定了对应的表明;
如果不这样指定,字段名默认为app_name, 而表明默认为app名+类名: [app_name]_info.
verbose_name指定在admin管理界面中显示中文;verbose_name表示单数形式的显示,verbose_name_plural表示复数形式的显示;中文的单数和复数一般不作区别。
- 表的名称
myapp_modelName
,是根据 模型中的元数据自动生成的,也可以覆写为别的名称 id
字段是自动添加的- 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
- 这个例子中的
CREATE TABLE
SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。 - 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加
models.py
所在应用的名称。 - 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None
二 、添加表记录
2.1 一对一
添加一个名称为王照的作者
方式一
关系属性名称= 模型类对象
- ad:为定义表时关系属性名称
- author_detail_obj:模型类对象
author_detail_obj = models.AuthorDetail.objects.get(id=1) models.Author.objects.create( name='王照', ad=author_detail_obj #1 关系属性名称= 模型类对象 )
方式二
关系属性名称_id = 关联的模型类对象的id值
- ad_id 生成表后显示的外键的字段名
models.Author.objects.create( name='王照', ad_id=1 # author_detail_obj.id # 2 关系属性名称_id = 关联的模型类对象的id值 )
2.2 一对多
添加一个书籍记录
方式一
关系属性名称= 模型类对象
publish_obj = models.Publish.objects.get(id=2) models.Book.objects.create( title='沈阳', publishDate='2020-11-11', price=20, publishs=publish_obj #模型类对象 )
方式二
关系属性名称_id = 关联的模型类对象的id值
models.Book.objects.create( title='沈阳', publishDate='2020-11-11', price=20, publishs_id=2 )
2.3 多对多
其实就是添加多对多关系记录表的关系记录
添加沈阳这本书 是 王照和玉波写
方式1
通过模型类对象添加
o1 = models.Author.objects.get(name='王照') # 2 o2 = models.Author.objects.get(name='玉波') # 3 book_obj = models.Book.objects.get(title='沈阳') book_obj.authors.add(o1, o2) # 2 3 book_obj.authors.add(*[o1, o2]) # 2 3
方式2
直接通过关系属性值添加
#o1 = models.Author.objects.get(name='王照') # 2 #o2 = models.Author.objects.get(name='玉波') # 3 book_obj.authors.add(2, 3) book_obj.authors.add(*[2,3])
二 、删除表记录
一对一和一对多的删改和单表的删改是一样的,别忘了删除表的时候,咱们是做了级联删除的
models.Book.objects.filter(title='沈阳小青年').delete()
多对多
obj = models.Book.objects.filter(title='白洁').first()
obj.authors.remove(2,3) # 4 2和3 # 将第三张表中的这个obj对象对应的那个作者id为2和3的记录删除
obj.authors.clear() # 清除该书籍对象对应的第三张表里面的所有记录
三、修改表记录
一对一和一对多的关系记录操作 和单表一样
models.Book.objects.filter(title='沈阳').update( title='沈阳小青年', # publishs= # 出版社模型类对象 publishs_id=3 )
多对多
obj = models.Book.objects.get(title='沈阳小青年') obj.authors.set(['3','4']) #更新 -- 两步:1 删除之前的关系记录 2 添加新记录
四、基于对象的跨表查询
相当于mysql的子查询
正向:关系属性写在A,那么通过A表数据去查询B表数据时,就是正向查询
反向:反之就是反向查询
4.1 一对一
正向查询: 靠属性
查询一下 王照作者的家庭住址
author_obj = models.Author.objects.get(name='王照') # author_obj.ad # 直接就找到了对应的关系记录对象 print(author_obj.ad.addr)
反向查询: 模型类名小写
查询一下手机号为120的作者名称
author_detail_obj = models.AuthorDetail.objects.get(telephone='120') print(author_detail_obj.author.name)
4.2 一对多 书籍和出版社
正向查询
查询白洁这本书是哪个出版社出版的
book_obj = models.Book.objects.get(title='白洁') print(book_obj.publishs.name) # 红浪漫出版社
反向查询: 模型类名小写_set 提示你:查询结果可能为多条记录
查询33期出版社出版了哪些书
pub_obj = models.Publish.objects.get(name='33期出版社') books = pub_obj.book_set.all().values('title') # 取出反向查询的多条记录 pub_obj.book_set.filter() # 过滤 print(books)
4.2 多对多
多对多
正向查询: 属性
查询一下三国这本书是哪几个作者写的
book_obj = models.Book.objects.get(title='三国') authors = book_obj.authors.all() # book_obj.authors objects控制器,得到的也可能是多条记录 print(authors)
反向查询: 模型类名小写_set
查询一下王照这个作者写了哪些书
author_obj = models.Author.objects.get(name='王照') print(author_obj.book_set.all())
相当于mysql的inner join 连表查询
正向:关系属性写在A,那么通过A表数据去查询B表数据时,就是正向查询
反向:反之就是反向查询
5.1 一对一
正向查询: 靠属性
查询一下 王照作者的家庭住址
#正向连表 ad__ ret = models.Author.objects.filter(name='王照').values('ad__addr') print(ret) #<QuerySet [{'ad__addr': '北京'}]>
反向查询 :模型类名小写
# 反向连表 author__
ret = models.AuthorDetail.objects.filter(author__name='王照').values('addr',)
print(ret) #<QuerySet [{'addr': '北京'}]>
5.2 一对多 书籍和出版社
查询白洁这本书是哪个出版社出版的
正向查询
ret = models.Book.objects.filter(title='白洁').values('publishs__name') print(ret) #<QuerySet [{'publishs__name': '红浪漫出版社'}]>
反向查询
ret = models.Publish.objects.filter(book__title='白洁').values('name') print(ret) #<QuerySet [{'name': '红浪漫出版社'}]>
5.3 多对多
查询一下三国这本书是哪几个作者写的
正向查询
ret = models.Book.objects.filter(title='三国').values('authors__name') print(ret) #<QuerySet [{'authors__name': '王照'}, {'authors__name': '玉波'}]>
反向查询
ret = models.Author.objects.filter(book__title='三国').values('name') print(ret) #<QuerySet [{'name': '王照'}, {'name': '玉波'}]>
聚合查询、分组查询、F查询和Q查询
6.1 聚合查询
aggregate(*args, **kwargs)
# 计算所有图书的平均价格 from django.db.models import Avg Book.objects.all().aggregate(Avg('price')) #或者给它起名 aggretate(a=Avg('price')) #{'price__avg': 34.35}
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。(结果是字典,所以不能在后面继续使用queryset其他方法了)
Book.objects.aggregate(average_price=Avg('price')) # {'average_price': 34.35}
如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询
from django.db.models import Avg, Max, Min Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) #{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
用于对同一张表中多个字段进行操作时使用
查询一下点赞数大于评论数的书籍
ret = models.Book.objects.filter(dianzan__gt=F('comment')).values('title') print(ret)
字段做统一操作时也能有效
书籍价格上调10元
models.Book.objects.all().update( price=F('price') + 10, # 支持四则运算 )
相当于sql语句中的 group by
annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)
annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有,用哪个字段分组,values里面就写哪个字段,annotate其实就是对分组结果的统计,统计你需要什么
查看一下每个出版社出版书的平均价格
# 方式1 ret = models.Book.objects.values('publishs_id').annotate(a=Avg('price')) #select avg('price') as a from app01_book group by publishs_id ;
# 方式2 # ret = models.Publish.objects.all().annotate() # 出版社模型类对象: 包含出版社表的所有字段数据和a这个分组统计结果 #默认以id分组 ret = models.Publish.objects.annotate(a=Avg('book__price')).values('name','a') # select app01_publish.name,avg(app01_book.price) as a from app01_publish inner join app01_book on app01_publish.id = app01_book.publishs_id group by app01_publish.id; print(ret) # <QuerySet [{'name': '33期出版社', 'a': 21.0}, {'name': '红浪漫出版社', 'a': 32.0}, {'name': '大头出版社', 'a': None}]>
统计每一个出版社的最便宜的书
publishList=Publish.objects.annotate(MinPrice=Min("book__price"))
#如果没有使用objects后面values或者values_list,得到的结果是queryset类型,里面是Publish的model对象,并且是对所有记 录进行的统计,统计的Minprice也成了这些model对象里面的一个属性,这种连表分组统计的写法最常用,思路也比较清晰 for publish_obj in publishList: print(publish_obj.name,publish_obj.MinPrice)
annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
queryResult= Publish.objects .annotate(MinPrice=Min("book__price")) .values_list("name","MinPrice") print(queryResult)
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
查询一下点赞数大于等于20 或者 价格大于20的书籍
ret = models.Book.objects.filter(~Q(dianzan__lt=20) | Q(price__gt=20))
查询一下点赞数小于20 或者 价格大于20的书籍, 并且出版日期为2021年的
ret = models.Book.objects.filter(Q(dianzan__lt=20) | Q(price__gt=20), publishDate__year='2021')
Q(dianzan__lt=20) | Q(price__gt=20)是一个条件,和逗号后面的是and的关系,如果有的条件没有用Q包裹,那么这个条件要放到被Q包裹的条件后面
支持 Q 嵌套
ret = models.Book.objects.filter(Q(Q(dianzan__lt=20) | Q(price__gt=20))&Q(publishDate__year='2021')) # 支持 Q 嵌套
print(ret)
七 、对原生sql的操作
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
方式2:
ret = models.Book.objects.filter(dianzan__gt=F('comment')).values('title') print(ret) from django.db import connection print(connection.queries)
方式一
查询一下所有书籍 ret = models.Book.objects.raw('select * from app01_book') # raw只能写操作本表的原生sql # <RawQuerySet: select * from app01_book> print(ret) for i in ret: print(i.title,i.price)
方式2
from django.db import connection # connection -- pymysql连接--conn cursor = connection.cursor() cursor.execute('select * from app01_book;') print(cursor.fetchall()) #((3, '红楼', datetime.date(2021, 2, 4), Decimal('21.00'), 2, 11, 12), (4, '白洁', datetime.date(2021, 2, 4), Decimal('32.00'), 1, 11, 23), (5, '水浒', datetime.date(2021, 2, 4), Decimal('21.00'), 2, 80, 11))
八、orm锁和事务
8.1 锁
行级锁
models.Book.objects.select_for_update().filter(id=1) #手动为id=1加了锁 # select * from app01_book where id=1 for update;
8.2事务
方式1: 视图函数中加事务
do_stuff() 没有什么特殊含义,代表事务执行的代码
from django.db import transaction # 视图中的所有orm或者sql语句都捆绑为了一个事务 @transaction.atomic def viewfunc(request): # This code executes inside a transaction. do_stuff()
from django.db import transaction def viewfunc(request): # This code executes in autocommit mode (Django's default). do_stuff() #with语句中的所有sql加上了事务 with transaction.atomic(): # models.Book.objects.select_for_update().filter(id=1) # This code executes inside a transaction. do_more_stuff() do_other_stuff()
import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_orm02.settings") import django django.setup() from app01 import models ret = models.Book.objects.all() print(ret)