django模型层(二)多表操作
分类:
IT文章
•
2024-06-01 10:20:25
一、 创建模型
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(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值
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')}
6.2 F查询
用于对同一张表中多个字段进行操作时使用
查询一下点赞数大于评论数的书籍
ret = models.Book.objects.filter(dianzan__gt=F('comment')).values('title')
print(ret)
字段做统一操作时也能有效
书籍价格上调10元
models.Book.objects.all().update(
price=F('price') + 10, # 支持四则运算
)
6.3 分组查询
相当于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)
6.4 Q查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 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的操作
7.1 查看原生sql语句
方式1
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)
7.2 执行原生sql语句
方式一
查询一下所有书籍
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()
方式2:
局部逻辑使用事务
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()
九、python脚本调动django环境
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)