Django之ORM连表操作
一 1对1(OneToOneField)
简单代码如下:
class UserGroup(models.Model): """部门""" department = models.CharField(max_length=32) class UserInfo(models.Model): """员工信息""" uid = models.AutoField(primary_key=True) # 若无指定,Django为默认生成以id命名的一列自增数据 username = models.CharField(max_length=32) # 必须指定长度 ug = models.ForeignKey("UserGroup",on_delete=models.CASCADE,) # 生成数据库后,列名称变更为ug_‘外键表的id’ class UserDeital(models.Model): '''员工详细''' joindata = models.DateField() age = models.IntegerField(default=18) email = models.EmailField() phone = models.IntegerField(max_length=12) user = models.OneToOneField('UserInfo',on_delete=models.CASCADE)
查
# 正向操作 deital_obj = models.UserDeital.objects.filter(age=24).first() print(deital_obj.user.username) # 反向操作 user_obj = models.UserInfo.objects.filter(username='张三1').first() print(user_obj.userdeital.email,user_obj.userdeital.phone)
更多操作可以参考下面的内容。
二 1对多(ForeignKey)
何为一对多?如果A表中的1条数据对应B表中N条数据,两表之间就是1对多关系;在1对多关系中 A表就是主表,B表为子表,ForeignKey字段就建在子表
我们还是结合之前的例子(去掉password字段,加入jiondata字段),,代码如下:
from django.db import models class UserGroup(models.Model): """部门""" department = models.CharField(max_length=32) class UserInfo(models.Model): """员工信息""" uid = models.AutoField(primary_key=True) #若无指定,Django为默认生成以id命名的一列自增数据 username = models.CharField(max_length=32) #必须指定长度 password = models.CharField(max_length=64) age = models.IntegerField(default=18) joindata = models.DateField() ug = models.ForeignKey("UserGroup",on_delete=models.CASCADE,) # 生成数据库后,列名称变更为ug_‘外键表的id’
我们从以上表结构可以看出:usergroup为主表,userinfo为子表。
2.1 增
# 方式一:直接通过字表指定ug_id添加,前提是主表里面必须有该数据 models.UserInfo.objects.create(username="张三1", age=22, joindata="2015-6-6", ug_id=1) # 方式二:获取主表对象某个后,通过create()添加 depart_obj = models.UserGroup.objects.filter(department="人力资源部").first() models.UserInfo.objects.create(username="张三1", age=22, joindata="2015-6-6", ug=depart_obj) #添加对象时为ug,而不是ug_id # 方式三:获取主表对象某个后,通过save()添加 depart_obj = models.UserGroup.objects.get(department="人力资源部") # 只有一个的时候用get,拿到的直接就是一个对象 user_obj = models.UserInfo(username="张三1", age=22, joindata="2015-6-6", ug=depart_obj) user_obj.save()
2.2 查
我们查询到的数据类型本质上都是queryset类型(类似于列表),内部有三种表现形式:对象、字典、元组。
正向操作
# 1.以对象的形式进行查询(通过“对象.外键字段.关联表字段”),该方式取的时候跨表
obj = models.UserInfo.objects.all().first()
print(obj) # UserInfo object (1)
print(obj.username, obj.age, obj.ug.department) # 张三1 22 人力资源部
# 2.以字典的形式进行查询(关键字:values),该方式查的时候跨表
dict1 = models.UserInfo.objects.filter(uid__gt=3).values('username', 'age', 'ug__department')
print(dict1) # <QuerySet [{'ug__department': 'IT', 'age': 23, 'username': '张三4'}, ...]>
for item in dict1:
print(item['username'], item['age'], item['ug__department']) # 张三4 23 IT,...
# 筛选
obj1 = models.UserInfo.objects.filter(ug__department='IT').values('username', 'age', 'ug__department')
print(obj1) # <QuerySet [{'ug__department': 'IT', 'age': 25, 'username': '张三3'}, ...]>
# 3.以元组的形式进行查询(关键字:values_list),该方式查的时候跨表
list1 = models.UserInfo.objects.filter(uid__gt=3).values_list('username', 'age', 'ug__department')
print(list1) # <QuerySet [('张三4', 23, 'IT'), ('张三5', 28, '公关部')]>
for item in list1:
print(item[0], item[1], item[2]) # 张三4 23 IT,...
# 筛选
obj2 = models.UserInfo.objects.filter(ug__department='IT').values_list('username', 'age', 'ug__department')
print(obj2) # <QuerySet [('张三3', 25, 'IT'), ('张三4', 23, 'IT')]>
反向操作
# 1.以对象的形式进行查询(通过“表名小写_set.all()”)
obj = models.UserGroup.objects.all().first()
print(obj.id, obj.department) # 1 人力资源部
for row in obj.userinfo_set.all():
print(row.username, row.age) # 张三1 22, 张三2 19
# 筛选
result = models.UserGroup.objects.all()
for item in result:
print(item.department, item.userinfo_set.filter(username='张三1'))
# 人力资源部 <QuerySet [<UserInfo: UserInfo object (1)>]>,IT <QuerySet []>,公关部 <QuerySet []>
# 2.以字典的形式进行查询,该方式查的时候跨表
obj1 = models.UserGroup.objects.values("id", 'department', 'userinfo') # 通过‘小写表名’获取id
print(obj1) # <QuerySet [{'department': '人力资源部', 'id': 1, 'userinfo': 1}, ...]>
obj2 = models.UserGroup.objects.values("id", 'department', 'userinfo__username') # 通过‘小写表名__列名’获取相应数据
print(obj2) # <QuerySet [{'department': '人力资源部', 'id': 1, 'userinfo__username': '张三1'}, ...]>
# 筛选
obj3 = models.UserGroup.objects.filter(userinfo__age=22).values('id', 'department', 'userinfo__username')
print(obj3) # <QuerySet [{'department': '人力资源部', 'id': 1, 'userinfo__username': '张三1'}]>
# 3.以元组的形式进行查询,该方式查的时候跨表
obj4 = models.UserGroup.objects.values_list("id", 'department', 'userinfo') # 通过‘小写表名’获取id
print(obj4) # <QuerySet [(1, '人力资源部', 1), (1, '人力资源部', 2), (2, 'IT', 3), (2, 'IT', 4), (3, '公关部', 5)]>
obj5 = models.UserGroup.objects.values_list("id", 'department', 'userinfo__username') # 通过‘小写表名__列名’获取相应数
print(obj5) # <QuerySet [(1, '人力资源部', '张三1'),...]>
# 筛选
obj6 = models.UserGroup.objects.filter(userinfo__age=22).values_list('id', 'department', 'userinfo__username')
print(obj6) # <QuerySet [(1, '人力资源部', '张三1')]>
补充:我们可以在ForeignKey字段中加入related_name属性,指定通过该属性值进行反向关联。注意:该方式与小写表名方式不能共存。
总结:
正向:
- 对象.外键字段.关联表字段
- values(外键字段__关联表字段)
- values_list(外键字段__关联表字段)
- filter(外键字段__关联表字段)
反向:
- 对象:对象.小写表名_set.all() / 对象:对象.related_name属性值.all()
- value、value_list:'小写表名' —> 获取id / 'related_name属性值' —> 获取id
- value、value_list:'小写表名__关联表字段' —> 获取相应字段数据 / 'related_name属性值__关联表字段' —> 获取相应字段数据
- filter(小写表名__关联表字段) / filter(related_name属性值__关联表字段)
2.3 1对多自关联( 由原来的2张表,变成一张表)
想象有第二张表,关联自己表中的行

举个例子,在网页上对新闻做评论的结构:

这时我们就需要1对多自关联,表结构如下:
class Comment(models.Model):
"""评论表"""
news_id = models.IntegerField() # 新闻ID
content = models.CharField(max_length=32) # 评论内容
user = models.CharField(max_length=32) # 评论者
reply = models.ForeignKey('Comment',null=True,blank=True) # 回复ID
我们向数据库插入数据形式如下:

需要注意的是:reply ID要么为空,要么填入已经存在的评论id,它是针对某个评论,而不是针对某个人。
三 多对多
何为多对多?如果A表中的1条数据对应B表中N条数据,且B表的1条数据也对应A表中N条数据,两表之间就是双向1对多关系,即称为多对多关系
3.1 自定义第三张表
接着上面员工部门的例子,我们在新增两个数据表,具体如下:
class HobbyList(models.Model):
'''爱好清单'''
hobby = models.CharField(max_length=16)
class U2H(models.Model):
user = models.ForeignKey("UserInfo",on_delete=models.CASCADE,)
hobby = models.ForeignKey("HobbyList",on_delete=models.CASCADE,)
# 根据实际情况,我们可以加上联合唯一索引
class Meta:
unique_together = [
('user','hobby'),
]
以上我们增加爱好清单,然后用另外一张数据表类存储员工与爱好之间的关系,以实现爱好与员工之间形成多对多的关系
员工表:

爱好表:

增
# 先查询或创建一个员工对象:
user_obj = models.UserInfo.objects.filter(username="张三1").first()
# 创建需要关联的爱好
hobby_obj1 = models.HobbyList.objects.filter(hobby="篮球").first()
hobby_obj2 = models.HobbyList.objects.filter(hobby="台球").first()
# 通过for循环进行添加
obj_list = [hobby_obj1,hobby_obj2]
for obj in obj_list:
models.U2H.objects.create(user=user_obj,hobby=obj)
第三张关系表:

查
# 获取张三1的爱好清单
# 方式一:通过userinfo表进行反向查找u2h,再正向查找
obj = models.UserInfo.objects.filter(username='张三1').first()
obj_list = obj.u2h_set.all()
for i in obj_list:
print(i.hobby.hobby) # 篮球、台球
# 方式二:通过u2h表查询
obj = models.U2H.objects.filter(user__username='张三1').all()
print(obj) # <QuerySet [<U2H: U2H object (1)>, <U2H: U2H object (2)>]>
for i in obj:
print(i.hobby.hobby) # 篮球、台球
# 注意:这样进行查询性能不好,在循环时需要重新发sql请求再进行查询另外一张表中的数据
# 方式三:(推荐)
obj = models.U2H.objects.filter(user__username='张三1').values('hobby__hobby')
print(obj) # <QuerySet [{'hobby__hobby': '篮球'}, {'hobby__hobby': '台球'}]>
for i in obj:
print(i['hobby__hobby']) # 篮球、台球
# 这样我们进行了优化,不会重复发送sql请求,当然我们也可以使用values_list,只是取到的结果表现形式不同
# 方式四:查询主动做连表(推荐)
obj = models.U2H.objects.filter(user__username='张三1').select_related('hobby')
print(obj) # <QuerySet [<U2H: U2H object (1)>, <U2H: U2H object (2)>]>
for i in obj:
print(i.hobby.hobby) # 篮球、台球
# select_related('hobby') 相当于inner join先连成一张表再进行查询
3.2 通过ManyToManyField生成第三张表
示例如下:

代码如下:
class UserInfo(models.Model): """员工信息""" uid = models.AutoField(primary_key=True) # 若无指定,Django为默认生成以id命名的一列自增数据 username = models.CharField(max_length=32) # 必须指定长度 age = models.IntegerField(default=18) joindata = models.DateField() ug = models.ForeignKey("UserGroup",on_delete=models.CASCADE,) # 生成数据库后,列名称变更为ug_‘外键表的id’ hb = models.ManyToManyField("Hobby") class HobbyList(models.Model): '''爱好清单''' hobby = models.CharField(max_length=16)
增
# 先查询或创建一个员工对象: user_obj = models.UserInfo.objects.filter(username="张三1").first() # 创建需要关联的爱好 hobby_obj1 = models.HobbyList.objects.filter(hobby="篮球").first() hobby_obj2 = models.HobbyList.objects.filter(hobby="台球").first() # 通过add绑定多对多关系 user_obj.hb.add(hobby_obj1,hobby_obj2) #可同时添加一个或多个,知道id我们可以直接:user_obj.hb.add(1,2,3,) # 如果我们需要将所有爱好与某一个员工进行关联,可以这样做: user_obj = models.UserInfo.objects.filter(username="张三2").first() hobby_objs = models.HobbyList.objects.all() user_obj.hb.add(*hobby_objs) #知道id我们可以直接:user_obj.hb.add(*[1,2,3,]) # user_obj.hb.set(hobby_objs) #同时添加多个对象,不需要加*
操作步骤:找到需要创建多关系的一个对象 –> 获取需要添加的多个对象 –> 用add方法绑定
删:remove # 将部分特定的对象从被关联对象集合中去除
user_obj = models.UserInfo.objects.filter(username="张三2").first() # 找到员工对象 hobby_obj = models.HobbyList.objects.filter(hobby="台球").first() # 找到符合条件的作者对象 user_obj.hb.remove(hobby_obj) # 也可以通过该方式解除多个对象:user_obj.hb.remove(hobby_obj1,hobby_obj3,…);知道id可以用其替换对象 # 如果需要解除多个对象的绑定[queryset/列表形式] user_obj.hb.remove(*hobby_objs) # 因为解除的是多条,得加个*;知道id可以用其替换对象
清除绑定:clear # 清空被关联对象集合
user_obj = models.UserInfo.objects.filter(username="张三2").first() # 找到员工对象 user_obj.hb.clear() # 如果需要对多个对象进行清除绑定操作,我们可以使用for循环 for user_obj_item in user_obj: user_obj_item.hb.clear()
remove和clear的区别
- remove:将需要清除的数据筛选出来,然后移除
- clear:不用查,直接把数据都清空
重置
user_obj = models.UserInfo.objects.filter(username="张三2").first() hobby_objs = models.HobbyList.objects.all() user_obj.hb.set(hobby_objs) #修改,之前的关系删除,注意:hobby_objs为可迭代对象;知道id可直接用其进行操作 # user_obj.hb.set([1,2,3])
查
# 正向查询:查询张三1的爱好清单
user_obj = models.UserInfo.objects.filter(username='张三1').first()
hobby_list = user_obj.hb.all().values_list("hobby") #user_obj.hb.all()获取到的对象为与张三1相关的爱好对象
for hobby in hobby_list:
print(hobby[0])
# 反向查询:查询爱好为篮球的有那几个员工
hobby_obj = models.HobbyList.objects.filter(hobby='篮球').first()
user_list = hobby_obj.userinfo_set.all() #获取到的对象为爱好为篮球的员工对象
for user in user_list:
print(user.username)
反向的增、删等操作与正向一致,不再敖述。
3.3 自定义第三张表的同时使用ManyToManyField
实例如下:

代码入下:
class UserInfo(models.Model): """员工信息""" uid = models.AutoField(primary_key=True) # 若无指定,Django为默认生成以id命名的一列自增数据 username = models.CharField(max_length=32) # 必须指定长度 age = models.IntegerField(default=18) joindata = models.DateField() ug = models.ForeignKey("UserGroup", on_delete=models.CASCADE, ) # 生成数据库后,列名称变更为ug_‘外键表的id’ bh = models.ManyToManyField("HobbyList",through='U2H',through_fields=('user','hobby')) # 只让其生成3张表,如果不加后面的参数产生生成4张表 class HobbyList(models.Model): '''爱好清单''' hobby = models.CharField(max_length=16) class U2H(models.Model): user = models.ForeignKey("UserInfo",on_delete=models.CASCADE,) hobby = models.ForeignKey("HobbyList",on_delete=models.CASCADE,) # 根据实际情况,我们可以加上联合唯一索引 class Meta: unique_together = [ ('user','hobby'), ]
这种杂交用法有如下特点:
1. ManyToManyField()字段创建第3张关系表,可以使用字段跨表查询,但无法直接操作第3张表
- obj.m2m字段.all() 只有查询和清空方法(这点使我们可以简单的进行操作)
2. 自定义的第三张关系表操作与之前一致
3.4 使用choices表混合
举一个简单的例子,我们将男女放在同一张表中,然后建立第二张表让第一张表中的男女配对。
实例如下:

不加related_name报错图片:

原因分析:
按照常规的做法,我们可以将男女分开为两张表,然后用第三张表来存储男女之间的配对关系,添加FK即可,这样两个外键对应2张表,2个主键,可以识别男女。 但是现在两个外键对应1张表,反向查找,无法区分男女:
object对象女.Userinfo_set.all() object对象男.Userinfo_set().all
所以我们需要加入参数:related_name/related_query_name
related_name/related_query_name的使用 *****
这两个参数可以指定别名,主要方便反向查找:
- related_name:obj.别名.all()
- related_query_name:obj.别名_set.all()
注意:不知道是不是版本的问题,此例使用related_query_name也会报错,待续。。。
代码如下:
class UserInfos(models.Model): '''通过choices字段合并为一张表,第三张关系表使用models.ManyToManyField('Userinfos')生成''' nickname = models.CharField(max_length=32) username = models.CharField(max_length=32) sex = ((1, '男'), (2, '女')) gender = models.IntegerField(choices=sex) class U2U(models.Model): b = models.ForeignKey(UserInfos,related_name='boys',on_delete=models.CASCADE,) g = models.ForeignKey(UserInfos,related_name='girls',on_delete=models.CASCADE,)
增
userinfos表增加数据:略
u2u表增加数据有以下两种方式:
# 方式一:直接通过id添加 models.U2U.objects.create(b_id=1,g_id=2) # 方式二:通过对象添加 boy_obj = models.UserInfos.objects.filter(id=1).first() girl_obj = models.UserInfos.objects.filter(id=3).first() # models.U2U.objects.create(b_id=boy_obj.id, g_id=girl_obj.id) #也是通过id添加,与方式一一致 models.U2U.objects.create(b=boy_obj, g=girl_obj)
查
# 查询与id为1的男性有关系的女性昵称 boy_obj = models.UserInfos.objects.filter(id=1).first() girl_objs = boy_obj.boys.all() # 反向查找,获取到U2U queryset对象,此时方式由boy_obj.u2u_set.all() -- > boy_obj.boys.all() for girl_obj in girl_objs: print(girl_obj.g.nickname) #正向查找获取昵称
3.5 多对多自关联
把两张表通过 choices字段合并为一张表,第三张关系表使用ManyToManyField()生成,相当于由原来的三张表变成了两张表。
实例如下:

表格样式:


代码如下:
class UserInfos(models.Model):
'''通过choices字段合并为一张表,第三张关系表使用models.ManyToManyField('Userinfos')生成'''
nickname = models.CharField(max_length=32)
username = models.CharField(max_length=32)
sex = ((1, '男'), (2, '女'))
gender = models.IntegerField(choices=sex)
m = models.ManyToManyField('UserInfos')
思考:怎么样通过代码的形式在第二张表中添加数据?
查
# 查询第一列数据:from_userinfos_id,默认第一列数据为男士 boy_obj = models.UserInfos.objects.filter(id=1).first() girl_objs = boy_obj.u.all() # 数据库查询代码:select xx from xx where from_userinfo_id = 1 for girl_obj in girl_objs: print(girl_obj.nickname) # 查询第二列数据:from_userinfos_id,默认第二列数据为男士 girl_obj = models.UserInfos.objects.filter(id=2).first() boy_objs = girl_obj.userinfos_set.all() # 数据库查询代码:select xx from xx where to_userinfo_id = 2 for boy_obj in boy_objs: print(boy_obj.nickname)
注意点:需要我们自己指定第一列、第二列放什么参数,查询时也需要按照我们自己的指定进行操作。
四 性能相关
查询
# 1.普通查询
obj_list = models.U2H.objects.filter(user__username='张三1').all()
for obj in obj_list: # for循环10次发送10次数据库查询请求
print(obj.hobby.hobby)
# 特点:性能不好,进行多次数据库查询请求(不推荐使用)
# 2.使用select_related()
obj_list = models.U2H.objects.filter(user__username='张三1').select_related('hobby')
for obj in obj_list:
print(obj.hobby.hobby)
# 特点:查询主动做连表,for循环时不用额外发请求,性能较高;适用于数据量少的情况
# 数据库操作代码:
# SELECT "app01_u2h"."id", "app01_u2h"."user_id", "app01_u2h"."hobby_id", "app01_hobbylist"."id", "app01_hobbylist"."hobby"
# FROM "app01_u2h" INNER JOIN "app01_userinfo" ON ("app01_u2h"."user_id" = "app01_userinfo"."uid")
# INNER JOIN "app01_hobbylist" ON ("app01_u2h"."hobby_id" = "app01_hobbylist"."id")
# WHERE "app01_userinfo"."username" = '张三1';
# 3.使用prefetch_related()
obj_list = models.U2H.objects.filter(user__username='张三1').prefetch_related('hobby')
print(obj_list)
for obj in obj_list:
print(obj.hobby.hobby)
# 特点:不做连表,做多次查询(去重);适用于数据量多、需要频繁查询的情况
# 数据库操作代码(有N个外键做1+N次单表查询):
# SELECT "app01_u2h"."id", "app01_u2h"."user_id", "app01_u2h"."hobby_id"
# FROM "app01_u2h" INNER JOIN "app01_userinfo"
# ON ("app01_u2h"."user_id" = "app01_userinfo"."uid")
# WHERE "app01_userinfo"."username" = '张三1';
# SELECT "app01_hobbylist"."id", "app01_hobbylist"."hobby"
# FROM "app01_hobbylist"
# WHERE "app01_hobbylist"."id" IN (1, 3);
更新:update()更新方式 优于 对象.save()更新方式
五 其他
1. choices详细
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。
每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或ModelChoiceField中用作显示的内容。
在一个给定的model类的实例中,想得到某个choices字段的显示值,就调用get_FOO_display方法(这里的FOO就是choices字段的名称 )。例如:
class Flower(models.Model):
colors = (
('1', '白色'),
('2', '红色'),
('3', '黄色'),
)
f_name = models.CharField(max_length=32)
f_color = models.CharField(max_length=4, choices=colors)
操作
# 添加数据 models.Flower.objects.create(f_name='百合', f_color=1) # 查询数据 obj = models.Flower.objects.filter(f_name='百合').first() print(obj) print(obj.f_color) print(obj.get_f_color_display()) #注意加括号
2. null/blank的区别
- null 如果为True,Django 将用NULL 来在数据库中存储空值, 默认值是 False
- blank 如果为True,该字段允许不填,默认为False
- null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
六 总结
6.1 三种表现形式
- models.xxx.objects.filter(*args, **kwargs) –> querySet [obj1,obj2]
- models.xxx.objects.filter(*args, **kwargs).values(*args) --> querySet [{},{},{}]
- models.xxx.objects.filter(*args, **kwargs).values_list(title) –> querySet [(),(),()]
6.2 查询方式
1对1查询
- 正向查询:对象.1To1字段名.xxxx
- 反向查询:对象.小写1To1表名.xxxx
1对多查询
正向:
- 字段值:对象.外键字段.关联表字段
- 对象:对象.M2M字段.all().values('xxx’)
- values(外键字段__关联表字段)
- values_list(外键字段__关联表字段)
- filter(外键字段__关联表字段)
反向:
- 字段值:对象.小写表名_set.关联表字段 / 字段值:对象.related_name属性值.关联表字段
- 对象:对象.小写表名_set.all().values('xxx’) / 对象:对象.related_name属性值.all().values('xxx’)
- value、value_list:'小写表名' —> 获取id / 'related_name属性值' —> 获取id
- value、value_list:'小写表名__关联表字段' —> 获取相应字段数据 / 'related_name属性值__关联表字段' —> 获取相应字段数据
- filter(小写表名__关联表字段) / filter(related_name属性值__关联表字段)
多对多查询
- 自定义第三张表:与1对多一致
- M2M:正向操作: obj.m2m字段;反向操作: obj.小写表名_set(与外键跨表一致)

浙公网安备 33010602011771号