Django模型层—ORM
一、模型层(models)
Django提供了一个orm(关系映射模型)系统,模型包含了一些字段信息以及查询方法
1-1. 常用的字段类型
AutoField
当model中如果没有自增列,则会自动创建一个列名为id的列,int 自增列,必须填入参数primary_key=True
CharField
字符类型 , 必须提供max_length参数, max_length表示字符长度
EmailField
字符串类型,实际在数据库中只是varchar类型,但Django Admin以及ModelForm中提供验证机制需要用到
SmallIntegerField
小整数 ,32768 ~ 32767
IntegerField
整数列(有符号的) ,2147483648 ~ 2147483647
BigIntegerField
长整型(有符号的),9223372036854775808 ~ 9223372036854775807
BooleanField
布尔值类型
TextField
文本类型
FileField
字符串,路径保存在数据库,文件上传到指定目录 参数:
- upload_to = "" 上传文件的保存路径
- storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
DecimalField
10进制小数 ,参数:
- max_digits,小数总长度
- decimal_places,小数位所在占长度
DateTimeField
日期+时间格式, YYYY-MM-DD HH:MM:ss
DateField
日期格式, YYYY-MM-DD
1-2. 字段参数
null
用于表示某个字段可以为空
unique
如果设置为unique=True 则该字段在此表中必须是唯一的
db_index
如果db_index=True 则代表着为此字段设置索引
default
为该字段设置默认值
DateField和DateTimeField
-
auto_now_add
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库
-
auto_now
配置上auto_now=True,每次更新数据记录的时候会更新该字段
1-3. 自定义char字段
# 如何自定义字段类型
class MyCharField(models.Field): # 必须继承Field
def __init__(self,max_length,*args,**kwargs):
self.max_length = max_length # 必须以关键字参数形式传参
# 重新调用父类的方法
super().__init__(max_length=max_length,*args,**kwargs)
def db_type(self, connection):
return 'char(%s)'%self.max_length
1-4. 外键关系
一对一
外键字段创建在任意一张表都可以,建议外键添加在查询频率较高的一方
# 关键字OneToOneField
author_detail = models.OneToOneField(to='Author_detail') # 外键本质fk + unique
一对多
外键字段创建在多的那一方
# 关键字ForeignKey
publish = models.ForeignKey(to='Publish') # to用来指代跟哪张表有关系 默认关联的就是表的主键字段
# 外键字段名在创建时会自动加上_id后缀
多对多
外键关系需要创建第三张表来处理。
# 关键字ManyToManyField
author = models.ManyToManyField(to='Author')
# django orm会自动帮你创建第三张关系表,表名为两个关联的表名用_连接
二、Django中测试脚本的使用
在Django中有一个test.py的测试文件,可用于测试。对于试验orm语句,可以在test中,省略与前端交互的步骤。
在使用前,需要配置好环境,否则测试脚本运行不了
-
直接在某一个应用下的tests.py文件中书写下面内容(与manage.py前四行代码相似),在内部导入Django模块,再手写两句代码即可。
```python
import os
if name == "main":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day53.settings")
import django
django.setup()
# 一定要等待测试脚本搭建完毕之后 才能导入django文件进行测试
# 必须在内部导入应用文件
from app01 import models
...# 测试代码
```
三、单表操作
3-1. 添加记录
- 方式一:返回结果是一个对象
# create方法的返回值book_obj就是插入book表中的红楼梦这本书籍纪录对象
book_obj=models.Book.objects.create(name='红楼梦',price=23.8,publish='人民出版社',author='曹雪芹',create_data='2018-09-17')
- 方式二:先实例化产生对象,然后调用save方法保存
book_obj=models.Book(name='水浒传',price=99,publish='清华出版社',author='施耐庵',create_data='2017-09-17')
book_obj.save()
3-2. 删除记录
- 方式一:
res=models.Book.objects.filter(name='红楼梦').delete()
# res返回的是被影响行数,queryset对象的.delete()方法
# 如果存在多本红楼梦将会被全部删除
- 方式二:
res=models.Book.objects.filter(name='红楼梦').first()
res.delete()
# 只删除一个
3-3. 修改记录
- 方式一:
res=models.Book.objects.filter(name='红楼梦').update(price=20.9)
- 方式二:
# 对象没有update方法,但是可以用save来修改
book=models.Book.objects.filter(name='红楼梦').first()
book.price=33
book.save()
3-4. 单表查询-必知必会13条
1.all()
# 查询所有结果,返回的是QuerySet对象
models.Book.objects.all()
2.filter()
# 指定条件查询,相当于你原生sql语句里面的where关键字,返回的结果QuerySet对象
# filter内可传多个参数,逗号分隔,他们之间是and关系,不支持负数
# 当条件不存在的情况下,filter不报错直接返回一个空
models.Book.objects.filter(name='红楼梦').first()
models.Book.objects.filter(name='红楼梦')[0]
3.get()
# 指定条件查询,有且只有一个结果,返回的是对象,是queryset对象,通常使用id查询,不存在或超过一个会报错
# 当条件不存在的情况下,get直接报错
ret=Book.objects.get(name='红楼梦')
4.exclude()
# 返回与筛选对象不匹配的对象,返回的结果QuerySet对象
models.Book.objects.exclude(price>40)
5.order_by()
# 对查询结果排序,默认升序,倒序使用-号,可以传多个参数,逗号隔开
models.Book.objects.all().order_by('price')
models.Book.objects.all().order_by('-price')
6.reverse()
# 颠倒顺序,必须排好序之后才能颠倒
models.Books.objects.all().order_by('price').reverse()
7.count()
# 查询结果的个数
models.Books.objects.count()
8.first()
# 取queryset中第一个数据对象
models.Books.objects.filter(title='西游记').first()
9.last()
# 取queryset中最后一个数据对象
models.Books.objects.filter(title='西游记').last()
10.exists()
# 如果queryset包含数据就返回True,否则返回False
models.Book.objects.filter(name='红楼梦').exists()
11.values()
# 获取数据对象中指定的字段的值,可以有多个,返回queryset对象,列表套字典的形式
res = models.Books.objects.values('title','price')
12.values_list()
# 获取数据对象中指定的字段的值,可以有多个,返回queryset对象,列表套字典的形式
models.Books.objects.values_list('title','price')
13.distinct()从结果中剔除重复记录
# 对查询结果进行去重操作,去重的前提:数据必须是完全相同的情况下,才能够去重(容易忽略主键)
res = models.Books.objects.values('title','price').distinct()
3-5. orm注意事项
-
只要是queryset对象就可以无限制的调用queryset的方法,类似链式操作
-
orm语句的查询默认都是惰性查询,只有在需要使用时才会执行orm语句
-
只要是queryset对象就可以通过
.query
查看当前结果内部对应的sql语句 -
在Django的settings文件中,配置以下信息,就可以查看所有orm对应的内部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', }, }}
3-6. 单表查询-双下划线
# 基于双下划线的模糊查询
1.大于__gt
models.Book.objects.filter(price__gt='89')
2.小于__lt
models.Book.objects.filter(price__lt='99')
3.小于等于__lte
models.Book.objects.filter(price__lte='99')
4.大于等于__gte
models.Book.objects.filter(price__gte='89')
5.__in 在XX中
ret=models.Book.objects.filter(price__in=['23.8','89','100'])
6.__range 在xx范围内,顾头顾尾
ret=models.Book.objects.filter(price__range=[50,100])
7.__contains 查询名字带有‘红’字的书,默认不忽略大小写
models.Book.objects.filter(name__contains='红')
8.__icontains 查询名字带p的书,忽略大小写
models.Book.objects.filter(name__icontains='P')
9.__startswith 以XX开头
models.Book.objects.filter(name__startswith='红')
10.__endwith 以xx结尾
models.Book.objects.filter(name__endwith='梦')
11.__year 按年查询
models.Book.objects.filter(create_data_year='2018')
12.__month 按月查询
models.Books.objects.filter(publish_date__month='1')
四、多表操作
数据准备
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
publish_date = models.DateField(auto_now_add=True)
publish = models.ForeignKey(to='Publish')
authors = models.ManyToManyField(to='Author')
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=32)
email = models.EmailField()
author_detail = models.OneToOneField(to='AuthorDetail')
def __str__(self):
return self.name
class AuthorDetail(models.Model):
phone = models.BigIntegerField()
addr = models.CharField(max_length=64)
def __str__(self):
return self.addr
-------------------------------------------------------
# 一对一的关系:OneToOneField 模型表的字段,后面会自定加_id
# 一对多的关系:ForeignKey 模型表的字段,后面会自定加_id
# 多对多的关系:ManyToManyField ManyToManyField会自动创建第三张表
4-1. 一对多
1.添加数据
方式一:
models.Book.objects.create(title='三国演义',price=34.5,publish_id=1)
# 直接传表里面的实际字段,跟数据主键值publish_id
方式二:
publish_obj = models.Publish.objects.filter(pk=2).first()
# publish_obj=出版社的对象,存到数据库,是一个id
models.Book.objects.create(title='红楼梦',price=444.33,publish=publish_obj)
# 传虚拟字段跟数据对象即可
2.修改数据
方式一:
models.Book.objects.filter(pk=1).update(publish_id=2)
# 括号内放的是publish_id这个数据库实际字段
方式二:
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)
# 括号内放的是publish,在models创建的字段名
3.删除数据
方式一:
Copyret=models.Book.objects.filter(name='红楼梦').delete()
# ret返回的是被影响行数,queryset对象的.delete()方法
如果存在多本红楼梦将会被全部删除
方式二:
models.Publish.objects.filter(pk=1).delete()
# 默认就是级联删除 级联更新
4-2. 多对多
1.添加数据 add()
# 给当前这一本书绑定作者
# 方式一
book_obj = models.Book.objects.filter(pk=2).first()
# book_obj.authors就是跳到第三张表
book_obj.authors.add(1,2) # 在第三张表里面给书籍绑定主键为1,2的作者
# 方式二
author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
# add中添加对象
book_obj.authors.add(author_obj,author_obj1)
"""
add方法 能够朝第三张关系表添加数据
即支持传数字,add(1,2) , 也支持传对象add(author_obj,author_obj1) , 并且两者都可以是多个
"""
2.删除数据 remove()
# 删除主键为2的书籍的作者
book_obj = models.Book.objects.filter(pk=2).first()
book_obj.authors.remove(1,2)
author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
book_obj.authors.remove(author_obj,author_obj1)
"""
remove既可以传主键数字,也可以传对象,并且都支持传多个, remove(1,2) , remove(author_obj,author_obj1)
"""
3.清空数据 clear()
# 清空,括号内不需要传参 , 删除某个数据在第三张表中的所有记录
# 删除主键为2的书籍的所有相关记录
book_obj = models.Book.objects.filter(pk=2).first()
book_obj.authors.clear()
4.修改数据 set()
# 修改主键为2的书籍的作者
book_obj = models.Book.objects.filter(pk=2).first()
book_obj.authors.set((1,3))
# book_obj.authors.set([1,]) # set括号内必须是可迭代对象,如元组、列表
author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
book_obj.authors.set((author_obj,author_obj1))
"""
set修改多对多关系表中的数据,既可以传数字也可以传对象, 但是需要注意的是括号内必须是可迭代对象
都支持多个 , set((1,3)) , set((author_obj,author_obj1))
"""
4-3. 基于对象的跨表查询
# 基于对象的查询是子查询也就是多次查询
跨表查询,需要分清楚正向和反向的区别
1. 正向查询:关系字段创建在表1,由表1查询表2,就是正向。正向查询按字段
2. 反向查询:关系字段创建在表1,由表2查询表1,就是反向。反向查询按表名小写 + _set
1.一对一 查询数据
正向查询 按字段
反向查询 按表名小写,不需要加_set
正向查询:查询作者是xxx的手机号码
author_obj = models.Author.objects.filter(name='xxx').first()
print(author_obj.author_detail) # author_detail是作者表中的外键字段名
# author_obj.author_detail 就是作者详情的对象
print(author_obj.author_detail.phone)
反向查询:查询手机号是123的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=123).first()
print(author_detail_obj.author) # author是小写的作者表名,得到个作者对象
print(author_detail_obj.author.name)
2.一对多
正向查询 按字段
反向查询 按表名小写+_set , 如果有多条数据再加.all()
正向查询:查询红楼梦这本书的出版社
book_obj=Book.objects.filter(name='红楼梦').first()
print(book_obj.publish) # 正向查询按字段,直接.字段名 ,获得出版社对象
print(book_obj.publish.name) # 获得出版社对象的名字
反向查询:查询出版社是上海出版社出版过的书籍
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
print(publish_obj.book_set) # 多对多的反向查询需要写 book_set (小写表名_set)
# 因为有多条数据,需要加.all()
print(publish_obj.book_set.all())
3.多对多
正向查询 按字段
反向查询 按表名小写+_set , 如果有多条数据再加.all()
正向查询:查询红楼梦这本书的所有作者
book_obj = models.Book.objects.filter(title='红楼梦').first()
print(book_obj.authors) # 正向查询用字段,authors是书籍表的外键字段名
# 如果有多条数据就用all()
print(book_obj.authors.all())
反向查询:查询作者是xxx写过的书籍
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.book_set) # 多对多的反向查询需要写 book_set (小写表名_set)
# 有多条数据就用all()
print(author_obj.book_set.all())
4-4. 基于双下划线查询
1.一对一
正向:按字段,跨表可以在filter,也可以在values中 按字段
反向:按表名小写,跨表可以在filter,也可以在values中 按表名小写
# models后面点的哪张表 , 就以哪张表为基表
正向查询:查询作者xxx的手机号
res=models.Author.object.filter(name='xxx').values('author_detail__phone')
# 基表为作者表,所以values可以通过作者表的author_detail字段 跨到详情表,从而获取手机号
反向查询:查询作者xxx的手机号
res=models.AuthorDetail.object.filter(author__name='xxx').values('phone')
# 基表为详情表,在filter内跨表
2.一对多
正向查询:查询出版社为上海出版社出版的所有图书的名字,价格
res=models.Book.objects.filter(publish_name='上海出版社').values('name','price')
反向查询:
res=models.Publish.objects.filter(name='上海出版社').values('book_name','book_price')
3.多对多
正向查询:查询红楼梦的所有作者名字
res=models.Book.objects.filter(title='红楼梦').values('authors_name')
反向查询:查询红楼梦的所有作者名字
res=models.Author.objects.filter(book__title='红楼梦').values('name')
4.联表操作
# 1.查询书籍pk为2的出版社名称
正向查询:基于有外键字段的书籍表查询
res = models.Book.objects.filter(pk=2).values('publish__name')
# 写外键字段就相当于已经跨到外键字段所关联的表
print(res)
反向查询:基于没有外键关系的出版社表查询
res = models.Publish.objects.filter(book__pk=2).values('name')
# 你想要改表的哪个字段信息 你只需要加__获取即可
print(res)
# 2.查询书籍pk为2的作者姓名和邮箱
正向查询:基于有外键字段的书籍表查询
res = models.Book.objects.filter(pk=2).values('authors__name','authors__email')
print(res)
反向查询:基于没有外键关系的出版社表查询
res = models.Author.objects.filter(book__pk=2).values('name','email')
print(res)
五、聚合查询
聚合查询需要用到一个关键字aggregate()
,并配合聚合函数一起使用。
用到的内置函数:
from django.db.models import Avg, Sum, Max, Min, Count
例子:
from django.db.models import Avg, Sum, Max, Min, Count
# 筛选出价格最高的书籍的,默认聚合值名为 price__max (字段名__聚合函数名)
res = models.Book.objects.aggregate(Max('price'))
也可以为聚合值指定一个名称
# 求书籍总价格,为得到的值取price_sum
res = models.Book.objects.aggregate(price_sum = Sum('price'))
生成多个聚合,aggregate()
可添加多个参数
# 查询书籍最高价格、最低价格、价格总和、记录条数、平均价格
res =models.Book.objects.aggregate(
Max('price'),Min('price'),Sum('price'),Count('price'),Avg('price')
)
六、分组查询
分组查询需要用到annotate()
关键字,分组查询一般是和聚合函数连用
1.统计每一本书的作者个数 书名 和对应的作者人数
res = models.Book.objects.annotate(
author_num=Count('authors__id')).values('title','author_num')
2.统计出每个出版社卖的最便宜的书的价格 出版社的名字 价格
res = models.Publish.objects.annotate(
min_price=Min('book__price')).values('name','min_price')
3.统计不止一个作者的图书
res = models.Book.objects.annotate(
author_num=Count('authors')).filter(author_num__gt=1).values('title','author_num')
4.查询各个作者出的书的总价格,作者名字,总价格
res = models.Author.objects.annotate(
sum_price=Sum('book__price')).values('name','sum_price')
总结:
1.values在annotate前表示分组,在后表示取值
2.filter在annotate前表示where条件,在后表示having
3.annotate本身表示group by的作用,前面找寻分组依据,内部放置显示可能用到的聚合运算式,后面跟filter来增加限制条件,最后的value来表示分组后想要查找的字段值
七、F查询
F查询:对两个字段的值做比较
F能够获取表中字段所对应的值,F()的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值
例子:
# 查询库存大于卖出的书籍
res = models.Book.objects.filter(kun_cun__gt = F('mai_cun')).values('title')
Django支持F()对象之间以及F()对象和常数之间的算术运算和取模的操作
例子:
# 将每本书的价格提升100
res = models.Book.objects.all().update(F('price')+100)
如果要修改char字段的数据,要对字符串进行拼接Concat操作,并且要加上拼接值value
例子:
# 在所有书的名字后面加上“爆款”
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.update(title=Concat(F('title'),Value('爆款')))
八、Q查询
filter()等方法进行的是'AND',更复杂的查询需要Q查询
表示与 & ,或 | ,非 ~
#查询是作者A也或者是作者B的书
Book.object.all().filter(Q(author_name='A')|Q(author_name='B'))
filter()等方法中逗号隔开的条件是 与(and)的关系。
#查询书名是三国演义 and 库存为500的书
res = models.Book.objects.filter(Q(title='三国演义'),Q(kun_cun=500))
#查询书名是三国演义 or 库存为500的书
res = models.Book.objects.filter(Q(title='三国演义')|Q(kun_cun=500))
#查询书名是三国演义 not 库存为500的书
res = models.Book.objects.filter(~Q(title='三国演义')|Q(kun_cun=500))
Q对象高级用法
# 不适用字段名,使用字符串
q = Q() # 实例化一个对象q
q.connector = 'or' # 默认是and 可以改为or
q.children.append(('title','三国演义'))
q.children.append(('kun_cun__gt', 500))
res = models.Book.objects.filter(q)
九、ORM中的事务操作
9-1. 什么是事务
将多个sql语句操作编程原子性操作,要么同时成功,否则有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性
9-2. 事务的特性(ACID)
-
原子性(Atomicity)
要么都执行,要么都不执行。
假如我们有个方法中对一个属性进行了N次的更新,但是执行到一半的时候有一个语句有问题出现了异常,这样就可能使得我们上面所说的操作后的点与我们预先的点不同,这不是我们想要的,所以原子性要求你这个方法要么全部执行成功,要么你就别执行。
-
一致性(Consistency)
只有两种状态:提交成功前、提交成功后。不会有中间状态,如修改了一半的。
原子性中规定方法中的操作都执行或者都不执行,但并没有说要所有操作一起执行(一起更新那就乱套了,要哪个结果?),所以操作的执行也是有先后顺序的,那我们要是在执行一半时查询了数据库,那我们会得到中间的更新的属性?答案是不会的,一致性规定事务提交前后只存在两个状态,提交前的状态和提交后的状态,绝对不会出现中间的状态。
-
隔离性(Isolation)
串行化,使得事务执行不会混乱。
事务的隔离性基于原子性和一致性,每一个事务可以并发执行,但是他们互不干扰,但是也有可能不同的事务会操作同一个资源,这个时候为了保持隔离性会用到锁方案。
-
持久性(Durability)
事务成功执行后,修改的状态将一直保持。
当一个事务提交了之后那这个数据库状态就发生了改变,哪怕是提交后刚写入一半数据到数据库中,数据库宕机(死机)了,那当你下次重启的时候数据库也会根据提交日志进行回滚,最终将全部的数据写入。
十, 数据库三大范式
数据库设计范式
什么是范式:简言之就是,数据库设计对数据的存储性能,还有开发人员对数据的操作都有莫大的关系。所以建立科学的,规范的的数据库是需要满足一些
规范的来优化数据数据存储方式。在关系型数据库中这些规范就可以称为范式。
什么是三大范式:
第一范式:当关系模式R的所有属性都不能在分解为更基本的数据单位时,称R是满足第一范式的,简记为1NF。满足第一范式是关系模式规范化的最低要
求,否则,将有很多基本操作在这样的关系模式中实现不了。
第二范式:如果关系模式R满足第一范式,并且R得所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式,简记为2NF。
第三范式:设R是一个满足第一范式条件的关系模式,X是R的任意属性集,如果X非传递依赖于R的任意一个候选关键字,称R满足第三范式,简记为3NF.
注:关系实质上是一张二维表,其中每一行是一个元组,每一列是一个属性
理解三大范式
第一范式
1、每一列属性都是不可再分的属性值,确保每一列的原子性
2、两列的属性相近或相似或一样,尽量合并属性一样的列,确保不产生冗余数据。
如果需求知道那个省那个市并按其分类,那么显然第一个表格是不容易满足需求的,也不符合第一范式。
显然第一个表结构不但不能满足足够多物品的要求,还会在物品少时产生冗余。也是不符合第一范式的。
第二范式
每一行的数据只能与其中一列相关,即一行数据只做一件事。只要数据列中出现数据重复,就要把表拆分开来。
一个人同时订几个房间,就会出来一个订单号多条数据,这样子联系人都是重复的,就会造成数据冗余。我们应该把他拆开来。
这样便实现啦一条数据做一件事,不掺杂复杂的关系逻辑。同时对表数据的更新维护也更易操作。
第三范式
数据不能存在传递关系,即没个属性都跟主键有直接关系而不是间接关系。像:a-->b-->c 属性之间含有这样的关系,是不符合第三范式的。
比如Student表(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)
这样一个表结构,就存在上述关系。 学号--> 所在院校 --> (院校地址,院校电话)
这样的表结构,我们应该拆开来,如下。
(学号,姓名,年龄,性别,所在院校)--(所在院校,院校地址,院校电话)
最后:
三大范式只是一般设计数据库的基本理念,可以建立冗余较小、结构合理的数据库。如果有特殊情况,当然要特殊对待,数据库设计最重要的是看需求跟性能,需求>性能>表结构。所以不能一味的去追求范式建立数据库。