django QuerySet 的常用API

Posted 大蒙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django QuerySet 的常用API相关的知识,希望对你有一定的参考价值。

为了加深对queryset对象api的了解,我们建立了以下示例模型:
  

  from django.db import models

 

  class Author(models.Model):
  """作者模型"""
    name = models.CharField(max_length=100)
    age = models.IntegerField()
    email = models.EmailField()

    class Meta:
      db_table = \'author\'


  class Publisher(models.Model):
  """出版社模型"""
    name = models.CharField(max_length=300)

    class Meta:
      db_table = \'publisher\'


  class Book(models.Model):
  """图书模型"""
    name = models.CharField(max_length=300)
    pages = models.IntegerField()
    price = models.FloatField()
    rating = models.FloatField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)

    class Meta:
      db_table = \'book\'


  class BookOrder(models.Model):
    """图书订单模型"""
    book = models.ForeignKey("Book", on_delete=models.CASCADE)
    price = models.FloatField()

    class Meta:
      db_table = \'book_order\'

 

QuerySet的常用API:
  1、filter:将满足条件的数据提取出来,返回新的QuerySet对象,可以继续链式调用执行QuerySet的所有Api,在查询过滤条件方面,之前博客https://www.cnblogs.com/limaomao/p/9302331.html中所有的条件都是作为filter中的过滤条件的参数,这里就不一一赘述了
  # 查询出id大于等于2的所有图书
  books = Book.objects.filter(id__gte=2)

 

  2、exclude:排除符合条件的数据,同样也是返回一个新的QuerySet对象。在查询条件方面,同样使用的是https://www.cnblogs.com/limaomao/p/9302331.html这篇博客中所提到的查询条件
  # 查询出id大于等于2,并且id不等于3的所有图书
  使用exclude:books = Book.objects.filter(id__gte=2).exclude(id=3)
  使用Q表达式:books = Book.objects.filter(~Q(id=3),id__gte=2) # Q表达式与关键字参数连用查询,Q表达式要放在前面

 

  3、annotate:给QuerySet中的每一个对象都增加一个使用查询表达式(聚合函数,F表达式,Q表达式,func表达式等)生成的属性:
  books = Book.objects.annotate(author_name=F(\'author__name\'))
  for book in books:
    print(\'%s/%s\'%(book.name,book.author_name))
  原生SQL:SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, `author`.`name` AS `author_name` FROM `book` INNER JOIN `author` ON (`book`.`author_id` = `author`.`id`)
  注意:这里使用annotate,并且使用F表达式,给Book模型增加一个author_name

 

  4、order_by:指定将查询的结果根据某个字段(该字段也可以是使用annotate生成的字段,也可以是连表中的字段,连表时,传递的字段和查询连表操作时传递的字段相同)进行排序。默认排序方式为升序排序,如果要倒叙/降序排序,那么可以在这个字段的前面加一个负号。如果指定多个排序字段,那么优先以第一个条件排序,如果第一个排序结果一样,则按第二个排序,以此类推

  # 提取所有的订单,首先按照实际售价进行降序排序,如果实际售价相同,那么以图书的name进行排序
  bookorders = BookOrder.objects.annotate(book_name=F(\'book__name\')).order_by(\'-price\',\'book_name\')
  原生SQL:SELECT `book_order`.`id`, `book_order`.`book_id`, `book_order`.`price`, `book`.`name` AS `book_name` FROM `book_order` INNER JOIN `book` ON (`book_order`.`book_id` = `book`.`id`) ORDER BY `book_order`.`price` DESC, `book_name` ASC

  一定要注意的一点是,多个order_by,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:
  books = Book.objects.order_by(\'-price\').order_by(\'rating\')
  这样的写法,只会使用\'rating\'字段进行排序,而\'price\'字段的排序会作废

 

  5、values:用来提取指定的字段(字段也可以是使用annotate生成的字段。也可是连表中的字段,此时字段名称与连表查询时是一样的,如果想要在提取时改变带有"__"的默认字段名,就需要使用F表达式去动态提取连表中字段数据加上更改名称)。默认情况下会把表中所有的字段全部都提取出来,可以使用values来进行指定,并且使用了values方法后,返回值依然是QuerySet对象,只不过提取出的新的QuerySet中的数据不再是完整的模型,而是在values方法中指定的字段和值形成的字典,如果values不指定任何字段,则会默认的提取模型中所有的字段作为键,字段的值作为值生成一个内部是字典的QuerySet对象:

  # 按照售价从高到低提取所有书籍的id,price
  results = Book.objects.order_by(\'-price\').values(\'id\',\'price\')
  print(results)
  结果为:<QuerySet [{\'id\': 2, \'price\': 137.0}, {\'id\': 4, \'price\': 119.0}, {\'id\': 1, \'price\': 118.0}, {\'id\': 3, \'price\': 115.0}]>

 

  # 查找出所有图书的id,name,销量(使用annotate及Count聚合函数生成的字段),作者名(连表中的字段,使用F表达式进行了重命名)
  books = Book.objects.annotate(total=Count(\'bookorder\')).values(\'id\',\'name\',\'total\',author_name=F(\'author__name\'))
  结果为:<QuerySet [{\'id\': 1, \'name\': \'三国演义(售)\', \'total\': 3, \'author_name\': \'罗贯中\'}, {\'id\': 2, \'name\': \'水浒传(售)\', \'total\': 2, \'author_name\': \'施耐庵\'}, {\'id\': 3, \'name\': \'西游记(售)\', \'total\': 0, \'author_name\': \'吴承恩\'}, {\'id\': 4, \'name\': \'红楼梦(售)\', \'total\': 0, \'author_name\': \'wce@qq.com\'}]>


6、values_list:类似于values。只不过返回的QuerySet中,存储的不是字典,而是元组。示例代码如下:

  # 按照售价从高到低提取所有书籍的id,price
  results = Book.objects.order_by(\'-price\').values_list(\'id\',\'price\')
  print(results)
  结果为:<QuerySet [(2, 137.0), (4, 119.0), (1, 118.0), (3, 115.0)]>

 

  如果在values_list中只有一个字段。那么你可以传递flat=True来将结果扁平化,那么此时得到的新的QuerySet就相当于一个列表,列表内是所有模型中该字段的值。示例代码如下
  results = Book.objects.order_by(\'-price\').values_list(\'price\',flat=True)
  print(results)
  结果为:<QuerySet [137.0, 119.0, 118.0, 115.0]>

 

  7、all:获取整个模型的QuerySet对象,数据库层面上是把所有模型对象的所有字段提取到内存中

 

  8、select_related:在查询数据的时候,如果之后的操作中会使用到连表中的字段,为了减少查询语句,我们可以事先使用select_related将OneToOneField或者ForeignKey连接的表中的数据全部提取到内存之中,这样在在之后提取连表中的数据的时候就不会再次执行SQL查询了,大量减少SQL查询,从而提高性能。其在SQL层面的原理是通过join连接在查询当前模型的数据的同时预先查询出外键(一对一也是外键)连接的主表的所有字段。此函数只能用于一对一字段OneToOneField和使用了ForeignKey的外键字段。

  # 查询Book模型中的每一本书的名字以及其出版社的名字,使用常规方式:
  books = Book.objects.all()
  for book in books:
    print(\'%s/%s\'%(book.name,book.publisher.name))

  使用这种方式在提取连表中的字段的时候,如果Book模型里有n个实例,那么会在SQL层面执行n+1次SQL查询,显然这种查询的效率极低。如果我们使用select_related在查询Book模型中数据的同时预先查询出在使用ForeignKey连接的Publisher模型中的数据,那么无论Book模型有多少条数据,在SQL层面上只会执行一条SQL语句,这样会大大提高orm操作的效率:

  books = Book.objects.all().select_related(\'publisher\') # select_related适用于一对一,多对一
  for book in books:
    print(\'%s/%s\'%(book.name,book.publisher.name))

  select_related接受可变长参数,每个参数是需要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键需要使用两个下划线“__”来连接,示例代码如下:

  # 获取BookOrder模型中每条数据所对应的书的作者的名字
  bookorders = BookOrder.objects.all().select_related(\'book__author\')
  for bookorder in bookorders:
    print(bookorder.book.author.name)

 

  9、prefetch_related:这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法是为了解决外键的反向提取和多对多的关系的查询问题。比如:

  # 要获取图书Book的名称和每本图书所有订单的id,使用传统方法:
  from django.db import connection
  books = Book.object.all()
  for book in books:
    print(book.name)
  

  orders = book.bookorder_set.all()
  for order in orders:
    print(order.id)

  # 要获取图书Book的名称和每本图书所有订单的id,使用prefetch_related:
  from django.db import connection
  books = Book.object.prefetch_related(\'bookorder_set\')
  for book in books:
    print(book.name)
    orders = book.bookorder_set.all()
    for order in orders:
      print(order.id)

  如果对比两个查询的原生SQL,你会发现使用prefetch_related的方式的SQL只有两条,在数据很多的时候,查询SQL数量远远小于传统方式,因此之中方式可以大大提高效率

  但是如果在使用book.bookorder_set的时候,在使用其他api的时候,如果使用api时又创建了一个新的QuerySet那么会把之前的SQL优化给破坏掉,显然这是不科学的,因此如果我们想要对book.bookorder_set进行进一步过滤,就需要使用django.db.models.Prefetch类,进行预先的过滤,例如提取价格大于100的订单的id:
  from django.db.models import Prefetch
  prefetch = Prefetch(\'bookorder_set\',BookOrder.objects.filter(price__gt=100))
  books = Book.object.prefetch_related(prefetch)
  for book in books:
    print(book.name)
    orders = book.bookorder_set.all()
      for order in orders:
        print(order.id)

 

  10、defer:在一些表中,可能存在很多的字段,但是一些字段的数据量可能是比较庞大的,而此时你又不需要,比如我们在获取文章列表的时候,文章的内容我们是不需要的,因此这时候我们就可以使用defer来过滤掉一些字段。这个字段跟values有点类似,只不过defer返回的不是字典,而是模型,所以我们在拿数据的时候可以使用模型名+.+字段的方式来提取,如果我们在提取数据的时候使用了defer过滤的字段,那么也不会报错,只不过在每查询一条数据时,都会执行新的SQL:
  books = Book.objects.defer(\'price\')
  for book in books:
    print(book.id,book.name)

 

  11、only:在提取数据的时候,只提取某些字段,结果同样是一个新的QuerySet,这个QuerySet可以里面和defer一样,是模型实例,不是字典,无论提不提取id字段,他都会自动的为我们提取id字段

 

  12、aggregate:使用聚合函数,详解在之前的博客里有,这里就不再赘述了,博客地址:https://www.cnblogs.com/limaomao/p/9327740.html

 

  13、create:相当于创建新的数据并保存,示例代码:
  publisher = Publisher(name=\'人民教育出版社\')
  publisher.save()


  如果使用create,上面两行代码可以写成一行:
  Publisher.objects.create(name=\'人民教育出版社\')

 

  14、get_or_create:获取模型数据,如果获取不到就把查询条件作为字段值用来创建模型数据并保存到数据库,返回值是一个元祖,元祖的第一个值是获取或者创建的实例,第二个值表示是否执行了创建操作,如果执行了创建操作,则返回True,否则,返回True。这个函数的应用场景有很多,在这里举一个例子说明,在定义model的时候,如果使用ForeignKey,该字段的on_delete=models.SET_DEFAULT,那么这个字段应该有一个default属性,这个default属性可以传递一个函数,这个函数就可以使用get_or_create方法,示例代码如下:


  def get_default_publisher():
    return Publisher.objects.get_or_create(name=\'人民教育出版社\')[0]

  class Book(models.Model):
    name = models.CharField(max_length=100)
    publisher = models.ForeignKey(\'Publisher\',on_delete=models.SET_DEFAULT,default=get_default_publisher)

 

  15、bulk_create:一次创建多条数据,这个方法减少了SQL语句,因此能提高效率,示例代码如下:
  names = [\'人民教育出版社\',\'清华大学出版社\',\'北京大学出版社\',\'安徽大学出版社\',\'南京大学出版社\',\'上海理工出版社\']
  publishers = []
  for name in names:
    publishers.append(Publisher(name=name))
  Publisher.objects.bulk_create(publishers)

 

  16、count:获取提取的数据的个数。如果想要知道总共有多少条数据,那么建议使用count,而不是使用len(articles)这种。因为count在底层是使用select count(*)来实现的,这种方式比使用len函数更加的高效。

  17、firstlast:返回QuerySet中的第一条和最后一条数据。

 

  18、update:执行更新操作,在SQL底层走的也是update命令。比如要将所有category为空的article的article字段都更新为默认的分类。示例代码如下:

  Article.objects.filter(category__isnull=True).update(category_id=3)
  注意这个方法走的是更新的逻辑。所以更新完成后保存到数据库中不会执行save方法,因此不会更新auto_now设置的字段。

 

  19、delete:删除所有满足条件的数据。删除数据的时候,要注意on_delete指定的处理方式。

 

 

  20、distinct:去重,去除掉那些重复的数据。这个方法如果底层数据库用的是mysql,那么不能传递任何的参数。这个方法会对提取的所有字段综合起来进行去重,如使用了values、values_list方法提取的字段,还有如果使用连表中的字段作为条件进行查询时,连表中有多少条数据符合条件就会提取本模型中多少条相关数据,此时可以使用distinct进行去重,这时去重的条件就是本模型中的全部字段(注意如果在使用distinct之前本模型如果使用了annotate生成了新的字段,那么新的字段也会被拿来判断是否重复),示例代码如下:

 

  books = Book.objects.filter(bookorder__price__gte=80).distinct()

  需要注意的是,如果在distinct之前使用了order_by,那么因为order_by会提取order_by中指定的字段,因此再使用distinct就会根据多个字段来进行唯一化,所以就不会把那些重复的数据删掉。示例代码如下:

 

  orders = BookOrder.objects.order_by("create_time").values("book_id").distinct()
  那么以上代码因为使用了order_by,即使使用了distinct,也会把重复的book_id提取出来。

 

 

  21、切片操作:有时候我们查找数据,有可能只需要其中的一部分。那么这时候可以使用切片操作来帮我们完成。QuerySet使用切片操作就跟列表使用切片操作是一样的。示例代码如下:

 

  books = Book.objects.all()[1:3]
  for book in books:
  print(book)
  切片操作并不是把所有数据从数据库中提取出来再做切片操作。而是在数据库层面使用LIMIE和OFFSET来帮我们完成。所以如果只需要取其中一部分的数据的时候,建议大家使用切片操作。

以上是关于django QuerySet 的常用API的主要内容,如果未能解决你的问题,请参考以下文章

Django常用的QuerySet操作

ORM模型里连接数据库常用方法和QuerySet API

Django的model操作中一些常用的小点

django模型系统二

django模型系统二

DJango filter_queryset