1. 程式人生 > >Django中聚合函式,F表示式和Q表示式詳解

Django中聚合函式,F表示式和Q表示式詳解

學習聚合函式的準備工作

新建一個專案,在新建一個app,名字隨意,然後在app中的models中定義幾個模型:

from django.db import models

# Create your models here.
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'

然後將模型對映至資料庫中,即先makegrations後在migrate。然後手動向表中新增資訊。
我新增的資訊為
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
然後配置urls,直至專案執行不報錯。

1.Avg:

求平均值。比如想要獲取所有圖書的價格平均值。那麼可以使用以下程式碼實現。在app中的views中的index中寫入程式碼

from django.shortcuts import render
from django.http import HttpResponse
from . import models
from django.db.models import Avg

# Create your views here.
def index(request):
    # 獲取所有圖書的定價的平均價格
    result = models.Book.objects.aggregate(Avg('price'))
    # 列印返回的結果
    print(result)
    # 列印原生的sql語句
    print(result.query)
    
    return HttpResponse('success')

注意:

  1. 在使用Avg方法時需要匯入。
  2. aggregate方法返回的是一個字典。
  3. Avg()裡面填寫需要獲取平均值的屬性。

然後與執行專案,訪問網址,我們發現報錯了
在這裡插入圖片描述
只是因為字典沒有query這個屬性,只有QuerySet這個物件才擁有這個屬性,才能使用這個屬性來檢視sql原生語句。
這裡我們就得換一種方法來檢視sql的原生語句了,
修改views中的程式碼

from django.shortcuts import render
from django.http import HttpResponse
from . import models
from django.db.models import Avg
from django.db import connection

# Create your views here.
def index(request):
    # 獲取所有圖書的定價的平均價格
    result = models.Book.objects.aggregate(Avg('price'))
    # 列印返回的結果
    print(result)
    # 列印原生的sql語句,會報錯,只有QuerySet物件才有query這個屬性
    # print(result.query)

    # 換一種方法列印sql原生語句
    print(connection.queries)

    return HttpResponse('success')

然後我們就能夠看到返回的平均值和sql原生語句了,返回的sql語句是一個列表。列表裡面存放著一個一個的字典,字典裡面存放著sql程式碼和執行時間,這個列表裡面最後一個字典中的sql語句就是models.Book.objects.aggregate(Avg('price'))django中對這句程式碼的翻譯。
在這個sql語句裡面我們可以看到一個price__avg這個變數。
在這裡插入圖片描述
這個變數就是聚合函式執行完成之後個這個聚合函式的返回值取的一個名字,如果我們不想要這個名字,我們也可以自己取一個名字。
只需要在查詢的時候新增一個引數。修改result = models.Book.objects.aggregate(Avg('price'))這句程式碼為

result = models.Book.objects.aggregate(avg = Avg('price'))

這樣我們就自己取了一個avg的名字。

注意:

  1. 所有的聚合函式都是放在django.db.models下面。
  2. 聚合函式不能夠單獨的執行,需要放在一些可以執行聚合函式的方法下面中去執行。比如aggregate下面。
  3. 聚合函式執行完成後,給這個聚合函式的值取個名字。取名字的規則,預設是filed+__+聚合函式名字形成的。比如上面的price__avg。如果不想使用預設的名字,那麼可以在使用聚合函式的時候傳遞關鍵字引數進去,引數的名字就是聚合函式執行完成的名字。
  4. aggregate:這個方法不會返回一個QuerySet物件,而是返回一個字典。這個字典中的key就是聚合函式的名字,值就是聚合函式執行後的結果。

在繼續講解聚合函式之前我們先了解一下aggregateannotate的區別:

首先講一個需求,現在我需要獲取到每一本賣出去書的實際價格的平均值,而我們實際賣出去的書的價格在book_order這個表中,儲存了最終賣書出去的價格和對應的書名。

在views中新定義一個函式index1,新增程式碼

def index1(request):
    result = models.Book.objects.aggregate(avg=Avg('bookorder__price'))
    print(result)
    print(connection.queries)
    return HttpResponse('success')

執行專案,我們可以發現返回的結果只有一個數據,這並不是我們想要的結果,因為在orderbook這張表中擁有兩本書的價格,我們需要的是有基本書返回幾個資料,而這裡只給我們返回了一個數據,顯然不能滿足我們的需求。
這個時候就需要使用到annotate這個方法來代替aggregate了,修改index1中的程式碼:

def index1(request):
    # 從book中訪問bookorder中的price
    # result = models.Book.objects.aggregate(avg=Avg('bookorder__price'))
    # print(result)
    # print(connection.queries)

    books = models.Book.objects.annotate(avg = Avg('bookorder__price'))
    print(books)
    for book in books:
        print('%s/%s'%(book.name,book.avg))
    print(connection.queries)
    return HttpResponse('success')

執行專案檢視效果。
在這裡插入圖片描述
這樣我們就得到我們想要的效果了,將每本書的平均價格都得到了。
注意: 在Book這個模型中,我們沒有avg這個屬性,那麼我們使用的avg這個屬性時從什麼地方來的呢。這個屬性就是在使用聚合函式之後Django自動給我們新增的,我們可以通過這個屬性在獲取聚合函式返回的值。

aggregateannotate的相同和不同:

  • 相同:這兩個方法都可以執行聚合函式。
  • 不同:
    1. aggregate返回的是一個字典,在這個字典中儲存的是這個聚合函式執行的結果。而annotate返回的是一個QuerySet物件,並且會在查詢的模型上新增一個聚合函式的屬性。
    2. aggregate不會做分組,而annotate會使用group by子句進行分組,只有呼叫了group by子句,就是對id為同一個的進行分組。才能對每一條資料求聚合函式的值。

count:

用來求某個資料的個數。比如要求所有圖書的數量,那麼可以使用以下程式碼:新增加一個index2的函式,寫入程式碼:

def index2(request):
    # book表中總共有多少本書(book中有多少id)
    result = models.Book.objects.aggregate(book_nums = Count('id'))
    print(result)
    print(connection.queries)
    return HttpResponse('success')

這樣,我們就獲取了book中總共有多少本書。

接下來我們還有一個需求,那就是獲取賣出去的書的種類的個數。因為賣出去的豎肯定是由重複的,即一本書賣出去了多本。而我們重要賣出去的不相同的書的個數。修改index2的程式碼

def index2(request):
    # book表中總共有多少本書(book中有多少id)
    # result = models.Book.objects.aggregate(book_nums = Count('id'))
    # print(result)
    # print(connection.queries)

    # 獲取賣出去的不同的書的書名個數。
    result = models.BookOrder.objects.aggregate(book_nums = Count('book_id',distinct=True))
    print(result)
    print(connection.queries)
    return HttpResponse('success')

可以看到我們只是在Count中添加了distinct=True,就實現了我們的需求。distinct=True用來剔除那些重複的值,只保留一個。可以把distinct=True去掉之後來檢視一下效果,看一下結果會一樣嗎。

接下來我燜需要獲取每本書的銷量:修改index2的程式碼

def index2(request):
    # book表中總共有多少本書(book中有多少id)
    # result = models.Book.objects.aggregate(book_nums = Count('id'))
    # print(result)
    # print(connection.queries)

    # 獲取賣出去的不同的書的書名個數。
    # result = models.BookOrder.objects.aggregate(book_nums = Count('book_id',distinct=True))
    # print(result)
    # print(connection.queries)

    # # 統計每本書的銷量
    books = models.Book.objects.annotate(book_nums = Count('bookorder__id'))
    for book in books:
        print("%s/%s"%(book.name,book.book_nums))
    print(books)
    print(connection.queries)
    return HttpResponse('success')

這樣, 我們就能獲取每本書的銷量了。

Max 和 Min:

求指定欄位的最大值和最小值。

需求:獲取author中年齡最大的和最小的

新建一個函式index3,寫入程式碼:

def index3(request):
    # 獲取author中年齡最大的和最小的
    result = models.Author.objects.aggregate(max=Max('age'),min=Min('age'))
    print(result)
    print(connection.queries)
    return HttpResponse('success')

這樣就實現了我們的需求,接下來我們繼續實現另外的需求
獲取每一本圖書售賣的時候的最大價格和最小价格

修改index3的程式碼:

def index3(request):
    # 獲取author中年齡最大的和最小的
    # result = models.Author.objects.aggregate(max=Max('age'),min=Min('age'))
    # print(result)
    # print(connection.queries)

    books = models.Book.objects.annotate(max=Max('bookorder__price'),min=Min('bookorder__price'))
    print(books)
    for book in books:
        print('%s的最高價格:%s    最低價格:%s'%(book.name,book.max,book.min))
    return HttpResponse('success')

怎樣就實現了我們的需求

Sum:

求某個欄位值的總和。
需求一:求所有圖書的銷售總額

新新增一個函式index4:新增程式碼

def index4(request):
	# 求所有圖書的銷售總額
    result = models.BookOrder.objects.aggregate(total=Sum('price'))
    print(result)

    return HttpResponse('success')

需求二:求每種圖書的銷售總額
修改index4 的程式碼:

def index4(request):
    # 求所有圖書的銷售總額
    # result = models.BookOrder.objects.aggregate(total=Sum('price'))
    # print(result)

    # 求每種圖書的銷售總額
    results = models.Book.objects.annotate(total=Sum('bookorder__price'))
    print(results)
    for result in results:
        print('%s的銷售總額為:%s'%(result.name,result.total))
    return HttpResponse('success')

注意: aggregateannotate方法可以在任何的QuerySet物件上呼叫。因此只要是返回了QuerySet物件,那麼就可以進行鏈式呼叫。例如在使用filter後在面再使用aggregateannotate

更多的聚合函式請參考官方文件:https://docs.djangoproject.com/en/2.1/ref/models/querysets/#aggregation-functions

F表示式:

F表示式是用來優化ORM操作資料庫的。比如我們要將所有圖書的定價都增加100元,如果按照正常的流程,應該是先從資料庫中提取所有的圖書定價到Python記憶體中,然後使用Python程式碼在圖書定價的基礎之上增加100元,最後再儲存到資料庫中。這裡面涉及的流程就是,首先從資料庫中提取資料到Python記憶體中,然後在Python記憶體中做完運算,之後再儲存到資料庫中。

示例:
新建一個index5:編寫程式碼

def index5(request):
    # 給每一本圖書定價增加100元
    models.Book.objects.update(price=F('price')+100)
    print(connection.queries)
    return HttpResponse('success')

這樣,我們使用一行程式碼就實現 了這個需求,而不用先取到python記憶體中在進行操作了。

需求2:
獲取author中nameemail相同的一條資料:

為了展示效果,首先我們先去資料庫中修改一個name使其值和對應的email的值相同。

然後修改index5中的程式碼:

from django.db.models import Avg,Count,Max,Min,Sum,F

def index5(request):
    # 給每一本圖書定價增加100元
    # models.Book.objects.update(price=F('price')+100)
    # print(connection.queries)

    # 獲取author的name和email值一樣的一條資料
    authors = models.Author.objects.filter(name=F('email'))
    for author in authors:
        print('%s的email和name值一樣,'%author.name)
    print(connection.queries)
    return HttpResponse('success')

Q表示式:

使用Q表示式包裹查詢條件,可以在條件之間進行多種操作。與/或非等,從而實現一些複雜的查詢操作。

需求一:獲取書籍中pages大於1000的,並且評分大於等於4.9的書籍。

新建一個index6的函式:編寫程式碼:

不使用Q表示式:

def index6(request):
    # 獲取書籍中pages大於1000的,並且評分大於等於4.9的書籍。
    # 不使用Q表示式
    books = models.Book.objects.filter(pages__gte=1000,rating__gte=4.9)
    for book in books:
        print('%s-%s-%s'%(book.name,book.pages,book.rating))
    print(connection.queries)
    return HttpResponse('success')

使用Q表示式

def index6(request):
    # 獲取書籍中pages大於1000的,並且評分大於等於4.9的書籍。
    # 不使用Q表示式
    # books = models.Book.objects.filter(pages__gte=1000,rating__gte=4.9)
    # for book in books:
    #     print('%s-%s-%s'%(book.name,book.pages,book.rating))
    # print(connection.queries)

    # 使用Q表示式
    books = models.Book.objects.filter(Q(pages__gte=1000)&Q(rating__gte=4.9))
    for book in books:
        print('%s-%s-%s'%(book.name,book.pages,book.rating))
    print(connection.queries)
    return HttpResponse('success')

我們可以看到使不使用Q表示式差別並不大,只是因為filter預設就是傳遞的多個條件就是與(&)操作,如果我們想實現等操作,那麼不使用Q表示式操作將很繁瑣。

需求二:獲取書籍中pages小於900的,或者評分大於等於4.85的書籍。

修改index6中的程式碼:

def index6(request):
    # 獲取書籍中pages大於1000的,並且評分大於等於4.9的書籍。
    # 不使用Q表示式
    # books = models.Book.objects.filter(pages__gte=1000,rating__gte=4.9)
    # for book in books:
    #     print('%s-%s-%s'%(book.name,book.pages,book.rating))
    # print(connection.queries)

    # 使用Q表示式
    # books = models.Book.objects.filter(Q(pages__gte=1000)&Q(rating__gte=4.9))
    # for book in books:
    #     print('%s-%s-%s'%(book.name,book.pages,book.rating))
    # print(connection.queries)

    # 獲取書籍中pages小於900的,或者評分大於等於4.85的書籍。
    books = models.Book.objects.filter(Q(pages__lt=900) | Q(rating__gte=4.85))
    for book in books:
        print('%s-%s-%s'%(book.name,book.pages,book.rating))
    print(connection.queries)
    return HttpResponse('success')

需求三:獲取書籍中pages大於1000的,並且書名中不能包含有的圖書

修改index6中的程式碼;

def index6(request):
    # 獲取書籍中pages大於1000的,並且評分大於等於4.9的書籍。
    # 不使用Q表示式
    # books = models.Book.objects.filter(pages__gte=1000,rating__gte=4.9)
    # for book in books:
    #     print('%s-%s-%s'%(book.name,book.pages,book.rating))
    # print(connection.queries)

    # 使用Q表示式
    # books = models.Book.objects.filter(Q(pages__gte=1000)&Q(rating__gte=4.9))
    # for book in books:
    #     print('%s-%s-%s'%(book.name,book.pages,book.rating))
    # print(connection.queries)

    # 獲取書籍中pages小於900的,或者評分大於等於4.85的書籍。
    # books = models.Book.objects.filter(Q(pages__lt=900) | Q(rating__gte=4.85))
    # for book in books:
    #     print('%s-%s-%s'%(book.name,book.pages,book.rating))
    # print(connection.queries)

    # 獲取書籍中pages大於1000的,並且書名中不能包含有`記`的圖書
    books = models.Book.objects.filter(Q(pages__gte=1000) & ~Q(name__icontains='記'))
    for book in books:
        print('%s-%s-%s'%(book.name,book.pages,book.rating))
    print(connection.queries)
    return HttpResponse('success')

想深入學習django的可以看一下這個視訊:超詳細講解Django打造大型企業官網