多表操作
1,创建模型
1.1 实例:我们来假定下面这些概念,字段和关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 作者模型 : 姓名 年龄 作者详细模型 : 生日 手机号码 家庭住址等等 出版商模型 : 出版商名称 所在城市 email 书籍模型 : 书名 出版日期 (作者模型 和 作者详细模型 是一对一关系 one-to -one) (一本书可能会有多个作者, 一个作者也可以写多本书) (作者 和 书籍 是多对多的关系 many -to -many) (一本书只应该由一个出版商出版, 出版商和书籍是一对多关联关系 one - to - many) |
1.2 代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | from django.db import models # Create your models here. class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() # 与AuthorDetail建立一对一的关系 authorDetail = models.OneToOneField(to= 'AuthorDetail' , on_delete=models.CASCADE) class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday = models.DateField() telephone = models.BigIntegerField() addr = models.CharField(max_length=64) class Publish(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) city = models.CharField(max_length=32) email = models.EmailField() class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) publishDate = models.DateField() # max_digits=None, 总长度 decimal_places=None,小数位长度 price = models.DecimalField(max_digits=5, decimal_places=2) # 与publish建立一对多的关系,外键字段建立在多的一方 publish = models.ForeignKey(to= 'Publish' , to_field= 'nid' ,on_delete=models.CASCADE) # 与Author 建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors = models.ManyToManyField(to= 'Author' ,) |
1.3 结果如下
1.4 注意事项
- 表的名称是Myapp_modelName,是根据模型中的元数据自动生成的额,也可以复写为别的名字。
- ID 字段是自动添加的
- 对于外键字段,Django会在字段名上添加“_id” 来创建数据库中的列名
- 这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django会根据settings中指定的数据库类型来使用相应的SQL语句。
- 定义好模型之后,你需要告诉Django_ 使用 _ 这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。
- 外键字段ForeignKey有一个null = True的设置(它允许外键接受空值NULL),你可以赋给他空值None。
2,添加表记录
操作前先简单的录入一些数据
2.1 Publish表
2.2 AuthorDetail表
2.3 Author表
2.4 一对多添加——book表
1 2 3 4 5 6 | def insert_data(request): publish_obj = Publish.objects. get (nid=1) book_obj = Book.objects.create(title= '秦腔' , publishDate= '2016-8-28' , price=1005, publish=publish_obj) book_obj2 = Book.objects.create(title= "追风筝的人" , publishDate= '2017-4-9' , price=124, publish_id=2) return HttpResponse( "insert data ok" ) |
2.5 多对多添加——book表,book_authors表
1 2 3 4 5 6 7 8 9 10 11 12 13 | def insert_data(request): # 生成当前的书籍对象 book_obj = Book.objects.create(title= '秦腔' , publishDate= '2016-8-28' , price=1005, publish_id=1) # 为书籍绑定的做作者对象 author_james = Author.objects.filter(name= 'james' ).first() # 在Author表中主键为1的记录 author_durant = Author.objects.filter(name= 'durant' ).first() # 在Author表中主键为2的记录 # 绑定多对多关系,即向关系表book_authors中添加数据 book_obj.authors.add(author_james, author_durant) # 将某些特定的model对象添加到被关联对象集合中 # book_obj.authors.add(*[]) return HttpResponse( "insert data ok" ) |
2.6 多对多关系常用API
1 2 3 4 5 6 7 8 9 | # 将某个特定的对象从被关联对象集合中去除。 book_obj.authors.remove() book_obj.authors.remove(*[]) #清空被关联对象集合 book_obj.authors.clear() #先清空再设置 book_obj.authors. set () |
create(**kwargs)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 创建一个新的对象,保存对象,并将它添加到关联对象集之中。返回新创建的对象: >>> b = Blog.objects. get (id=1) >>> e = b.entry_set.create( ... headline= 'Hello' , ... body_text= 'Hi' , ... pub_date=datetime.date(2005, 1, 1) ... ) # No need to call e.save() at this point -- it's already been saved. 这完全等价于(不过更加简洁于): >>> b = Blog.objects. get (id=1) >>> e = Entry( ... blog=b, ... headline= 'Hello' , ... body_text= 'Hi' , ... pub_date=datetime.date(2005, 1, 1) ... ) >>> e.save(force_insert=True) 要注意我们并不需要指定模型中用于定义关系的关键词参数。在上面的例子中,我们并没有 传入blog参数给create()。Django会明白新的 Entry对象blog 应该添加到b中。 |
remove(obj1 [, obj2,...])
1 2 3 4 5 6 | 从关联对象集中移除执行的模型对象: >>> b = Blog.objects. get (id=1) >>> e = Entry.objects. get (id=234) >>> b.entry_set.remove(e) # Disassociates Entry e from Blog b. 对于ForeignKey对象,这个方法仅在 null =True时存在。 |
clear()
1 2 3 4 5 6 7 | 从关联对象集中移除一切对象。 >>> b = Blog.objects. get (id=1) >>> b.entry_set.clear() 注意这样不会删除对象 —— 只会删除他们之间的关联。 就像 remove() 方法一样,clear()只能在 null =True的ForeignKey上被调用。 |
set()
先清空,再设置,编辑书籍时即可用到
注意:对于所有类型的关联字段,add() ,create(),remove(),clear(),set() 都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。
直接赋值:通过赋值一个新的可跌打的对象,关联对象集可以被整体替换掉
1 2 | >>> new_list = [obj1, obj2, obj3] >>> e.related_set = new_list |
如果外键关系满足 null =True,关联管理器会在添加new_list 中的内容之前,首先调用clear()方法来解除关联集中一切已存在对象的关联。否则 , new_list中的对象会在已存在的关联的基础上被添加。
2.7 如何处理外键关系的字段(如一对多的publisher和多对多的authors)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | #一对多(ForeignKey): # 方式一: 由于绑定一对多的字段,比如publish,存到数据库中的字段名叫publish_id # 所以我们可以直接给这个 # 字段设定对应值: Book.objects.create(title= 'php' , publisher_id=2, #这里的2是指为该book对象绑定了Publisher表中id=2的行对象 publication_date= '2017-7-7' , price=99) #方式二: # <1> 先获取要绑定的Publisher对象: pub_obj=Publisher(name= '河大出版社' ,address= '保定' ,city= '保定' , state_province= '河北' ,country= 'China' ,website= 'http://www.hbu.com' ) OR pub_obj=Publisher.objects. get (id=1) # <2>将 publisher_id=2 改为 publisher=pub_obj #多对多(ManyToManyField()): author1=Author.objects. get (id=1) author2=Author.objects.filter(name= 'alvin' )[0] book=Book.objects. get (id=1) book.authors.add(author1,author2) #等同于: book.authors.add(*[author1,author2]) book.authors.remove(*[author1,author2]) #------------------- book=models.Book.objects.filter(id__gt=1) authors=models.Author.objects.filter(id=1)[0] authors.book_set.add(*book) authors.book_set.remove(*book) #------------------- book.authors.add(1) book.authors.remove(1) authors.book_set.add(1) authors.book_set.remove(1) #注意: 如果第三张表是通过models.ManyToManyField()自动创建的,那么绑定关系只有上面一种方式 # 如果第三张表是自己创建的: class Book2Author(models.Model): author=models.ForeignKey( "Author" ) Book= models.ForeignKey( "Book" ) # 那么就还有一种方式: author_obj=models.Author.objects.filter(id=2)[0] book_obj =models.Book.objects.filter(id=3)[0] s=models.Book2Author.objects.create(author_id=1,Book_id=2) s.save() s=models.Book2Author(author=author_obj,Book_id=1) s.save() |
2.8 对象查询,单表条件查询,多表条件关联查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #--------------------对象形式的查找-------------------------- # 正向查找 ret1=models.Book.objects.first() print(ret1.title) print(ret1.price) print(ret1.publisher) print(ret1.publisher.name) #因为一对多的关系所以ret1.publisher是一个对象, 而不是一个queryset集合 # 反向查找 ret2=models.Publish.objects.last() print(ret2.name) print(ret2.city) #如何拿到与它绑定的Book对象呢? print(ret2.book_set.all()) #ret2.book_set是一个queryset集合 #---------------了不起的双下划线(__)之单表条件查询---------------- # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # # models.Tb1.objects.filter(name__contains="ven") # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # # startswith,istartswith, endswith, iendswith, #----------------了不起的双下划线(__)之多表条件关联查询--------------- # 正向查找(条件) # ret3=models.Book.objects.filter(title='Python').values('id') # print(ret3)#[{'id': 1}] #正向查找(条件)之一对多 ret4=models.Book.objects.filter(title= 'Python' ).values( 'publisher__city' ) print(ret4) #[{ 'publisher__city' : '北京' }] #正向查找(条件)之多对多 ret5=models.Book.objects.filter(title= 'Python' ).values( 'author__name' ) print(ret5) ret6=models.Book.objects.filter(author__name= "alex" ).values( 'title' ) print(ret6) #注意 #正向查找的publisher__city或者author__name中的publisher,author是book表中绑定的字段 #一对多和多对多在这里用法没区别 # 反向查找(条件) #反向查找之一对多: ret8=models.Publisher.objects.filter(book__title= 'Python' ).values( 'name' ) print(ret8)#[{ 'name' : '人大出版社' }] 注意,book__title中的book就是Publisher的关联表名 ret9=models.Publisher.objects.filter(book__title= 'Python' ).values( 'book__authors' ) print(ret9)#[{ 'book__authors' : 1}, { 'book__authors' : 2}] #反向查找之多对多: ret10=models.Author.objects.filter(book__title= 'Python' ).values( 'name' ) print(ret10)#[{ 'name' : 'alex' }, { 'name' : 'alvin' }] #注意 #正向查找的book__title中的book是表名Book #一对多和多对多在这里用法没区别 |
注意:条件查询即与对象查询对应,是指在filter,values等方法中通过__来明确查询条件
3,关联管理器——Class RelatedManager
“关联管理器” 是在一对多或者多对多的关联上下文中使用的管理器。他存在于下面两种情况:ForeignKey关系的“另一边”。像这样:
1 2 3 4 5 6 7 8 9 | # models.py from django.db import models class Reporter(models.Model): # ... pass class Article(models.Model): reporter = models.ForeignKey(Reporter) |
在上面的例子中,管理器reporter.article_set拥有下面的方法。
MangToManyField关系的两边:
1 2 3 4 5 6 | class Manager1(models.Model): # ... pass class Manager2(models.Model): manager1 = models.ManyToManyField(Manager1) |
3.1 查询表记录(filter,value)
查询操作是Django的ORM框架中最重要的内容之一,下面是我们常用到与查询相关的API。
注意:一定要区分出object 和QuerySet 的区别
附加SQL查询extra()
1 2 3 4 5 6 7 8 9 10 11 12 13 | #扩展查询,有时候DJANGO的查询API不能方便的设置查询条件,提供了另外的扩展查询方法extra: #extra(select=None, where=None, params=None, tables=None,order_by=None, select_params=None (1) Entry.objects.extra( select ={ 'is_recent' : "pub_date > '2006-01-01'" }) (2) Blog.objects.extra( select =SortedDict([( 'a' , '%s' ), ( 'b' , '%s' )]), select_params=( 'one' , 'two' )) (3) q = Entry.objects.extra( select ={ 'is_recent' : "pub_date > '2006-01-01'" }) q = q.extra(order_by = [ '-is_recent' ]) (4) Entry.objects.extra( where =[ 'headline=%s' ], params =[ 'Lennon' ]) |
4,基于对象的跨表查询
4.1 一对一查询(Author和AuthorDetail)
正向查询(按字段:authorDetail)
1 2 3 4 5 6 7 | def select_data(request): # 查询作者名为james的电话 james = Author.objects.filter(name= 'james' ).first() print(james) print(james.authorDetail.telephone) return HttpResponse( "select data ok" ) |
反向查询(按表名:author)
1 2 3 4 5 6 7 | def select_data(request): # 查询住址在chengdu的作者姓名 authorDetail_list = AuthorDetail.objects.filter(addr= 'chengdu' ) for obj in authorDetail_list: print(obj.author.name) return HttpResponse( "select data ok" ) |
4.2 一对多查询(Publish和Book)
正向查询(按字段:publish)
1 2 3 4 5 6 7 | def select_data(request): # 查询主键为1的书籍的出版社所在的城市 book_obj = Book.objects.filter(nid=1).first() # book_obj.publish 是主键为1 的书籍对象关联的出版社对象 print(book_obj.publish.city) return HttpResponse( "select data ok" ) |
反向查询(按表名:book_set)
1 2 3 4 5 6 7 8 9 10 | def select_data(request): publish = Publish.objects. get (name= "机械出版社" ) # 与机械出版社 关联的所有书籍对象集合 res1 = publish.book_set.all() print(res1) book_list = publish.book_set.all() for book_obj in book_list: print(book_obj.title) return HttpResponse( "select data ok" ) |
4.3 多对多查询(Author与Book)
正向查询(按字段:author)
1 2 3 4 5 6 7 8 9 10 | def select_data(request): # 查找平凡的世界所有作者的名字以及手机号码 book_obj = Book.objects.filter(title= "秦腔" ).first() print(book_obj) authors = book_obj.authors.all() print(authors) for author_obj in authors: print(author_obj.name, author_obj.authorDetail.telephone) return HttpResponse( "select data ok" ) |
反向查询(按表名:book_set)
1 2 3 4 5 6 7 8 9 10 11 | def select_data(request): # 查找james出过的所有书籍的名字 author_obj = Author.objects. get (name= 'james' ) print(author_obj) # 与james作者相关的所有书籍 book_list = author_obj.book_set.all() print(book_list) for book_obj in book_list: print(book_obj.title) return HttpResponse( "select data ok" ) |
4.4 注意
你可以通过在ForeignKey()和ManyToManyField的定义中设置related_name的值来覆写FOO_set的名称。例如,如果Article model中做一下更改:
1 | publish = ForeignKey(Book, related_name= 'bookList' ) |
那么接下来,我们就会看到这样:
1 2 3 4 5 6 7 8 | def select_data(request): # 查找人们出版社出版过的所有书籍 publish = Publish.objects. get (name= '机械出版社' ) print(publish) # 与机械出版社关联的所有书籍对象集合 book_list = publish.bookList.all() print(book_list) return HttpResponse( "select data ok" ) |
5,基于双下划线的跨表查询
Django还提供了一种直观而高效的方法在查询(lookups)中表示关联关系,它能自动确认SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model为止。
关键点:正向查询按字段,反向查询按表名
5.1 一对多查询
1 2 3 4 5 6 7 8 9 10 | def select_data(request): # 查询机械出版社 出版过的所有书籍的名字与价钱(一对多) # 正向查询 按字段publish queryResult1 = Book.objects.filter(publish_id=1).values_list( 'publish__book' , 'publish__city' ) print(queryResult1) # 反向查询 按表名:book queryResult2 =Publish.objects.filter(nid=2).values_list( "book__title" , 'book__price' ) print(queryResult2) return HttpResponse( "select data ok" ) |
查询结果:
1 2 3 4 5 6 | <QuerySet [(1, 'xian' ), (3, 'xian' ), (4, 'xian' ), (5, 'xian' ), (6, 'xian' ), (1, 'xian' ), (3, 'xian' ), (4, 'xian' ), (5, 'xian' ), (6, 'xian' ), (1, 'xian' ), (3, 'xian' ), (4, 'xian' ), (5, 'xian' ), (6, 'xian' ), (1, 'xian' ), (3, 'xian' ), (4, 'xian' ), (5, 'xian' ), (6, 'xian' ), ' ...(remaining elements truncated)...']> <QuerySet [( '追风筝的人' , Decimal( '124.00' ))]> |
5.2 多对多查询
1 2 3 4 5 6 7 8 9 | def select_data(request): # 查询james出版过的所有书籍的名字 (多对多) # 正向查询,按照字段:authors queryResult1 = Book.objects.filter(authors=1).values_list( "authors__name" , 'authors__age' , 'publish__name' ) print(queryResult1) # 反向查询 按照表名book queryResult2 = Author.objects.filter(name= 'durant' ).values_list( "book__authors__name" , 'age' , 'book__publish__name' ) print(queryResult2) return HttpResponse( "select data ok" ) |
结果展示:
1 2 3 4 5 | <QuerySet [( 'james' , 34, '机械出版社' ), ( 'james' , 34, '机械出版社' )]> <QuerySet [( 'james' , 30, '机械出版社' ), ( 'james' , 30, '机械出版社' ), ( 'durant' , 30, '机械出版社' ), ( 'durant' , 30, '机械出版社' )]> |
5.3 一对一查询
1 2 3 4 5 6 7 | # 查询james的手机号 # 正向查询 ret=Author.objects.filter(name= "james" ).values( "authordetail__telephone" ) # 反向查询 ret=AuthorDetail.objects.filter(author__name= "james" ).values( "telephone" ) |
5.4 混合使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def select_data(request): # 查询机械出版社 出版过的所有书籍的名字与作者名字 # 正向查询 按照字段 authors queryResult1 = Book.objects.filter(publish_id=1).values_list( 'title' , 'authors__name' ) print(queryResult1) # 反向查询 按表名:book queryResult2 =Publish.objects.filter(nid=2).values_list( "book__title" , 'book__price' ) print(queryResult2) # 查询手机是以123开头的作者 出版过的所有书籍名称以及出版社名称 queryResult3 = Book.objects.filter(authors__authorDetail__telephone__regex= '123' ).values_list( 'title' , 'publish__name' ) print(queryResult3) return HttpResponse( "select data ok" ) |
结果展示:
1 2 3 4 5 6 | <QuerySet [( '秦腔' , 'james' ), ( '秦腔' , 'james' ), ( '秦腔' , 'durant' ), ( '秦腔' , 'durant' ), ( '平凡的世界' , None), ( '平凡的世界2' , None), ( '秦腔' , None)]> <QuerySet [( '追风筝的人' , Decimal( '124.00' ))]> <QuerySet [( '秦腔' , '机械出版社' ), ( '秦腔' , '机械出版社' )]> |
注意:反向查询时,如果定义了related_name,则用related_name替换表名,例如:
1 | publish = ForeignKey(Blog, related_name= 'bookList' ) |
5.5 练习:查询人民出版社出版过的所有书籍的名字与价格(一对多)
1 2 3 4 5 6 7 8 9 10 11 | def select_data(request): # 查询机械出版社 出版过的所有书籍的名字与价格 # 正向查询 按照字段 authors queryResult1 = Book.objects.filter(publish_id=1).values_list( 'title' , 'price' ) print(queryResult1) # 反向查询 不再按照表名book,而是related_name:bookList queryResult2 = Publish.objects.filter(name= '机械出版社' ).values_list( "book__title" , "book__price" ) print(queryResult2) return HttpResponse( "select data ok" ) |
结果展示:
1 2 3 4 5 | <QuerySet [( '平凡的世界' , Decimal( '125.00' )), ( '平凡的世界2' , Decimal( '105.00' )), ( '秦腔' ,Decimal( '1005.00' )), ( '秦腔' , Decimal( '1005.00' )), ( '秦腔' , Decimal( '1005.00' ))]> <QuerySet [( '平凡的世界' , Decimal( '125.00' )), ( '平凡的世界2' , Decimal( '105.00' )), ( '秦腔' ,Decimal( '1005.00' )), ( '秦腔' , Decimal( '1005.00' )), ( '秦腔' , Decimal( '1005.00' ))]> |
5.6 进阶练习(连续跨表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # 练习: 查询人民出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 queryResult=Book.objects .filter(publish__name= "人民出版社" ) .values_list( "title" , "authors__name" ) # 反向查询 queryResult=Publish.objects .filter(name= "人民出版社" ) .values_list( "book__title" , "book__authors__age" , "book__authors__name" ) # 练习: 手机号以151开头的作者出版过的所有书籍名称以及出版社名称 # 方式1: queryResult=Book.objects .filter(authors__authorDetail__telephone__regex= "151" ) .values_list( "title" , "publish__name" ) # 方式2: ret=Author.objects .filter(authordetail__telephone__startswith= "151" ) .values( "book__title" , "book__publish__name" ) |
5.7 related_name
反向查询时,如果定义了related_name,则使用 related_name替换表名,例如:
1 | publish = ForeignKey(Blog, related_name= 'bookList' ) |
练习:
1 2 3 4 5 6 7 8 | # 练习: 查询人民出版社出版过的所有书籍的名字与价格(一对多) # 反向查询 不再按表名:book,而是related_name:bookList queryResult=Publish.objects .filter(name= "人民出版社" ) .values_list( "bookList__title" , "bookList__price" ) |
6 ,聚合查询和分组查询
6.1 aggregate(*args , **kwargs):
通过对QuerySet进行计算,返回一个聚合值的字典,aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
总结:跨表分组查询的本质就是将关联表join成一张表,再按照单表的思路进行分组查询。
aggregate()字句的参数描述了我们想要计算的聚合值。aggregate() 是QuerySet的一个终止子句,意思是它返回一个包含一些键值对的字典,键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from django.db.models import Avg, Min, Sum, Max def select_data(request): # 比如你想要计算所有在售的书的平均价钱 # Django 提供了一种方式描述所有图书的集合 # 在下面的例子中,aggregate()计算的Book模型中price字段的平均值 res1 = Book.objects.all().aggregate(Avg( 'price' )) print(res1) #{ 'price__avg' : 561.5} # 如果想要为聚合值指定一个名称,可以向聚合子句提供它。 res2 = Book.objects.aggregate(avergae_price=Avg( 'price' )) print(res2) # { 'avergae_price' : 561.5} # 如果你想知道所有图书价格的最大值和最小值,可以这样查询 res3 = Book.objects.aggregate(Avg( 'price' ), Max( 'price' ), Min( 'price' )) print(res3) # {'price__avg': 561.5, 'price__max': Decimal('1005.00'), 'price__min': Decimal('105.00')} return HttpResponse( "select data OK" ) |
6.2 annotate( *args , **kwargs)
可以通过计算查询结果中每一个对象所关联的对象集合,从而得到总计值(也可以是平均值或者总和),即为查询集的每一项生成聚合。
单表分组查询的ORM 语法如下:单表模型.objects.values('group by 的字段‘).annotate(聚合函数('统计字段'))
查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors_name
查询各个出版社最便宜的书价是多少
1 2 3 4 5 6 7 8 9 10 11 12 | from django.db.models import Avg, Min, Sum, Max def select_data(request): # 查询各个作者出的书的总价格,这里涉及分组,分组条件是authors_name res1 = Book.objects.values( "authors__name" ).annotate(Sum( 'price' )) print(res1) # 查询各个出版社最便宜的书架是多少 res2 = Book.objects.values( "publish__name" ).annotate(Min( "price" )) print(res2) return HttpResponse( "select data OK" ) |
结果如下:
1 2 3 4 5 6 7 | <QuerySet [{ 'authors__name' : 'james' , 'price__sum' : Decimal( '2010.00' )}, { 'authors__name' : 'durant' , 'price__sum' : Decimal( '2010.00' )}, { 'authors__name' : None, 'price__sum' : Decimal( '1359.00' )}]> <QuerySet [{ 'publish__name' : '机械出版社' , 'price__min' : Decimal( '105.00' )}, { 'publish__name' : '清华出版社' , 'price__min' :Decimal( '124.00' )}]> |
6.3 查询练习
注意:(框里面的相等)
1,练习:统计每一个出版社里最便宜的书籍
1 2 3 | publishList = Publish.objetcs.annotate(minPrice=Min('book__price")) for publish_obj in publishList: print(publish_obj.name, publish_obj.MinPrice) |
annotate的返回值是QuerySet,如果不想遍历对象,可以用上 valuelist
1 2 3 4 | queryResult= Publish.objects .annotate(MinPrice=Min( "book__price" )) .values_list( "name" , "MinPrice" ) print(queryResult) |
对应的SQL代码如下:
1 2 3 4 5 6 | '' ' SELECT "app01_publish" . "name" , MIN( "app01_book" . "price" ) AS "MinPrice" FROM "app01_publish" LEFT JOIN "app01_book" ON ( "app01_publish" . "nid" = "app01_book" . "publish_id" ) GROUP BY "app01_publish" . "nid" , "app01_publish" . "name" , "app01_publish" . "city" , "app01_publish" . "email" '' ' |
2,练习:统计每一本书的作者个数
1 | ret = Book.objects.annotate(authorsNum=Count( 'authors__name' )) |
3,统计每一本以py开头的书籍的作者个数
1 2 | queryResult=Book.objects.filter(title__startswith= "Py" ) .annotate(num_authors=Count( 'authors' )) |
4,统计不止一个作者的图书:
1 2 | queryResult=Book.objects.annotate(num_authors=Count( 'authors' )) .filter(num_authors__gt=1) |
5,根据一本图书作者数量的多少对查询集QuerySet进行排序
1 | Book.objects.annotate(num_authors=Count( 'authors' )).order_by( 'num_authors' ) |
6,查询各个作者出的书的总价格
1 2 3 4 | # 按author表的所有字段 group by queryResult=Author.objects.annotate(SumPrice=Sum( "book__price" )) .values_list( "name" , "SumPrice" ) print(queryResult) |
7,F查询和Q查询
仅仅靠着单一的关键字参数查询已经很难满足查询要求。此时Django为我们提供了F和Q查询:
7.1 F查询
F()的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值。
1 2 3 4 | # 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F( 'keepNum' )) |
Django支持F() 对象之间以及F()对象和常数之间的加减乘除和取模的操作
1 2 3 | # 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commnetNum__lt=F( 'keepNum' )*2) |
修改操作也可以使用F函数,比如把每一本书的价格提高10元
1 2 3 4 5 6 7 | def update_use_F(request): # F 使用查询条件的值,专门取对象中某列值的操作 from django.db.models import F # models.Tb1.objects.update(num=F('num') + 1) res1 = Book.objects.update(price = F( 'price' ) + 10) print(res1) return HttpResponse( "update use F OK" ) |
操作之前:
操作之后:
7.2 Q() 查询
filter()等方法中的关键字参数查询都是一起“AND”的,如果你需要执行更复杂的查询(例如OR语句),你可以使用Q 对象。
1 2 | from django.db.models import Q Q(title__startswith= 'Py' ) |
Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
1 | bookList=Book.objects.filter(Q(authors__name= "james" )|Q(authors__name= "durant" )) |
等同于SQL WHERE字句:
1 | WHERE name = "james" OR name = "durant" |
你可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
1 2 | bookList=Book.objects.filter(Q(authors__name= "james" ) & ~Q(publishDate__year=2017)).values_list( "title" ) |
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:
1 2 3 | bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017), title__icontains= "python" ) |
7.3 综合运用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | # F 使用查询条件的值,专门取对象中某列值的操作 # from django.db.models import F # models.Tb1.objects.update(num=F('num')+1) # Q 构建搜索条件 from django.db.models import Q #1 Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询 q1=models.Book.objects.filter(Q(title__startswith= 'P' )).all() print(q1)#[<Book: Python>, <Book: Perl>] # 2、可以组合使用&,|操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象。 Q(title__startswith= 'P' ) | Q(title__startswith= 'J' ) # 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合 Q(title__startswith= 'P' ) | ~Q(pub_date__year=2005) # 4、应用范围: # Each lookup function that takes keyword-arguments (e.g. filter(), # exclude(), get()) can also be passed one or more Q objects as # positional (not-named) arguments. If you provide multiple Q object # arguments to a lookup function, the arguments will be “AND”ed # together. For example: Book.objects. get ( Q(title__startswith= 'P' ), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) ) #sql: # SELECT * from polls WHERE question LIKE 'P%' # AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06') # import datetime # e=datetime.date(2005,5,6) #2005-05-06 # 5、Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。 # 正确: Book.objects. get ( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), title__startswith= 'P' ) # 错误: Book.objects. get ( question__startswith= 'P' , Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))) |
8,删除表记录(delete)
删除方法就是delete() 它运行时立即删除对象而不返回任何值。例如:
你可以一次性删除多个对象,每个QuerySet都有一个delete()方法,它一次性删除QuerySet中所有的对象。
例如下面的代码将删除pub_date是2005年的Entry对象:
1 | Entry.objects.filter(pub_date__year=2005).delete() |
在Django删除对象时,会模仿SQL约束ON DELETE CASCADE的行为,换句话说,删除一个对象时也会删除与它相关联的外键对象。例如:
1 2 3 | b = Blog.objects. get (pk=1) # This will delete the Blog and all of its Entry objects. b.delete() |
要注意的是:delete() 方法就是QuerySet上的方法,但并不适用于Manager本身。这是一种保护机制,是为了避免意外的调用Entry.objects.delete() 方法导致所有的记录被误删除。如果你确认要删除所有的对象,那么你必须显示的调用:
1 | Entry.objects.all().delete() |
如果不想级联删除,可以设置为:
1 | pubHouse = models.ForeignKey(to= 'Publisher' , on_delete=models.SET_NULL, blank=True, null =True) |
如果是多对多的关系:remove() 和 clear()方法
多对多删除的两个重要参数:clear() 清空数据 remove() 删除数据
1 2 3 4 5 6 7 8 9 10 11 | #正向 book = models.Book.objects.filter(id=1) #删除第三张表中和女孩1关联的所有关联信息 book.author.clear() #清空与book中id=1 关联的所有数据 book.author.remove(2) #可以为id book.author.remove(*[1,2,3,4]) #可以为列表,前面加* #反向 author = models.Author.objects.filter(id=1) author.book_set.clear() #清空与boy中id=1 关联的所有数据 |
9,修改表记录(update和save)
1 | Book.objects.filter(title__startswith= "py" ).update(price=120) |
此外,update() 方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录update()方法会返回一个整型数值,表示受影响的记录条数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #---------------- update方法直接设定对应属性---------------- models.Book.objects.filter(id=3).update(title= "PHP" ) ##sql: ##UPDATE "app01_book" SET "title" = 'PHP' WHERE "app01_book"."id" = 3; args=('PHP', 3) #--------------- save方法会将所有属性重新设定一遍,效率低----------- obj=models.Book.objects.filter(id=3)[0] obj.title= "Python" obj.save() # SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", # "app01_book"."color", "app01_book"."page_num", # "app01_book"."publisher_id" FROM "app01_book" WHERE "app01_book"."id" = 3 LIMIT 1; # # UPDATE "app01_book" SET "title" = 'Python', "price" = 3333, "color" = 'red', "page_num" = 556, # "publisher_id" = 1 WHERE "app01_book"."id" = 3; |
在这个例子里我们可以看到Django的save()方法更新了不仅仅是title列的值,还有更新了所有的列。若title以外的列有可能会被其他的进程所改动的情况下,只更改title列显然是更加明智的。更改某一指定的列,我们可以调用结果集(QuerySet)对象的update()方法,与之等同的SQL语句变得更加高效,并且不会引起竞态条件。
此外,update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录update()方法会返回一个整型数值,表示受影响的记录条数。
注意:这里因为update返回的是一个整型,所以没法用query属性;对于每次创建一个对象,想显示对应的raw sql ,需要在settings加上日志记录部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | LOGGING = { 'version' : 1, 'disable_existing_loggers' : False, 'handlers' : { 'console' :{ 'level' : 'DEBUG' , 'class' : 'logging.StreamHandler' , }, }, 'loggers' : { 'django.db.backends' : { 'handlers' : [ 'console' ], 'propagate' : True, 'level' : 'DEBUG' , }, } } |
注意:如果是多对多的改:
1 2 3 4 5 | obj=Book.objects.filter(id=1)[0] author=Author.objects.filter(id__gt=2) obj.author.clear() obj.author.add(*author) |