1. 程式人生 > 實用技巧 >Django model 層之聚合查詢總結

Django model 層之聚合查詢總結

Django model 層之聚合查詢總結

by:授客 QQ1033553122

實踐環境

Python版本:python-3.4.0.amd64

下載地址:https://www.python.org/downloads/release/python-340/

Win7 64位

Django 1.11.4

下載地址:https://www.djangoproject.com/download/

聚合查詢

MySQL資料庫為例,假設專案目錄結構如下:

mysite/

myapp/

__init__.py

admin.py

apps.py

migrations/

__init__.py

models.py

tests.py

views.py

 manage.py
 mysite/
 __init__.py
 settings.py
 urls.py
 wsgi.py

models.py內容如下:

from django.db import models

# Create your models here.

class Person(models.Model):

first_name = models.CharField(max_length=30)

last_name = models.CharField(max_length=30)

class Book(models.Model):

book_name = models.CharField(max_length=30)

borrower = models.ForeignKey(Person, to_field='id', on_delete=models.CASCADE)

class Store(models.Model):

id = models.AutoField(primary_key=True)

name = models.CharField(max_length=50)

last_update = models.DateField(auto_now=True)

class Production_addr(models.Model):

addr = models.CharField(max_length=50)

distance = models.IntegerField()

class Fruit(models.Model):

store = models.ManyToManyField(Store)

production_addr = models.ForeignKey(Production_addr, to_field='id', on_delete=models.CASCADE)

name = models.CharField(max_length=100)

onsale_date = models.DateField()

price = models.IntegerField()

class Taste(models.Model):

taste = models.CharField(max_length=50)

fruit=models.ManyToManyField(Fruit)

class News(models.Model):

title = models.CharField(max_length=20)

n_comments = models.IntegerField()

n_pingbacks = models.IntegerField()

rank = models.IntegerField()

class Blog(Book):

author = models.CharField(max_length=50)

針對整個QuerySet生成聚合

例:查詢myapp_news表中,rank值大於26的記錄,其n_comments平均值

>>> from myapp.models import News

>>> from django.db.models import Avg

>>> News.objects.filter(rank__gt=26).aggregate(Avg('n_comments'))

{'n_comments__avg': 17.0}

如果是針對所有記錄求均值,我們可以這樣

>>> News.objects.all().aggregate(Avg('n_comments'))

{'n_comments__avg': 23.0}

也可以去掉all()

>>> News.objects.aggregate(Avg('n_comments'))

{'n_comments__avg': 23.0}

返回結果說明 {'聚合結果值標識':'聚合結果值'}

自定義聚合結果標識

>>> News.objects.all().aggregate(average_price = Avg('n_comments'))

{'average_price': 23.0}

>>> from django.db.models import Avg, Max, Min

>>> News.objects.aggregate(Avg('n_comments'), Max('n_comments'), Min('n_comments'))

{'n_comments__max': 35, 'n_comments__min': 14, 'n_comments__avg': 23.0}

針對整個QuerySet的每項生成聚合

可以理解為mysql中的分組統計,Model.objects.annotate(……) ,不過不一樣的是,這裡沒有指定分組欄位,是按每個model物件分組。

例子:Fruit和Store model存在多對多關係。現在需要查詢,myapp_fruit表中某條記錄(可以理解為每類水果),有多少家商店在出(myapp_store表中每條記錄對應一個商店)

>>> from myapp.models import Fruit, Production_addr, Store, Taste

>>> from django.db.models import Count

>>> q = Fruit.objects.annotate(Count('store'))

>>> q[0]

<Fruit: Fruit object>

>>> q[0].store__count

1

>>> q[1].store__count

3

>>> q[2].store__count

1

預設的,annotation標識由aggregate函式及被聚合field而來(例中為store__count),類似aggregate, 可以自定義annotation標識

>>> q = Fruit.objects.annotate(store_num = Count('store'))

>>> q[0].store_num

1

>>>

和aggregate不同的是,annotate()語句輸出結果為QuerySet,支援其它QuerySet操作,包括filter(),order_by(),甚至是再次呼叫annotate()

>>> q = Fruit.objects.annotate(store_num = Count('store')).filter(id__gt=3)

>>> q[0]

<Fruit: Fruit object>

>>> q[0].store_num

3

混用多個聚合函式

使用annotate()函式,混用多個聚合函式,會返回錯誤的結果,因為實現使用的是join查詢,而非子查詢。針對count聚合函式,可以使用distinct=True引數避免這個問題

例子:檢索myapp_fruit表中第一個條記錄,查詢出售該類水果的商店數及該類水果的口味總數。

>>> fruit = Fruit.objects.first()

>>> fruit.store.count()

2

>>> fruit.taste_set.count()

3

>>> from django.db.models import Count

>>> q = Fruit.objects.annotate(Count('store'), Count('taste'))

>>> q[0].store__count

6

>>> q[0].taste__count

6

解決方法:

>>> q = Fruit.objects.annotate(Count('store', distinct=True), Count('taste', distinct=True))

>>> q[0].taste__count

3

>>> q[0].store__count

2

>>>

聯合查詢與聚合

有時候,需要獲取和當前正在查詢模組關聯的另一個模組的相關聚合值,這個時候,可在聚合函式中,指定欄位使用雙下劃線方式,關聯相關模組進行join查詢

例子:檢索myapp_store表,查詢每個商店正在出售水果種類中,最高價和最低價。

>>> q = Store.objects.annotate(min_price=Min('fruit__price'), max_price=Max('fruit__price'))

>>> for item in q:

... print(item.min_price, item.max_price)

...

10 20

19 20

None None

None None

None None

None None

聯合查詢的深度取決於你的查詢要求。

例子:檢索myapp_store表,查詢每個商店正在出售水果種類中,產地最遠是多遠。

>>> from django.db.models import Min, Max

>>> q = Store.objects.annotate(max_distance=

Min('fruit__production_addr__distance'))

>>> for item in q:

... print(item.name, item.max_distance)

...

aimi 40

ximi 20

xima None

masu None

gami None

gama None

反向關聯查詢

例:查詢每個產地的水果種類數量(myapp_production_addr.id是myapp_fruit表的外來鍵)

>>> q = Production_addr.objects.annotate(cnt=Count('fruit'))

>>> for item in q:

... print(item.addr, item.cnt)

...

changting 1

shanghang 1

longyan 1

例,檢索所有產地產出的水果種類,最小价格

>>> q = Production_addr.objects.aggregate(min_price=Min('fruit__price'))

>>> print(q)

{'min_price': 10}

對比(分組統計):

>>> q = Production_addr.objects.annotate(min_price=Min('fruit__price'))

>>> for item in q:

... print(item.addr, item.min_price)

...

changting 20

shanghang 16

longyan 10

不僅僅是針對外來鍵,針對多對多關係也可以

>>> from django.db.models import Avg

>>> q = Taste.objects.annotate(avg_price=Avg('fruit__price'))

>>> for item in q:

... print(item.taste, item.avg_price)

...

sour 20.0

sweet 20.0

bitter 20.0

聚合及其它QuerySet語句

filter()exclude()

例子:統計myapp_fruit表中banana除外的水果種類的最小价

>>> Fruit.objects.exclude(name='banana').aggregate(Min('price'))

{'price__min': 16}

filter也支援類似用法

Filtering on annotations

例子:檢索myapp_store表,查詢每個商店正在出售水果種類中最低價,過濾最低價小於等於10的。

>>> Store.objects.annotate(min_price=Min('fruit__price')).filter(min_price__gt=10)

說明:先執行annotate,得到結果集,然後執行filter語句,得出結果。

注意:annotationsfilter()exclude()語句是有先後順序之分的,後一步的處理依賴前一步的結果,順序不一樣,結果可能也會也不一樣。

order_by()

例子:檢索myapp_store表,查詢每個商店正在出售水果種類中最低價,並按最低價升許排序。

>>> Store.objects.annotate(min_price=Min('fruit__price')).order_by('min_price')

<QuerySet [<Store: Store object>, <Store: Store object>, <Store: Store object>,<Store: Store object>, <Store: Store object>, <Store: Store object>]>

values()

values()結合annotate的使用

例子:檢索myapp_store表,按商店名稱分組查詢商店正在出售水果種類中最低價

>>> Store.objects.values('name').annotate(min_price=Min('fruit__price'))

<QuerySet [{'min_price': 10, 'name': 'aimi'}, {'min_price': 19, 'name': 'ximi'}, {'min_price': None, 'name': 'xima'}, {'min_price': None, 'name': 'masu'}, {'min_price': None, 'name': 'gami'}, {'min_price': None, 'name': 'gama'}]>

>>>

可以理解為mysql中的分組統計,values('filed')中指定filed即為分組統計欄位

注意:類似filter()valuesannotate也有先後順序之分。

annotateaggregate配合使用

例:

>>> Store.objects.values('name').annotate(min_price=Min('fruit__price')).aggregate(Avg('min_price'))

{'min_price__avg': 14.5}

說明,鏈式處理

其它例子

參考連結:https://www.cnblogs.com/YingLai/p/6601243.html

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

v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))

# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id

v= models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)

# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)

# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

更多詳情,參考連結:

https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#