ORM

Django ORMで関連データをカウント|annotateとCountの使い方

DjangoのORMで、関連するモデルのレコード数を各レコードに追加するには、annotate()Count()を組み合わせます。例えば「各カテゴリの記事数」や「各部署の社員数」などを効率的に取得できます。

基本的な使い方

views.py
from django.db.models import Count

model = Company.objects.all().annotate(
    persons=Count('person')
).values()

print(model)

説明

Step 1Count関数の基本

Djangoでは、Count関数を使用して関連するモデルの数を数えることができます。基本的な構文は以下の通りです:

from django.db.models import Count

Count(数えたいフィールド)

この関数は、annotateやaggregateメソッドと組み合わせて使用します。

Step 2基本的な使用例

例えば、Companyモデルに紐づいているPersonモデルの数を数える場合:

from django.db.models import Count

# 各会社に紐づくPersonの数をカウント
companies = Company.objects.annotate(persons_count=Count('persons'))

上の例は、自身に紐づいたPersonモデルの数量をpersons_countフィールドに代入しています。

Step 3フィルタリングと組み合わせる

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

# 社員が5人以上の会社だけを取得
companies = Company.objects.annotate(
    persons_count=Count('persons')
).filter(persons_count__gte=5)

# 特定の部署の社員数をカウント
companies = Company.objects.annotate(
    dev_count=Count('persons', filter=Q(persons__department='開発部'))
)

filterパラメータを使うことで、カウント対象を特定の条件に一致するものだけに限定できます。

Step 4distinct引数を使う

重複を除外してカウントする場合は、distinct=Trueを指定します:

# 各会社にある部署の数(重複を除く)
companies = Company.objects.annotate(
    department_count=Count('persons__department', distinct=True)
)

# 各会社が取引している顧客の数(重複を除く)
companies = Company.objects.annotate(
    customer_count=Count('projects__customer', distinct=True)
)

distinctを使うことで、同じ値が複数回出現しても1つとしてカウントされるようになります。

Step 5複数のカウントを同時に行う

1つのクエリで複数の集計を行うことができます:

# 各会社の社員数と部署数を同時に取得
companies = Company.objects.annotate(
    persons_count=Count('persons'),
    department_count=Count('departments')
)

# 部署ごとの男性社員数と女性社員数
departments = Department.objects.annotate(
    male_count=Count('persons', filter=Q(persons__gender='男性')),
    female_count=Count('persons', filter=Q(persons__gender='女性'))
)

Step 6実践的な使用例

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

from django.shortcuts import render
from django.db.models import Count, Q
from .models import Company, Department

def company_statistics(request):
    # 会社ごとの統計情報を計算
    companies = Company.objects.annotate(
        # 全社員数
        total_employees=Count('persons'),
        
        # 部署ごとの社員数
        dev_employees=Count('persons', filter=Q(persons__department='開発部')),
        sales_employees=Count('persons', filter=Q(persons__department='営業部')),
        admin_employees=Count('persons', filter=Q(persons__department='管理部')),
        
        # 部署数(重複を除外)
        department_count=Count('persons__department', distinct=True)
    ).order_by('-total_employees')
    
    return render(request, 'companies/statistics.html', {
        'companies': companies
    })

def department_comparison(request):
    # 部署ごとの統計
    departments = Department.objects.annotate(
        employee_count=Count('persons'),
        male_ratio=Count('persons', filter=Q(persons__gender='男性')) * 100.0 / Count('persons'),
        project_count=Count('persons__projects', distinct=True)
    ).order_by('-employee_count')
    
    return render(request, 'departments/comparison.html', {
        'departments': departments
    })

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

<h1>会社統計</h1>

<table>
    <tr>
        <th>会社名</th>
        <th>総社員数</th>
        <th>開発部</th>
        <th>営業部</th>
        <th>管理部</th>
        <th>部署数</th>
    </tr>
    {% for company in companies %}
        <tr>
            <td>{{ company.name }}</td>
            <td>{{ company.total_employees }}人</td>
            <td>{{ company.dev_employees }}人</td>
            <td>{{ company.sales_employees }}人</td>
            <td>{{ company.admin_employees }}人</td>
            <td>{{ company.department_count }}部署</td>
        </tr>
    {% endfor %}
</table>
重要ポイント:
  • Count関数は、Django ORMのクエリ内で数を集計するために使用します。
  • annotateと組み合わせると、各レコードに集計フィールドを追加できます。
  • filterパラメータを使うと、特定条件に一致するものだけをカウントできます。
  • distinct=Trueを指定すると、重複する値を除外してカウントできます。
  • 複数のCount関数を組み合わせることで、1つのクエリで複数の集計を行うことができます。

まとめ

  • annotate(Count())で関連データの件数を各レコードに追加できる
  • Count()の引数にリレーション名を指定する
  • distinct=Trueで重複を除いたカウントが可能
  • filter引数やQオブジェクトで条件付きカウントができる
  • order_by()と組み合わせてカウント順にソートできる