Django model 層之聚合查詢總結
by:授客 QQ:1033553122
實踐環境
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語句,得出結果。
注意:annotations和filter()、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(),values和annotate也有先后順序之分。
annotate和aggregate配合使用
例:
>>> 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/#