ORM

年、月、日ごとに集計する

views.py
from django.db.models import Sum
from django.db.models.functions import TruncMonth

month_sales = Person.objects.annotate(
    month=TruncMonth('sales__date')).annotate(total_sales=Sum('sales__sale')
).values('month', 'name','total_sales').order_by('month')

print(month_sales)

説明

Step 1Trunc関数の基本

Djangoでは、Trunc関数を使用して日付時間フィールドを特定の精度(年、月、週、日など)で切り捨てて集計することができます。これにより、時系列データの集計や分析が容易になります。

from django.db.models.functions import TruncMonth, TruncYear, TruncWeek, TruncDay, TruncHour

これらの関数は通常、annotateメソッドと一緒に使用されます。

Step 2TruncMonthの使用例

TruncMonthを使用すると、日付データを月ごとにまとめることができます:

from django.db.models.functions import TruncMonth
from django.db.models import Sum

# 月ごとの売上集計
monthly_sales = Person.objects.annotate(
    month=TruncMonth('sales__date')
).values('month').annotate(
    total_sales=Sum('sales__sale')
).order_by('month')

この例では、annotateでPersonモデルのmonthフィールドにsales__dateを月でまとめたもの(1日から31日までのデータが当月の1日としてまとめられる)を、total_salesには月でまとめたもののsaleの合計が代入されます。

Step 3様々な時間単位でのTrunc関数

他の時間単位でも同様にデータをまとめることができます:

from django.db.models.functions import TruncYear, TruncWeek, TruncDay, TruncHour
from django.db.models import Sum

# 年ごとの集計
yearly_sales = Person.objects.annotate(
    year=TruncYear('sales__date')
).values('year').annotate(
    total_sales=Sum('sales__sale')
).order_by('year')

# 週ごとの集計
weekly_sales = Person.objects.annotate(
    week=TruncWeek('sales__date')
).values('week').annotate(
    total_sales=Sum('sales__sale')
).order_by('week')

# 日ごとの集計
daily_sales = Person.objects.annotate(
    day=TruncDay('sales__date')
).values('day').annotate(
    total_sales=Sum('sales__sale')
).order_by('day')

# 時間ごとの集計
hourly_sales = Person.objects.annotate(
    hour=TruncHour('sales__date')
).values('hour').annotate(
    total_sales=Sum('sales__sale')
).order_by('hour')

このように、TruncYearにすると年ごと、TruncWeekにすると週ごと、TruncDayにすると日ごと、TruncHourにすると1時間ごとの集計が可能です。

Step 4複数の集計関数との組み合わせ

Trunc関数と複数の集計関数を組み合わせることもできます:

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

monthly_stats = Person.objects.annotate(
    month=TruncMonth('sales__date')
).values('month').annotate(
    total_sales=Sum('sales__sale'),
    avg_sale=Avg('sales__sale'),
    sales_count=Count('sales'),
    max_sale=Max('sales__sale'),
    min_sale=Min('sales__sale')
).order_by('month')

この例では、月ごとの売上合計、平均売上、売上件数、最高売上、最低売上を一度に計算しています。

Step 5フィルタリングとの組み合わせ

Trunc関数はフィルタリングと組み合わせることもできます:

from django.db.models.functions import TruncMonth
from django.db.models import Sum
from datetime import datetime

# 今年の月別売上
current_year = datetime.now().year
monthly_sales_this_year = Person.objects.annotate(
    month=TruncMonth('sales__date')
).values('month').annotate(
    total_sales=Sum('sales__sale')
).filter(
    month__year=current_year
).order_by('month')

# 特定の担当者の月別売上
sales_by_person = Person.objects.filter(
    name='山田太郎'
).annotate(
    month=TruncMonth('sales__date')
).values('month').annotate(
    total_sales=Sum('sales__sale')
).order_by('month')

Step 6実践的な使用例

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

from django.shortcuts import render
from django.db.models.functions import TruncMonth, TruncYear
from django.db.models import Sum, Count
from .models import Person, Sales
from datetime import datetime

def sales_report(request):
    # 期間フィルターの取得(デフォルトは今年)
    year = request.GET.get('year', datetime.now().year)
    
    # 月別売上データ
    monthly_data = Person.objects.annotate(
        month=TruncMonth('sales__date')
    ).values('month').annotate(
        total_sales=Sum('sales__sale'),
        sales_count=Count('sales')
    ).filter(
        month__year=year
    ).order_by('month')
    
    # 年別売上データ(過去5年分)
    yearly_data = Person.objects.annotate(
        year=TruncYear('sales__date')
    ).values('year').annotate(
        total_sales=Sum('sales__sale'),
        sales_count=Count('sales')
    ).order_by('-year')[:5]
    
    # 担当者別・月別の売上データ
    person_monthly_data = Person.objects.annotate(
        month=TruncMonth('sales__date')
    ).values('name', 'month').annotate(
        total_sales=Sum('sales__sale')
    ).filter(
        month__year=year
    ).order_by('name', 'month')
    
    return render(request, 'sales/report.html', {
        'monthly_data': monthly_data,
        'yearly_data': yearly_data,
        'person_monthly_data': person_monthly_data,
        'selected_year': year
    })

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

<h1>売上レポート({{ selected_year }}年)</h1>

<form method="get">
    <label for="year">年を選択:</label>
    <select name="year" id="year" onchange="this.form.submit()">
        {% for y_data in yearly_data %}
            <option value="{{ y_data.year|date:'Y' }}" {% if y_data.year|date:'Y' == selected_year %}selected{% endif %}>
                {{ y_data.year|date:'Y' }}年
            </option>
        {% endfor %}
    </select>
</form>

<h2>月別売上</h2>
<table>
    <tr>
        <th>月</th>
        <th>売上合計</th>
        <th>売上件数</th>
    </tr>
    {% for data in monthly_data %}
        <tr>
            <td>{{ data.month|date:'Y年m月' }}</td>
            <td>{{ data.total_sales|floatformat:0 }}円</td>
            <td>{{ data.sales_count }}件</td>
        </tr>
    {% empty %}
        <tr>
            <td colspan="3">データがありません</td>
        </tr>
    {% endfor %}
</table>

<h2>担当者別・月別売上</h2>
{% regroup person_monthly_data by name as person_data %}
{% for person in person_data %}
    <h3>{{ person.grouper }}</h3>
    <table>
        <tr>
            <th>月</th>
            <th>売上合計</th>
        </tr>
        {% for data in person.list %}
            <tr>
                <td>{{ data.month|date:'Y年m月' }}</td>
                <td>{{ data.total_sales|floatformat:0 }}円</td>
            </tr>
        {% endfor %}
    </table>
{% endfor %}
重要ポイント:
  • Trunc関数は、時系列データの分析や集計に非常に便利です。
  • 使用できる主なTrunc関数には、TruncYear、TruncQuarter、TruncMonth、TruncWeek、TruncDay、TruncHour、TruncMinute、TruncSecondがあります。
  • values()メソッドと組み合わせることで、グループ化した集計が可能になります。
  • DateField、DateTimeFieldの両方に対して使用できますが、時間単位の関数(TruncHourなど)はDateTimeFieldにのみ使用できます。
  • 集計結果は、データベースのタイムゾーン設定に依存します。Djangoの設定(TIME_ZONE)と一致するように注意してください。