ORM

Django ORMでデータの一括集計|aggregate()の使い方

DjangoのORMでQuerySet全体の集計(合計、平均、最大値、最小値、件数など)を行うには、aggregate()メソッドを使用します。annotate()が各レコードに値を追加するのに対し、aggregate()はQuerySet全体を1つの値にまとめます。

基本的な使い方

views.py
from django.db.models import Avg

# 全商品の平均価格を取得
avg_price = Product.objects.aggregate(Avg('price'))

説明

Step 1Aggregate関数の基本

Djangoのaggregate()メソッドは、クエリセット全体に対して集計を行い、辞書型で結果を返します。これまで紹介したannotate()がクエリセットの各オブジェクトに集計フィールドを追加するのに対し、aggregate()はクエリセット全体に対して単一の集計値を返します。

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

# 基本的な使い方
result = モデル名.objects.aggregate(集計関数('フィールド名'))

Step 2基本的な使用例

例えば、Productモデルの全商品の平均価格を取得する場合:

from django.db.models import Avg

# 全商品の平均価格を取得
avg_price = Product.objects.aggregate(Avg('price'))
# 結果: {'price__avg': 1234.56}

返り値は辞書型で、キーはデフォルトで「フィールド名__集計関数」という形式になります。

Step 3複数の集計を同時に行う

複数の集計を一度のクエリで行うことも可能です:

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

# 複数の集計を一度に行う
result = Product.objects.aggregate(
    avg_price=Avg('price'),
    max_price=Max('price'),
    min_price=Min('price'),
    total_inventory=Sum('stock'),
    product_count=Count('id')
)
# 結果: {'avg_price': 1234.56, 'max_price': 9999.99, 'min_price': 10.0, 'total_inventory': 1500, 'product_count': 100}

このように、キーワード引数を使用することで、結果の辞書のキー名をカスタマイズできます。

Step 4条件付き集計

フィルタリングと組み合わせて、条件に一致するレコードだけを集計することもできます:

# アクティブな商品の平均価格
active_avg = Product.objects.filter(is_active=True).aggregate(
    active_avg_price=Avg('price')
)

# カテゴリー別の商品数
from django.db.models import Count
category_counts = Product.objects.values('category').annotate(
    count=Count('id')
).order_by('-count')

# 複数条件を組み合わせた集計
from django.db.models import Q
result = Product.objects.filter(
    Q(category='Electronics') | Q(category='Computers')
).aggregate(
    avg_price=Avg('price'),
    total_stock=Sum('stock')
)

Step 5関連モデルの集計

関連するモデルのフィールドに対しても集計を行えます:

# 全顧客の注文合計金額
from django.db.models import Sum
total_sales = Customer.objects.aggregate(
    total_order_amount=Sum('order__amount')
)

# 各商品の総売上(annotateを使用)
product_sales = Product.objects.annotate(
    total_sales=Sum('orderitem__quantity * orderitem__price')
).aggregate(
    grand_total=Sum('total_sales')
)

Step 6実践的な使用例

views.pyでのAggregate関数の使用例:

from django.shortcuts import render
from django.db.models import Avg, Sum, Min, Max, Count
from .models import Product, Order, Customer

def dashboard(request):
    # 商品統計
    product_stats = Product.objects.aggregate(
        total_products=Count('id'),
        avg_price=Avg('price'),
        max_price=Max('price'),
        min_price=Min('price'),
        total_stock=Sum('stock')
    )
    
    # 売上統計
    sales_stats = Order.objects.aggregate(
        total_orders=Count('id'),
        total_revenue=Sum('total_amount'),
        avg_order_value=Avg('total_amount'),
        max_order=Max('total_amount')
    )
    
    # カテゴリー別商品数
    category_stats = Product.objects.values('category').annotate(
        count=Count('id'),
        avg_category_price=Avg('price')
    ).order_by('-count')
    
    # アクティブか非アクティブかで分けた商品数
    status_counts = {
        'active': Product.objects.filter(is_active=True).count(),
        'inactive': Product.objects.filter(is_active=False).count()
    }
    
    return render(request, 'dashboard.html', {
        'product_stats': product_stats,
        'sales_stats': sales_stats,
        'category_stats': category_stats,
        'status_counts': status_counts
    })

テンプレートでの使用例(dashboard.html):

<h1>ダッシュボード</h1>

<div class="stats-card">
    <h2>商品統計</h2>
    <p>総商品数: {{ product_stats.total_products }}点</p>
    <p>平均価格: {{ product_stats.avg_price|floatformat:0 }}円</p>
    <p>最高価格: {{ product_stats.max_price|floatformat:0 }}円</p>
    <p>最低価格: {{ product_stats.min_price|floatformat:0 }}円</p>
    <p>在庫総数: {{ product_stats.total_stock }}個</p>
</div>

<div class="stats-card">
    <h2>売上統計</h2>
    <p>総注文数: {{ sales_stats.total_orders }}件</p>
    <p>総売上: {{ sales_stats.total_revenue|floatformat:0 }}円</p>
    <p>平均注文額: {{ sales_stats.avg_order_value|floatformat:0 }}円</p>
    <p>最高注文額: {{ sales_stats.max_order|floatformat:0 }}円</p>
</div>

<div class="stats-card">
    <h2>カテゴリー別商品数</h2>
    <table>
        <tr>
            <th>カテゴリー</th>
            <th>商品数</th>
            <th>平均価格</th>
        </tr>
        {% for category in category_stats %}
            <tr>
                <td>{{ category.category }}</td>
                <td>{{ category.count }}点</td>
                <td>{{ category.avg_category_price|floatformat:0 }}円</td>
            </tr>
        {% endfor %}
    </table>
</div>
重要ポイント:
  • aggregate()はクエリセット全体に対する単一の結果を返し、辞書型で返されます。
  • 複数の集計を一度に行うことができ、キーワード引数を使って結果のキー名をカスタマイズできます。
  • 集計関数にはAvgCountMaxMinSumなどがあります。
  • filter()と組み合わせることで、条件に一致するレコードだけを集計できます。
  • 複雑な集計では、annotate()で中間計算を行ってからaggregate()を使用するパターンも有効です。

まとめ

  • aggregate()はQuerySet全体の集計結果を辞書形式で返す
  • Sum, Avg, Max, Min, Countなどの集計関数が使える
  • 複数の集計を1回のクエリで同時に実行できる
  • filter()と組み合わせて条件付きの集計が可能
  • annotate()との違いは、QuerySet全体を1つの辞書にまとめる点
  • 戻り値はQuerySetではなく辞書(dict)なので、チェーンはできない