Django-ORM框架
对象关系映射模型是通过面向对象的方式来操作数据库,这就需要对应的关系映射,数据中可以分为库,表,字段信息,一条条数据,而需要用面向对象的关系去对应。于是就有了下面对应关系。
数据库 -- 面向对象模型 表 <--> 类 字段 <--> 类属性 记录 <--> 每个实例
Django中的关系映射
使用面向对象的方式描述数据库的关系模型,Django采用了以下的方式。
class Employees(models.Model): # 类名 class Meta: # Meta类中指定表元数据 db_table = "Employees" # 指定数据库中的表名,否则为类名小写 # 每一个model的Field类型属性都对应数据库表中的一条字段。数据类型通过不同类型的Field属性指定,约束条件通过参数传递的方式 emp_no = models.AutoField(primary_key=True, null=False) birth_data = models.DateField(null=False) # 对应日期类型 first_name = models.CharField(null=False, max_length=14) last_name = models.CharField(null=False, max_length=16) # 对应varchar 类型 gender = models.SmallIntegerField(null=False) # 对应int类型 hire_data = models.DateField(null=False)
Fieldl类型
常用类型 | 说明 |
AutoField | 自增整数类型 |
BigAutoField | 更大自增整数类型 |
BigIntegerField | 大整数 |
BooleanField | 布尔类型True或者False |
CharField | 字符串类型 |
DateField | 日期类型 |
DateTimeField | 日期时间 |
DecimalField | 使用python的Decimal实例表示10进制浮点数,需要极高精度的数值使用该字段 |
EmailField | 能做Email检验,基于CharField,最大254 |
FileField | 文件字段 |
FilePathField | 文件路径 |
FloatField | 浮点数 |
ImageField | 继承了FileField,但是对上传的文件进行检验,确保为一个图片 |
IntegerField | int类型 |
GenericIPAddressField | Ipv4/Ipv6校验 |
NullBooleanField | 布尔值可以为空 |
PositiveIntegerField | 正整数 |
TextField | 大文本,超过4000字符 |
TimeField | 时间类型 |
URLField | 能做URL检验,最大200 |
关系映射
ForeignKey一对多关系
关系类型字段可以将两张表进行关联,该字段在一对多方字段建立。例如官方的实例,一个Manufacture可以生产多个车型的汽车,所以在车辆表中建立一个外键(多端),标识该汽车类属于哪一个Manufacture
class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...
创建外键至少需要指定两个参数,指定关联的类和on_delete,主表字段和从表字段之间联系,上面指定为级联,即主表中的某个数据删除,从表中关联了该主表数据的数据将会被删除。
上面Car中的manufacturer字段与Manufacture类进行了关联,在生成的manufacture表中,将会多出一个Car_set的字段,可以通过一条Manufacture信息查询出多条Car信息。而Car中manufacturer字段将被设置为manufacture_id,该数据将会关联到Manufacture中对应的数据。
ManyToManyField多对多关系
官网的示例:一种Topping可能存在于多个Pizza中,并且每个Pizza含有多种Topping,这就是一个多对多的关系
class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
这个属性可以在关联的任何一个类中创建,不能同时在连个类中添加该字段。一般来讲,应该把 ManyToManyField 实例放到需要在表单中被编辑的对象中
在定义的字段类型中,指定某些参数选项,可以将这个字段做一些特殊的设置或者做一些特殊的验证。主要包括以下的字段选项。
字段参数选项
选项 | 说明 |
null | 是否可以null |
blank | 是否可以为空 |
choices | 类似枚举 |
db_column | 指定数据库列名 |
db_index | 是否在该字段建索引 |
db_tablespace | |
default | 默认值 |
editable | |
error_messages | 指定后覆盖默认的错误信息 |
help_text | 指定帮助信息 |
primary_key | 定义为主键 |
unique | 定义为唯一键 |
unique_for_date | |
verbose_name | 字段备注名 |
validators | models.IntegerField(validators=[validate_even]),添加验证函数 |
Manager管理器对象
管理器对象用于实现表的增删改查。管理器对象是类型为django.db.models.manager.Manager类型
,管理器对象可以在定义模型类中创建,如果没有创建,默认会创建一个objects管理器,手动创建后将不会自动创建。
管理器Django查询数据库的接口,每一个模型都至少有一个管理器,用户自定义管理器模型需要继承于django.db.models.manager.Manager
类。
内部测试 manager管理器
根下创建一个Python 文件,写入以下内容即可使用
# 从wsgi复制 import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ORM.settings') application = get_wsgi_application() # 将模型类导入,也就是定义模型类的model文件, from salary.models import Employees # object 是Employees自带的一个管理器,这样我们可以使用这个管理进行查询 mgr = Employees.objects print(mgr.all())
配置日志输出
import os LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django': { 'handlers': ['console'], 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), }, }, }
查询
使用管理器对象查询,将会返回一个查询集,Django内部将这些结果封装为一个Django.db.models.manager.query.Query类型。这个结果是惰性(只有需要时才会真的查询),可以进行迭代。查询的结果不会缓存,再次使用必须再次从数据库查询。
mgr = Employees.objects result_sset = mgr.all() # 查询所有结果,将整个表查询返回结果集
print(mgr.all()) # 获取数据时,才会执行sql查询
并且由于我们的日志配置,我们可以在日志中看到执行sql语句信息,这样方便我们对查询进行调优。
查询方法
方法 | 说明 |
all() | 查询所有结果的查询集 |
filter() | 过滤查询,类似于where |
exclude() | 过滤的结果取反 |
order_by() | 排序结果 类似于order by |
values() | 返回这个查询集,每一个元素是{字段:值}的字典 |
方法 | 说明 |
get(pk=1) | 返回单个值,没有数据和多条数据都将报错 |
count() | 返回查询的总条数 |
first() | 返回第一个对象 |
last() | 返回最后一个对象 |
exist() | 如果能查到返回True |
条件表达式
filter,exclude,get等方法中需要控制查询条件。django使用了一套条件表达式作为查询方法中的参数,实现查询条件筛选。
表达式参数 | 说明 |
=, exact iexact |
等于,mgr.filter(id=1)
不等于 mgr.filter(id__iexact=3) |
gt, gte | 大于,大于等于, mge.filter(id__gt=10001) |
lt, lte | 小于,小于等于 mgr.exclude(id__lte=10010) |
startwith endswith |
首尾匹配,大小写敏感 |
isnull isnotnull |
是否为null |
in | 在某个范围,mgr.filter(pk__in=(1,2,3)) |
contains icontains |
包含指定的值,%值%,效率极低 |
iexact icontains istartwith iendswith |
i,表示忽略大小写 |
year month |
判断一个时间,需要时时间类型的字段,mgr.filter(birth_date__year=2000) 年份为2000的 |
Q 对象
在一个函数中连续写条件表示这些条件是and(与)关系,并不能表示(or)或(!)非的关系,此时需要使用Q对象,Q对象可以使用 |
或, &
与, ~
非;来描述条件之间的关系。
Q 对象的类型:django.db.models.Q
from django.db.models import Q # 使用 Q 对象处理这个条件,并使用 | 连接,表示或关系,& 表示与 mgr.filter( Q(id__lt=10005) | Q(lastname__startswith="K") & Q(age=12)) # 与或 mgr.filter( ~Q(id__lt=10005)) # ~ 表示取反
分组,聚合
聚合函数
from django.db.models import Q, Avg, Sum, Max, Min, Count
aggregate
将所有列的数据分为一组进行聚合
UseInfo.objects.all().aggregate(Sum("age")) # 所有年龄之和,自动使用主键分组 返回一个字典{"age__sum": 120}, 如果想控制age_sum这个key的名字,使用aggregate(agesum=Sum("age"))key将会替换成agesum。
annotate()
annotate可以和value组合实现分组聚合
from django.db.models import Q, Avg, Sum, Max, Min, Count # emp_no 员工编号 # salary 工资 # 使用emp_no分组,计算每个员工的工资总和 s2 = sal.all().values("emp_no").annotate(Sum("salary")) # 返回查询后的结果集 # 只会显示两个字段 # <QuerySet [{'emp_no': 1, 'salary__sum': 1281612}, {'emp_no': 2, 'salary__sum': 413127}]}> # 显示所有字段使用values,或者在values中指定想要的字段,没有使用聚合函数的行使用第一行数据为准 s2.values() # 还可以对其继续排序。等操作 s2.values().order_by("salary_sum")
Raw
直接使用原生的sql语句
empmgr = Employee.objects raw_query = "select * from Employee where id > %s" emp = empmgr.raw(raw_query, params=None)
extra
表示添加额外的部分,select参数表示再select处添加字段以及值,所以使用字段key-value,where表示添加额外的条件,使用列表,and连接内部元素。tables参数表示关联的表。
Userinfo.objects.all().values("id", name) .extra( select={ "total":"select count("id") from table ", "height":"select count("height") from table where gengder=%s", }, select_params=[1,男] where=["user_type.id=userinfo.type_id"], params=[0, 18] tables="app01.user_type" ) 等价的sql "select id, name, select count("id") from table as "total", select count("height") from table as height form (userinfo, app01.user_type) where user_type.id=userinfo.type_id
select_related
会根据指定的字段做连表查询
Userinfo.objects.all().select_related("group") # 查询group 表的内容 select * from `userinfo` inner join `group` on userinfo.group_id = group.id
prefetch_related
效果同select_releted,但是执行多次单表查询,用连表字段的条件进行查询。
Userinfo.objects.all().perfetch_releted("group") select 其他字段, group_id from userinfo # 查处所有group_id select * from group where group.id in group_ids # 更具id查出所有组
ManytoMany
class Boy: id = AutoField() name = models.Charfield() friend = models.ManyToMany("Girl") # 会自动建立第三章表 # f = models.ManyToMany("Girl", through="Friend", through_field = ["b", "g"]) # 使用我们自己建立的第三章表来关联,指定表和字段,第三章表的b,g一定是分别做了外键的才可以 class Girl: id = AutoField() name = models.Charfield() # boy_set 反向找boy对象的属性 obj = models.objects.get(name="小明") # 操作这个第三张表,进行查询操作 obj.friend.add(10) # 对应的一个girl的id值 obj.friend.remove(10) # obj.friend.set([10, 20]) # 设置 obj.friend.clear() obj.friend.all() # 获得所有关联的girl对象,是一个查询集。可以继续filter查询
foreigenKey实现多对多
使用第三张表,第三张表两个字段,分别于其他两个表做外键
class Boy: id = AutoField() name = models.Charfield() class Girl: id = AutoField() name = models.Charfield() class Friend: b = models.ForeigenKey("Boy") g = models.ForeigenKey("Boy")
如果需要查询时小明的所有女性朋友名字,可以有三种方式,
# 小明的所有女性朋友名字 # 第一种 boy = Boy.objects.filter(name="小明").first() # 小明对象 for f in boy.Friend_set: # Friend中和小明有关的所有Fiend对象 g_name = f.g.name # 每个f对象关联了一个girl对象获得 # for 循环中出现了连表,出现了N+1多次查表得问题 # 第二种: 使用join连表,从中间表起手,使用values连表或者select_related都可以 friends = Friend.objects.filter(b__name="小明").values("g__name") # 使用了join连表,得到了 for friend in friends: g_name = friend.g_name # 第三种:perfetch_related多次单表查询 friends = Friend.objects.filter(b__name="小明").perfetch_related("g__name") # 使用了join连表,得到了 for friend in friends: g_name = friend.g_name
建立联合唯一索引
class friend: g = field() b = field() class Meta: unique_together=["g", "b"]