ORM

Django ORMのaggregate入門|合計値・平均値の取得方法

Django ORMでフィールドの合計値を取得する方法を解説します。aggregate()メソッドとSum関数を使うと、SQLのSUM()に相当する集計をPythonコードで簡単に実行できます。

基本的な使い方

フィールドの合計値を取得するには、aggregate()Sumを組み合わせます。

views.py
from django.db.models import Sum
from .models import Sale

def index(request):
    # amountフィールドの合計を取得
    result = Sale.objects.aggregate(Sum('amount'))
    print(result)
実行結果
{'amount__sum': 1250000}

aggregate()は辞書を返します。キーはフィールド名__関数名の形式です。カスタムキー名を指定することもできます。

views.py
# カスタムキー名を指定
result = Sale.objects.aggregate(total=Sum('amount'))
print(result)  # {'total': 1250000}
print(result['total'])  # 1250000

条件付きの合計

filter()と組み合わせると、条件に一致するレコードだけの合計を計算できます。

views.py
from django.db.models import Sum
from datetime import date

# 今月の売上合計
result = Sale.objects.filter(
    sales_date__year=date.today().year,
    sales_date__month=date.today().month
).aggregate(total=Sum('amount'))

# 特定カテゴリの売上合計
result = Sale.objects.filter(
    product_category="電化製品"
).aggregate(total=Sum('amount'))

# 特定の顧客の購入合計
result = Sale.objects.filter(
    customer_id=5
).aggregate(total=Sum('amount'))

複数の集計を同時に取得

aggregate()には複数の集計関数を同時に渡せます。

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

result = Sale.objects.aggregate(
    total=Sum('amount'),
    average=Avg('amount'),
    count=Count('id'),
    min_amount=Min('amount'),
    max_amount=Max('amount')
)
print(result)
実行結果
{'total': 1250000, 'average': 25000.0, 'count': 50, 'min_amount': 1000, 'max_amount': 150000}

グループごとの合計(annotate)

グループ化して合計を取得するには、values()annotate()を組み合わせます。

views.py
from django.db.models import Sum

# カテゴリごとの売上合計
category_totals = Sale.objects.values('product_category').annotate(
    total=Sum('amount')
).order_by('-total')

for item in category_totals:
    print(f"{item['product_category']}: {item['total']}円")
実行結果
電化製品: 500000円
家具: 350000円
食品: 200000円
書籍: 120000円
衣類: 80000円

values()でグループ化のキーを指定し、annotate()で各グループに集計値を付与します。SQLのGROUP BYに相当します。

関連モデルの合計

関連モデルのフィールドを集計することもできます。

views.py
from django.db.models import Sum

# 各顧客の購入合計金額を取得
customers_with_total = Customer.objects.annotate(
    total_purchases=Sum('sale__amount')
).order_by('-total_purchases')

for customer in customers_with_total:
    print(f"{customer.name}: {customer.total_purchases}円")

annotate()を使うと、各オブジェクトに集計値が属性として追加されます。テンプレートでも{{ customer.total_purchases }}のようにアクセスできます。

実践的な使用例

売上ダッシュボードを構築する実践例です。

views.py
from django.shortcuts import render
from django.db.models import Sum, Count, Avg
from datetime import date, timedelta
from .models import Sale

def sales_dashboard(request):
    today = date.today()
    first_of_month = today.replace(day=1)
    last_month_start = (first_of_month - timedelta(days=1)).replace(day=1)

    # 総売上
    total_sales = Sale.objects.aggregate(
        total=Sum('amount')
    )['total'] or 0

    # 今月の売上
    monthly_sales = Sale.objects.filter(
        sales_date__gte=first_of_month
    ).aggregate(total=Sum('amount'))['total'] or 0

    # 前月の売上
    prev_month_sales = Sale.objects.filter(
        sales_date__gte=last_month_start,
        sales_date__lt=first_of_month
    ).aggregate(total=Sum('amount'))['total'] or 0

    # カテゴリ別売上
    category_sales = Sale.objects.values(
        'product_category'
    ).annotate(
        total=Sum('amount'),
        count=Count('id'),
        avg=Avg('amount')
    ).order_by('-total')

    return render(request, 'sales/dashboard.html', {
        'total_sales': total_sales,
        'monthly_sales': monthly_sales,
        'prev_month_sales': prev_month_sales,
        'category_sales': category_sales,
    })
ポイント

aggregate()はクエリセット全体の集計結果を辞書で返し、annotate()は各オブジェクトに集計値を属性として付与します。全体の合計が欲しい場合はaggregate()、グループごとの合計が欲しい場合はvalues().annotate()を使い分けましょう。

注意

aggregate()の結果で対象レコードが0件の場合、SumNoneを返します。or 0を付けるか、Sum('amount', default=0)(Django 4.0以降)を使ってNone対策を行いましょう。

まとめ

  • aggregate(Sum('field'))でフィールドの合計値を辞書として取得できる
  • Sum以外にもAvgCountMinMaxが使える
  • values().annotate()でグループごとの集計ができる(GROUP BY相当)
  • annotate()は各オブジェクトに集計値を属性として追加する
  • 対象が0件の場合にNoneが返るため、or 0default=0で対策する