Djangoのannotateの今風な使い方: リレーション数、最大値、最小値、平均値の集計
- 作成日: 2021-08-06
- 更新日: 2023-12-26
- カテゴリ: Django
Djangoのannotateの今風な使い方
Djangoではモデルの集計を行うための関数がいくつか用意されています。
その中の1つが今回紹介するannotate()関数です。
annotate()
を使うとForeignKey
などでリレーションしているモデルの集計を簡単に行うことが出来ます。
たとえばA
というモデルを持っているB
のフィールドC
をA
について集計する、といった具合です。
この記事ではannotate()
について具体的に↓を見ていきます。
- 解説の前提とするモデル
- 解説の前提とするクラス
- データベースの初期化
- annotate()の構造
- 評価数の集計
- 最大評価の集計
- 最小評価の集計
- 平均評価の集計
関連記事
Djangoのexcludeで指定のレコードを除外する
DjangoのQuerySetのdistinct()の使い方
解説の前提とするモデル
今回のannotate()
の解説で使用するモデルは↓のような定義になります。
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
class Response(models.Model):
"""
ブログへのレスポンス
"""
name = models.CharField(max_length=64, help_text='投稿者名')
content = models.TextField(max_length=512, help_text='投稿内容')
class Evaluation(models.Model):
"""
レスポンスへの評価
星の数(stars)で評価する
"""
STARS_CHOICES = (
('☆☆☆☆☆', 5),
('☆☆☆☆★', 4),
('☆☆☆★★', 3),
('☆☆★★★', 2),
('☆★★★★', 1),
)
stars = models.IntegerField(
choices=STARS_CHOICES,
validators=[MinValueValidator(1), MaxValueValidator(5)],
help_text='星',
)
response = models.ForeignKey(Response, on_delete=models.PROTECT, help_text='評価先のレス')
ブログを構成するblog
というアプリを作ってあります。
そしてそのモデルが↑です。
Response
はブログの記事へのレスポンスを表し、Evaluation
はそのレスポンスの評価を表します。
Evaluation
はよくある星の5段階評価になってます。
解説の前提とするクラス
以降のコードでは省略しますが、↓のクラスを事前にインポートしている前提で解説を行います。
あらかじめご了承ください。
from django.db.models import Count, Max, Min, Avg
from blog.models import Response, Evaluation
データベースの初期化
データベースを↓のように初期化します。
response_1 = Response.objects.create(name='太郎', content='こんにちは')
response_2 = Response.objects.create(name='マイケル', content='こんにちは')
response_3 = Response.objects.create(name='スーパーボブ', content='Hi, Guys')
Evaluation.objects.create(response=response_1, stars=3)
Evaluation.objects.create(response=response_1, stars=4)
Evaluation.objects.create(response=response_1, stars=5)
Evaluation.objects.create(response=response_1, stars=1)
Evaluation.objects.create(response=response_2, stars=2)
Evaluation.objects.create(response=response_2, stars=2)
Evaluation.objects.create(response=response_2, stars=4)
Evaluation.objects.create(response=response_3, stars=5)
レスポンスを3つ作り、それぞれのレスポンスに評価を加えています。
annotate()の構造
annotate()
はQuerySet
のメソッドです。
その構造は↓のようになっています。
def annotate(self, *args, **kwargs):
...
可変長引数と可変長キーワード引数を取るだけのシンプルなメソッドです。
内部では_annotate()
に処理を委譲しています。
annotate()
はQuerySet
のメソッドなので、↓のようにモデルのobjects
から使うことが出来ます。
Response.objects.annotate()
また、annotate()
の結果もQuerySet
なので↓のように他のメソッドを繋げることも可能です。
Response.objects.annotate(Count('evaluation')).filter(evaluation__count__gte=2)
評価数の集計
Response
に繋がっているEvaluation
の数を集計したい場合は↓のようにします。
# 評価数の集計
for response in Response.objects.annotate(Count('evaluation')):
print(f'投稿者名[{response.name}] 評価数[{response.evaluation__count}]')
投稿者名[太郎] 評価数[4]
投稿者名[マイケル] 評価数[3]
投稿者名[スーパーボブ] 評価数[1]
Response
のannotate()
にCount()
を渡すと、Count()
の引数のモデル数(あるいはフィールド名)をカウントしてくれます。
↑の場合はCount('evaluation')
としていますので、Response
に紐づけられているEvaluation
の数がカウントされます。
カウント結果は「クラスの引数__クラス名
」というフォーマットで、モデルに保存されます。↑の例で言うと、evaluation__count
という属性がresponse
に保存されています。
このevaluation__count
が実際の集計の値になります。
↑の出力結果では投稿者名「太郎」の評価数が「4」になっていますが、これは太郎に紐づけられているEvaluation
の数です。
最大評価の集計
レスポンスに繋がっている最大評価の集計を行うには↓のようにします。
# 最大評価の集計
for response in Response.objects.annotate(Max('evaluation__stars')):
print(f'投稿者名[{response.name}] 最大評価[{response.evaluation__stars__max}]')
投稿者名[太郎] 最大評価[5]
投稿者名[マイケル] 最大評価[4]
投稿者名[スーパーボブ] 最大評価[5]
最大評価の集計にはMax()
をannotate()
に渡します。
Max()
の引数には「モデル名__フィールド名
」というフォーマットで集計したいフィールドを指定します。
↑の例ではEvaluation
のstars
を指定しています。
↑の場合、「太郎」の最大評価は「5」で、「マイケル」の最大評価は「4」になっています。
最小評価の集計
レスポンスに繋がっている最小評価の集計を行うには↓のようにします。
# 最小評価の集計
for response in Response.objects.annotate(Min('evaluation__stars')):
print(f'投稿者名[{response.name}] 最小評価[{response.evaluation__stars__min}]')
投稿者名[太郎] 最小評価[1]
投稿者名[マイケル] 最小評価[2]
投稿者名[スーパーボブ] 最小評価[5]
最小評価の集計を行うにはMin()
をannotate()
に渡します。
↑の場合、「太郎」の最小評価は「1」で、マイケルの最小評価は「2」になっています。
平均評価の集計
レスポンスに繋がっている評価の平均値を集計するには↓のようにします。
# 平均評価の集計
for response in Response.objects.annotate(Avg('evaluation__stars')):
print(f'投稿者名[{response.name}] 最小評価[{response.evaluation__stars__avg}]')
投稿者名[太郎] 最小評価[3.25]
投稿者名[マイケル] 最小評価[2.6666666666666665]
投稿者名[スーパーボブ] 最小評価[5.0]
平均値を集計するにはAvg()
をannotate()
に渡します。
↑の場合、太郎の評価数は4
つで評価はそれぞれ3
, 4
, 5
, 1
ですが、その平均値3.25
が集計できています。
🦝 < ( 3 + 4 + 5 + 1 ) / 4 = 3.25 ですね
複数の集計
たとえば最大値と最小値の評価を同時に集計したい場合があります。
そういう場合はannotate()
に複数のクラス(Max
, Min
など)を渡せば集計可能です。
# 最大評価と最小評価の集計
for response in Response.objects.annotate(Min('evaluation__stars'), Max('evaluation__stars')):
print(f'投稿者名[{response.name}] 最大評価[{response.evaluation__stars__max}] 最小評価[{response.evaluation__stars__min}]')
投稿者名[太郎] 最大評価[5] 最小評価[1]
投稿者名[マイケル] 最大評価[4] 最小評価[2]
投稿者名[スーパーボブ] 最大評価[5] 最小評価[5]
集計変数のエイリアス
集計変数のエイリアスを作ることができます。
annotate()
にキーワード引数でクラスを指定すると、そのクラスの結果がそのキーワード引数名のフィールドに保存されます。
# 集計変数のエイリアス
for response in Response.objects.annotate(max_stars=Max('evaluation__stars')):
print(f'投稿者名[{response.name}] 最大評価[{response.max_stars}]')
↑の場合、max_stars
というのがエイリアスです。
max_stars
はResponse
に保存され、それを参照することで↑の場合、最大評価を取得しています。
おわりに
今回はDjangoのannotate()
を見てみました。
annotate()
を使えば手軽に集計を行うことが可能です。
🦝 < 評価数で勝負だ!
🐭 < いいや最大評価で勝負だ!