Django ORMでデータ取得時に計算したフィールドを追加する方法を解説します。annotate()やextra()を使うと、データベース側で計算を行い、結果を仮想的なフィールドとして取得できます。
基本的な使い方
annotate()とFオブジェクトを使って、既存フィールドから計算フィールドを追加します。
views.py
from django.db.models import F, FloatField
from django.db.models.functions import Cast
from .models import Sale
def index(request):
# amountの1.1倍を税込価格として追加
sales = Sale.objects.annotate(
tax_included=F('amount') * 1.1
).values('amount', 'sales_date', 'tax_included')
print(sales)実行結果
<QuerySet [
{'amount': 10000, 'sales_date': datetime.date(2025, 1, 15), 'tax_included': 11000.0},
{'amount': 25000, 'sales_date': datetime.date(2025, 1, 20), 'tax_included': 27500.0}
]>annotate()で追加したフィールドは、通常のフィールドと同様にアクセスできます。データベース側で計算されるため、Python側で処理するより効率的です。
様々な計算例
Fオブジェクトを使った算術演算の例です。
views.py
from django.db.models import F
# 割引後の金額を計算
sales = Sale.objects.annotate(
net_amount=F('amount') - F('discount')
)
# 割引率を計算
sales = Sale.objects.annotate(
discount_rate=F('discount') * 100.0 / F('amount')
)
# 数量 × 単価 の合計金額
orders = Order.objects.annotate(
total_price=F('price') * F('quantity')
)データベース関数の活用
Djangoには多くのデータベース関数が用意されています。
views.py
from django.db.models import Value, CharField
from django.db.models.functions import (
Concat, Upper, Round, Coalesce,
ExtractYear, ExtractMonth
)
# 文字列結合
users = User.objects.annotate(
full_name=Concat('last_name', Value(' '), 'first_name')
)
# 四捨五入
sales = Sale.objects.annotate(
rounded_tax=Round(F('amount') * 1.1, 0)
)
# NULL値のデフォルト値を設定
sales = Sale.objects.annotate(
actual_discount=Coalesce('discount', Value(0))
)
# 日付から年・月を抽出
sales = Sale.objects.annotate(
sale_year=ExtractYear('sales_date'),
sale_month=ExtractMonth('sales_date')
)条件分岐(Case/When)
SQLのCASE文に相当する条件分岐もannotateで表現できます。
views.py
from django.db.models import Case, When, Value, CharField
# 金額に応じたランク付け
sales = Sale.objects.annotate(
scale=Case(
When(amount__lt=5000, then=Value("小口")),
When(amount__lt=10000, then=Value("中口")),
default=Value("大口"),
output_field=CharField()
)
)実行結果
<QuerySet [
<Sale: 商品A - scale=小口>,
<Sale: 商品B - scale=中口>,
<Sale: 商品C - scale=大口>
]>annotateの結果でfilterやorder
追加した計算フィールドを使って、フィルタリングや並べ替えもできます。
views.py
from django.db.models import F
# 税込10000円以上のデータをフィルタ
high_sales = Sale.objects.annotate(
tax_included=F('amount') * 1.1
).filter(tax_included__gte=10000)
# 計算フィールドで並べ替え
sorted_sales = Sale.objects.annotate(
total_price=F('price') * F('quantity')
).order_by('-total_price')集計関数との組み合わせ
annotate()で集計関数を使うと、グループごとの集計値を各レコードに追加できます。
views.py
from django.db.models import Sum, Count
# カテゴリごとの売上合計と税込合計
category_stats = Sale.objects.values('product_category').annotate(
total_amount=Sum('amount'),
tax_included_total=Sum(F('amount') * 1.1),
sale_count=Count('id')
).order_by('-total_amount')実践的な使用例
売上一覧画面で計算フィールドを活用する実践例です。
views.py
from django.shortcuts import render
from django.db.models import F, Case, When, Value, CharField
from .models import Sale
def sale_list(request):
sales = Sale.objects.annotate(
tax_included=F('amount') * 1.1,
scale=Case(
When(amount__lt=5000, then=Value("小")),
When(amount__lt=10000, then=Value("中")),
default=Value("大"),
output_field=CharField()
)
).order_by('-sales_date')
return render(request, 'sales/list.html', {
'sales': sales
})テンプレートでは通常のフィールドと同様にアクセスできます。
templates/sales/list.html
{% for sale in sales %}
<tr>
<td>{{ sale.product_name }}</td>
<td>{{ sale.amount }}円</td>
<td>{{ sale.tax_included }}円</td>
<td>{{ sale.scale }}</td>
</tr>
{% endfor %}ポイント
annotate()で追加した計算フィールドはデータベース側で処理されるため、Pythonのループで1件ずつ計算するよりはるかに効率的です。大量データを扱う場合は積極的に活用しましょう。
注意
annotate()で追加したフィールドはデータベースには保存されません。毎回クエリ実行時に計算されます。頻繁に使う計算値は、モデルにフィールドを追加するか@propertyデコレータの使用を検討してください。
まとめ
annotate()とF()で既存フィールドから計算フィールドを追加できるConcat、Round、Coalesceなどのデータベース関数が利用できるCase/Whenで条件分岐の計算フィールドを作成できる- 追加した計算フィールドで
filter()やorder_by()もできる - 計算はデータベース側で実行されるためPythonで処理するより効率的