1. 程式人生 > >django QuerySet 的常用API

django QuerySet 的常用API

size 數據量 https 新的 內部 連表 銷量 sca cat

為了加深對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‘: ‘[email protected]‘}]>


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指定的處理方式。

django QuerySet 的常用API