DjangoのORMで、QuerySetの各レコードに計算結果などの新しいフィールドを追加するには、annotate()メソッドを使用します。集計関数やF式と組み合わせて、データベース側で効率的に計算できます。
基本的な使い方
views.py
model = Person.objects.all().annotate(
next_age=F('age') + 1
).values()
説明
Step 1annotateメソッドの基本
Djangoでは、annotateメソッドを使用して、クエリ結果に一時的なフィールドを追加することができます。基本的な構文は以下の通りです:
モデル.objects.annotate(追加するフィールド名=追加するデータ)
annotateを使用しても取得したデータに対してフィールドを追加するだけなので、元のモデルにフィールドが増えるわけではありません。
Step 2基本的な使用例
例えば、Personモデルのageフィールドに1を足した値を新しいフィールドとして追加する場合:
from django.db.models import F
# ageフィールドに1を足した値をnext_ageフィールドとして追加
persons = Person.objects.annotate(next_age=F('age') + 1)
上の例では、Personモデルにannotateでnext_ageフィールドにageフィールドの値+1した値を追加しています。
Step 3様々な計算の例
annotateメソッドでは、様々な計算や集計ができます:
from django.db.models import F, Sum, Count, Avg, Max, Min
# 数値演算
products = Product.objects.annotate(
discounted_price=F('price') * 0.9, # 10%割引価格
price_with_tax=F('price') * 1.1 # 税込価格
)
# 文字列連結(PostgreSQLの場合)
from django.db.models.functions import Concat
from django.db.models import Value
persons = Person.objects.annotate(
full_name=Concat('first_name', Value(' '), 'last_name')
)
Step 4集計関数との組み合わせ
関連モデルの集計値を注釈として追加することも可能です:
# 会社ごとの社員数を追加
companies = Company.objects.annotate(employee_count=Count('persons'))
# 会社ごとの平均年齢を追加
companies = Company.objects.annotate(avg_age=Avg('persons__age'))
# 会社ごとの最高年齢と最低年齢を追加
companies = Company.objects.annotate(
max_age=Max('persons__age'),
min_age=Min('persons__age')
)
# 会社ごとの部署数を追加
companies = Company.objects.annotate(department_count=Count('departments', distinct=True))
Step 5フィルタリングと組み合わせる
annotateで追加したフィールドを使ってフィルタリングすることもできます:
# 社員数が10人以上の会社を取得
companies = Company.objects.annotate(
employee_count=Count('persons')
).filter(employee_count__gte=10)
# 平均年齢が30歳以上の会社を取得
companies = Company.objects.annotate(
avg_age=Avg('persons__age')
).filter(avg_age__gte=30)
# 追加フィールドでソート
companies = Company.objects.annotate(
employee_count=Count('persons')
).order_by('-employee_count') # 社員数の多い順
Step 6実践的な使用例
views.pyでのannotateメソッドの使用例:
from django.shortcuts import render
from django.db.models import Count, Avg, F
from .models import Company, Person
def company_statistics(request):
# 会社ごとの統計情報を計算
companies = Company.objects.annotate(
employee_count=Count('persons'),
avg_age=Avg('persons__age'),
next_year_avg_age=Avg('persons__age') + 1
).order_by('-employee_count')
return render(request, 'companies/statistics.html', {
'companies': companies
})
def product_pricing(request):
# 割引率をパラメータから取得
discount_rate = float(request.GET.get('discount', 0)) / 100
# 標準価格と割引価格を計算
products = Product.objects.annotate(
price_with_tax=F('price') * 1.1,
discounted_price=F('price') * (1 - discount_rate)
)
return render(request, 'products/pricing.html', {
'products': products,
'discount_rate': discount_rate * 100
})
テンプレートでの使用例(statistics.html):
<h1>会社統計</h1>
<table>
<tr>
<th>会社名</th>
<th>社員数</th>
<th>平均年齢</th>
<th>来年の平均年齢</th>
</tr>
{% for company in companies %}
<tr>
<td>{{ company.name }}</td>
<td>{{ company.employee_count }}人</td>
<td>{{ company.avg_age|floatformat:1 }}歳</td>
<td>{{ company.next_year_avg_age|floatformat:1 }}歳</td>
</tr>
{% endfor %}
</table>
重要ポイント:
- annotateは一時的なフィールドを追加するため、データベースに変更は加えられません。
- 複雑な計算をデータベース側で行うことができるため、パフォーマンスの向上につながります。
- F式やデータベース関数を使用することで、より高度な計算が可能です。
- 複数のannotateを連鎖させると、各annotateの結果を次のannotateで使用できます。
まとめ
annotate()でQuerySetの各レコードに計算フィールドを追加できるCount,Sum,Avg,Max,Minなどの集計関数と組み合わせて使う- 追加したフィールドは
filter()やorder_by()の条件にも使える - F式と組み合わせてフィールド同士の演算結果を追加できる
- すべての計算はデータベース側で行われるため効率的