Django基础(8)_ORM多表操作
ORM多表操作
表和表之间的关系
一对一、多对一、多对多 ,用book表和publish表自己来想想关系,想想里面的操作,加外键约束和不加外键约束的区别,一对一的外键约束是在一对多的约束上加上唯一约束。
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。

创建模型

通过上表的关系,创建我们的模型
1 from django.db import models 2 3 # Create your models here. 4 5 6 # 作者表 7 class Book(models.Model): 8 title = models.CharField(max_length=32) 9 price = models.DecimalField(max_digits=5, decimal_places=2) 10 pub_date = models.DateField() 11 publish = models.ForeignKey(to='Publish', to_field='id', on_delete=models.CASCADE) # 与Publish建立一对多的关系,字段publish如果是外键字段,那么它自动是int类型 12 # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,并且注意一点,你查看book表的时候,你看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,所以只能说这个book类里面有authors这个字段属性 13 authors = models.ManyToManyField(to='Author') 14 15 16 class Publish(models.Model): 17 name = models.CharField(max_length=32) 18 city = models.CharField(max_length=32) 19 email = models.EmailField() 20 21 22 class Author(models.Model): # 比较常用的信息放在这个表里面 23 name = models.CharField(max_length=32) 24 age = models.SmallIntegerField() 25 au_detail = models.OneToOneField(to='AutoDetail', on_delete=models.CASCADE) # 建立一对一关系的时候,这个关系字段放在两个表的任何一个里面都可以 26 27 28 class AutoDetail(models.Model): # 不常用的字段放在这个表里面 29 gender_choices = ( 30 (0, '女'), 31 (1, '男'), 32 ) 33 gender = models.SmallIntegerField(choices=gender_choices) 34 tel = models.CharField(max_length=32) 35 addr = models.CharField(max_length=64) 36 birthday = models.DateField()
关于多对多表的三种创建方式(目前你先作为了解)
方式一:自行创建第三张表
1 class Book(models.Model): 2 title = models.CharField(max_length=32, verbose_name="书名") 3 4 5 class Author(models.Model): 6 name = models.CharField(max_length=32, verbose_name="作者姓名") 7 8 9 # 自己创建第三张表,分别通过外键关联书和作者 10 class Author2Book(models.Model): 11 author = models.ForeignKey(to="Author") 12 book = models.ForeignKey(to="Book") 13 14 class Meta: 15 unique_together = ("author", "book")
方式二:通过ManyToManyField自动创建第三张表
1 class Book(models.Model): 2 title = models.CharField(max_length=32, verbose_name="书名") 3 4 5 # 通过ORM自带的ManyToManyField自动创建第三张表 6 class Author(models.Model): 7 name = models.CharField(max_length=32, verbose_name="作者姓名") 8 books = models.ManyToManyField(to="Book", related_name="authors") #自动生成的第三张表我们是没有办法添加其他字段的
方式三:设置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方法来管理多对多的关系了。
Django Model中字段(Field)的可选项和必选项
| 字段与选项(必选项为黄色标注) |
|
CharField() 字符字段
|
|
TextField() 文本字段
|
|
DateField() and DateTimeField() 日期与时间字段
|
|
EmailField() 邮件字段
|
|
IntegerField(), SlugField(), URLField(),BooleanField()
|
|
FileField(upload_to=None, max_length=100) - 文件字段
|
|
ImageField(upload_to=None, height_field=None, width_field=None, max_length=100,)
|
|
ForeignKey(to, on_delete, **options) - 单对多关系
|
|
ManyToManyField(to, **options) - 多对多关系
|
1 # 创建一对一关系字段时的一些参数 2 to 3 设置要关联的表 4 5 to_field 6 设置要关联的表的字段 7 8 related_name 9 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。 10 related_query_name 11 反向查询操作时,使用的连接前缀,用于替换表名。 12 13 on_delete 14 当删除关联表中的数据时,当前表与其关联的行的行为。 15 16 # 创建多对多字段时的一些参数 17 多对多的参数: 18 to 19 设置要关联的表 20 21 related_name 22 同ForeignKey字段。 23 24 related_query_name 25 同ForeignKey字段。 26 through 27 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。 28 29 但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过 30 through来指定第三张表的表名。 31 32 through_fields 33 设置关联的字段。 34 35 db_table 36 默认创建第三张表时,数据库中表的名称。 37 38 # 创建表的时候一些元信息设置 39 40 元信息 41 ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下: 42 class Author2Book(models.Model): 43 author = models.ForeignKey(to="Author") 44 book = models.ForeignKey(to="Book") 45 class Meta: 46 unique_together = ("author", "book") 47 48 db_table 49 ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model' 50 51 index_together 52 联合索引。 53 54 unique_together 55 联合唯一索引。 56 57 ordering 58 指定默认按什么字段排序。 59 ordering = ['pub_date',] 60 只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据) 61 62 创建表时的一些元信息设置
Django级联删除的选项
Django模型中的on_delete属性具有如下选项:
CASCADE
级联删除 , 也就是被引用的实体删除后, 相关的信息记录都会被删除
PROTECT
阻止删除被引用的实体,除非先把所有引用的记录删除。抛出ProtectedError类
SET_NULL
把字段设置成null,但是必须允许为null的情况下。
SET_DEFAULT
把字段设置成默认值,这时这个字段必须设置了默认值。
SET()
可以传递一个函数给SET()方法作为参数,当字段引用的记录被删除后,此字段的值由给定的方法决定。
DO_NOTHING
什么都不做,保持之前的值。如果你的数据库后端设置了外键约束,那么就抛出一个IntegrityError。
添加表记录
一对一新增
先创建 作者详细信息表记录
- 方法一 传对象的方法
1 def add_author(request): 2 # 方法一 创对象的方式 3 detail = models.AutoDetail.objects.create(gender=0, tel=110, addr='山东省聊城市', birthday='1999-06-17') 4 models.Author.objects.create(name='情玖', age=21, au_detail=detail) 5 return HttpResponse('ok')
- 方法二 传id的方法
1 def add_author(request): 2 # 方法二 传对象的id 3 detail = models.AutoDetail.objects.create(gender=0, tel=110, addr='山东省聊城市', birthday='1999-06-17') 4 models.Author.objects.create(name='情玖', age=21, au_detail_id=detail.id) 5 return HttpResponse('ok')
一对多的新增
我们向书籍表中添加数据, 先要去publish中添加一条记录
- 方法一 传对象的方法
1 def add_book(request): 2 pub_obj = models.Publish.objects.get(id=1) 3 models.Book.objects.create(title='傲世九重天', price=22, pub_date='2012-12-01', publish=pub_obj) 4 5 return HttpResponse('ok')
- 方法二 传id的方法
1 def add_book(request): 2 pub_obj = models.Publish.objects.get(id=1) 3 pub_id = pub_obj.id 4 models.Book.objects.create(title='异世邪君', price=88, pub_date='2020-06-12', publish_id=pub_id) 5 return HttpResponse('ok')
多对多新增
- 方法一 传对象的方法
1 def add_book(request): 2 # 多对多新增 3 book_obj = models.Book.objects.get(title='傲世九重天') 4 author_id1 = models.Author.objects.get(id=1) 5 author_id2 = models.Author.objects.get(id=2) 6 book_obj.authors.add(author_id1, author_id2) 7 return HttpResponse('ok')
- 方法二 传id的方法
1 def add_book(request): 2 book_obj = models.Book.objects.get(title='傲世九重天') 3 author_id1 = models.Author.objects.get(id=1) 4 author_id2 = models.Author.objects.get(id=2) 5 book_obj.authors.add(author_id1.id, author_id2.id) 6 return HttpResponse('ok')
1 方式一: 多对多一般在前端页面上使用的时候是多选下拉框的样子来给用户选择多个数据,这里可以让用户选择多个书籍,多个作者 2 # 当前生成的书籍对象 3 book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1) 4 # 为书籍绑定的做作者对象 5 yuan=Author.objects.filter(name="yuan").first() # 在Author表中主键为2的纪录,注意取的是author的model对象 6 egon=Author.objects.filter(name="alex").first() # 在Author表中主键为1的纪录 7 #有人可能会说,我们可以直接给第三张表添加数据啊,这个自动生成的第三张表你能通过models获取到吗,是获取不到的,用不了的,当然如果你知道了这个表的名字,那么你通过原生sql语句可以进行书的添加,所以要通过orm间接的给第三张表添加数据,如果是你手动添加的第三张表你是可以直接给第三张表添加数据 8 # 绑定多对多关系,即向关系表book_authors中添加纪录,给书添加两个作者,下面的语法就是告诉orm给第三张表添加两条数据 9 book_obj.authors.add(yuan,egon) # 将某些特定的 model 对象添加到被关联对象集合中。 ======= book_obj.authors.add(*[]) 10 #book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称。 11 #其实orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去 12 13 方式二: 14 book_obj.authors.add(1,2) 15 book_obj.authors.add(*[1,2]) # #这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值
多对多关系其他常用API:
1 book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 ====== book_obj.authors.remove(*[1,2]),将多对多的关系数据删除 2 book_obj.authors.clear() #清空被关联对象集合 3 book_obj.authors.set() #先清空再设置
删除示例:
1 book_obj = models.Book.objects.filter(nid=4)[0] 2 # book_obj.authors.remove(2) #将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除 3 # book_obj.authors.clear() 4 # book_obj.authors.set('2') #先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录 3---2,比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以我们就可以先清空,然后再重新绑定关系数据,注意这里写的是字符串,数字类型不可以 5 book_obj.authors.set(['1',]) #这么写也可以,但是注意列表中的元素是字符串,列表前面没有*,之前我测试有*,感觉是版本的问题,没事,能够用哪个用哪个
更新和删除
1 更新: 2 book_obj = models.Book.objects.get(id=1) #获取一个书籍对象 3 data = {'title':'xxx','price':100} #这个书籍对象更新后的数据 4 models.Book.objects.filter(id=n).update(**data) #将新数据更新到原来的记录中 5 book_obj.authors.set(author_list) #将数据和作者的多对多关系加上 6 7 删除: 8 models.Book.objects.filter(id=1).delete()
基于对象的跨表查询
数据展示






一对多查询 (Publish 和 Book)

正向查询(按字段) 关联属性字段所在的表查询被关联的记录就是正向查询, 反之就是反向查询
正向查询: 查询主键为1的书籍的出版社所在的城市
1 book_obj = models.Book.objects.get(pk=1) 2 print(book_obj.publish.city) # book_obj.publish是主键为1的书籍对象所关联的出版社对象
反向查询:查询风华出版社出版的书
1 pub_obj = models.Publish.objects.get(name='风华出版社') 2 title = pub_obj.book_set.all().values('title')
一对一查询 (Author和 AuthorDetail)

正向查询 查询唐家三的手机号
1 author_obj = models.Author.objects.filter(name='唐家三少').first() 2 print(author_obj.au_detail.tel) # au_detailW为Author表关联AuthorDetail表的字段
反向查询 查询手机号为110的人的姓名
1 authordetail_obj = models.AuthorDetail.objects.get(tel=110) 2 print(authordetail_obj.author.name)
多对多查询 (Author和 Book)

正向查询 : 查询id值为2的书籍的所有作者
1 # 方法一 2 book_obj = models.Book.objects.get(id=2) 3 authors = book_obj.authors.all().values_list('name') 4 print(authors) # <QuerySet [('风凌天下',), ('唐家三少',)]> 5 6 # 方法二 7 book_obj = models.Book.objects.filter(id=2).first() 8 authors = book_obj.authors.all() 9 for author_obj in authors: 10 print(author_obj.name, type(author_obj.name)) # 获取到的是字符串
反向查询: 查询唐家三少出过的所有书籍的名称
1 author_obj = models.Author.objects.get(name='唐家三少') 2 book_list = author_obj.book_set.all() 3 for book_obj in book_list: 4 print(book_obj.title) 5 return HttpResponse('ok')
注意:
你可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改:
1 publish = ForeignKey(Book, related_name='bookList')
那么接下来就会如我们看到这般:
# 查询 人民出版社出版过的所有书籍 publish=Publish.objects.get(name="人民出版社") book_list=publish.bookList.all() # 与人民出版社关联的所有书籍对象集合
基于双下划线的跨表查询 基于join实现
Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model 为止。
正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表,一对一、一对多、多对多都是一个写法,注意,我们写orm查询的时候,哪个表在前哪个表在后都没问题,因为走的是join连表操作。
""" 正向查询按照关联字段, 反向查询按照表名小写 """ # 一对一查询 # 正向查询 ret = models.Author.objects.filter(id=1).values('au_detail__tel') # 反向查询 ret = models.AutoDetail.objects.filter(id=1).values('author_name') # 一对多查询 出版社和书籍 # 正向查询 ret = models.Book.objects.filter(id=1).values('publish__book') # 反向查询 ret = models.Publish.objects.filter(id=1).values('book__title') # 多对多查询 # 正向查询 ret = models.Book.objects.filter(title='异世邪君').values('authors__name') # 反向查询 查询异世邪君这本书的所有作者 ret = models.Author.objects.filter(book__title='异世邪君').values('name')
聚合查询 分组查询
聚合
aggregate(*args, **kwargs)
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
1 from django.db.models import Avg, Max, Min, Count 2 res = models.Book.objects.all().aggregate(Avg('price')) 3 res = models.Book.objects.all().aggregate(Max('price')) 4 res = models.Book.objects.all().aggregate(Count('price')) 5 # 起别名 6 res = models.Book.objects.all().aggregate(a=Avg('price'), m=Max('price')) 7 print(res['a'], res['m']) 8 print(res)
分组
annotate 等同于SQL中的group by
默认就是按照你所查询这个表的id
annotate的返回值是一个queryset, 若你不想循展示的话,可以在后面加values或者values_list,那么返回的是一个queryset类型的字典或者元组
1 # 分组查询 2 """ 3 MySql分组查询的特点: 4 分组之后默认只能获取到分组的依据, 组内其他字段无法自动获取了(严格模式) 5 """ 6 from django.db.models import Min, Max, Count, Sum, Avg 7 8 # 1. 统计每一本书的作者个数 9 # res = models.Book.objects.annotate() # models后面写什么, 就是按照什么分组 10 res = models.Book.objects.annotate(authors_num=Count('authors')).values('title', 'authors_num') 11 # res = models.Book.objects.annotate(authors_num=Count('authors__id')).values('title', 'authors_num') # 等价于上面,orm会自动帮我们找到id 12 """ 13 authors_num 就是我们自定义的字段, 用来存储统计后的每本书的作者的个数 14 """ 15 print(res) 16 17 # 2. 统计每个出版社出版的最便宜的书的价格 18 res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price') 19 """ 20 因为是反向查询, 又因为我们找的是书的价格, 所以Min里面写上book__price 21 """ 22 print(res) 23 return HttpResponse('ok')
按照指定的字段进行查询 values(...)

若出现了分组查询报错的情况, 你需要修改数据的严格模式 ONLY_FULL_GROUP_BY
1 # 练习 2 # 1. 统计每个出版社的最便宜的书的价格 3 res = models.Publish.objects.values('name').annotate(book_price=Min('book__price')).order_by('book_price')[0] 4 print(res) # {'name': '东方出版社', 'book_price': Decimal('22.00')} 5 # Publish表示表,values中的子段表示按照哪个字段进行分组,annotate里面显示分组统计的什么 6 # 2. 统计每一本以“傲”开头的书籍的作者个数 7 res = models.Book.objects.filter(title__startswith='傲').annotate(authors_num=Count('authors')).values('title', 'authors_num') 8 print(res) # <QuerySet [{'title': '傲世九重天', 'authors_num': 1}]> 9 return HttpResponse('ok')
注意:
1 # 如果values里面有多个字段的情况: 2 models.Author.objects.values('name', 'age').annotate(a=Count('id'), b=Max('age')) 3 # 是按照values里面的两个字段进行分组, 两个字段同时相同才算一组
F查询与Q查询
F查询
F()的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
Django 支持 F() 对象之间以及 F()对象和常数之间的加减乘除和取模的操作。
1 # F查询 查询工资小于两倍年龄的人 2 from django.db.models import F 3 models.Emp.objects.filter(salary__lt=F('age') * 2) 4 5 # 修改操作也可以使用F函数 对每一本书的价格提高100 6 models.Book.objects.update(price=F('price') + 100)
Q查询
Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
1 # 1. 查询作者id大于2或者作者年龄大于等于20岁的女作者的姓名以及出版的书的最高价格 2 from django.db.models import Q 3 models.Author.objects.filter(Q(age__gte=20) | Q(id__gt=2)).annotate(max_price=Max('book__price')).values('name', 'max_price') 4 # 2.查询价格大于50或者名称以'异'开头的书籍 5 models.Book.objects.filter(Q(price__gt=50) | Q(title__startswith='异')).values('title') 6 # 3. 查询价格大于50或者不是2008年八月份的书籍 7 models.Book.objects.filter(Q(price__gt=50) | ~Q(Q(pub_date__year='2008') & Q(pub_date__month='08'))).values('title')
执行原生SQL
有时候我们通过pycharm的orm并不能达到我们想要的结果, 那么我们就要通过原生SQL语句去操作数据库了
方法一:
执行原生查询
raw()管理器方法用于原始的SQL查询,并返回模型的实例:
注意:raw()语法查询必须包含主键。
这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例。
1 res = models.Book.objects.raw('select * from app02_book') 2 for i in res: 3 print(i.title) 4 # 傲世九重天 5 # 异世邪君
raw()方法自动将查询字段映射到模型字段。还可以通过translations参数指定一个把查询的字段名和ORM对象实例的字段名互相对应的字典
1 d = {'tname': 'haha'} # 也就是说把一个字典的值和字段名字对应起来,相当于把 tname换成了haha 2 ret = models.Student.objects.raw('select * from app02_teacher where id > 2', translations=d) 3 for i in ret: 4 print(i.id, i.tname, i.haha) # 替换后,原来的tname一样可以使用
原生sql还可以使用参数加上 判断条件
d = {'title': 'name'}
for i in models.Book.objects.raw('select * from app01_book where id = %s', translations=d, params=[1, ]):
print(i.name, i.price)
方法二:
connection模块
有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。
我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。
我们可以使用我们的test.py文件使用models (django外部脚步使用models)
test.py
当然你也可以自己创建一个py文件
1 import os 2 3 if __name__ == "__main__": 4 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo2.settings") 5 import django 6 django.setup() 7 8 from django.db import connection, connections 9 cursor = connection.cursor() # 10 cursor.execute("""select * from app02_book where id = %s""", [1]) 11 ret = cursor.fetchone() # 获取一条数据 若想获取多条使用fetchall 12 print(ret) # (1, '傲世九重天', Decimal('22.00'), datetime.date(2012, 12, 1), 1)
本文来自博客园,作者:长情不羁的五年,转载请注明原文链接:https://www.cnblogs.com/grlend/articles/14043141.html

浙公网安备 33010602011771号