ORM

Django ORMのcount入門|データの件数を数える方法

Django ORM

Django ORMのcount入門
データの件数を数える方法

Django ORMでデータの件数を効率的に数える方法を解説します。count()、exists()、aggregateの使い分けを紹介します。

こんな人向けの記事です

  • Django ORMでデータの件数を取得したい人
  • count()とlen()の違いを知りたい人
  • 条件付きカウントやグループごとのカウントを学びたい人

Step 1count()の基本

QuerySetのcount()メソッドは、SQLのSELECT COUNT(*)を発行してデータの件数を取得します。

Python
# models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    category = models.CharField(max_length=50)
    price = models.IntegerField()
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return self.name
Python
# 全件数を取得
total = Product.objects.count()
print(f"全商品数: {total}")

# 条件を指定して件数を取得
active_count = Product.objects.filter(is_active=True).count()
print(f"アクティブ商品: {active_count}")

# カテゴリごとの件数
food_count = Product.objects.filter(category="食品").count()
print(f"食品: {food_count}")
実行結果
全商品数: 50
アクティブ商品: 42
食品: 15
Python
# count() vs len() の違い
# count(): SQLのCOUNTを使う(効率的)
count = Product.objects.filter(is_active=True).count()
# SQL: SELECT COUNT(*) FROM product WHERE is_active = True

# len(): 全データをPythonに読み込んでから数える(非効率)
count = len(Product.objects.filter(is_active=True))
# SQL: SELECT * FROM product WHERE is_active = True
# → 全レコードをメモリに読み込む
len()とcount()の使い分け
件数だけが必要な場合は必ずcount()を使いましょう。len()は全データをメモリに読み込むため、大量データでは非常に遅くなります。ただし、既にQuerySetを評価済み(ループ後など)の場合はlen()でも構いません。

Step 2exists()で存在チェック

データが1件以上存在するかどうかだけを知りたい場合は、exists()が最も効率的です。

Python
# exists(): 1件でも存在すればTrue(最も高速)
has_products = Product.objects.filter(category="食品").exists()
print(f"食品あり: {has_products}")

# BAD: count()で存在チェック(遅い)
if Product.objects.filter(category="食品").count() > 0:
    print("食品があります")

# BAD: bool()で存在チェック(全件取得してしまう)
if Product.objects.filter(category="食品"):
    print("食品があります")

# GOOD: exists()で存在チェック
if Product.objects.filter(category="食品").exists():
    print("食品があります")
実行結果
食品あり: True
食品があります
exists()の内部動作
exists()はSQLのSELECT 1 FROM ... LIMIT 1を発行します。1件見つかった時点で即座にTrueを返すため、count()よりも高速です。

Step 3条件付きカウント

Python
from django.db.models import Q

# 複数条件でのカウント
expensive_active = Product.objects.filter(
    is_active=True,
    price__gte=10000
).count()
print(f"高額アクティブ商品: {expensive_active}")

# OR条件でのカウント
food_or_drink = Product.objects.filter(
    Q(category="食品") | Q(category="飲料")
).count()
print(f"食品または飲料: {food_or_drink}")

# 除外してカウント
non_food = Product.objects.exclude(category="食品").count()
print(f"食品以外: {non_food}")

# NULLの件数
null_count = Product.objects.filter(category__isnull=True).count()
not_null_count = Product.objects.filter(category__isnull=False).count()

Step 4aggregateとannotateでの集計

グループごとのカウントや、複数のカウントを一度に取得する方法です。

Python
from django.db.models import Count, Q

# aggregate: 全体の集計値を辞書で取得
result = Product.objects.aggregate(
    total=Count("id"),
    active=Count("id", filter=Q(is_active=True)),
    inactive=Count("id", filter=Q(is_active=False)),
)
print(f"全体: {result['total']}")
print(f"アクティブ: {result['active']}")
print(f"非アクティブ: {result['inactive']}")
実行結果
全体: 50
アクティブ: 42
非アクティブ: 8
Python
# annotate: グループごとの集計(GROUP BY)
category_counts = Product.objects.values("category").annotate(
    count=Count("id")
).order_by("-count")

for item in category_counts:
    print(f"{item['category']}: {item['count']}件")

# 関連モデルのカウント
from .models import Department

departments = Department.objects.annotate(
    emp_count=Count("employees")
).order_by("-emp_count")
for dept in departments:
    print(f"{dept.name}: {dept.emp_count}人")

# 条件付きの関連カウント
departments = Department.objects.annotate(
    total=Count("employees"),
    active=Count("employees", filter=Q(employees__is_active=True)),
)
for dept in departments:
    print(f"{dept.name}: {dept.active}/{dept.total}人")
実行結果
食品: 15件
電子機器: 12件
衣料品: 10件
飲料: 8件
その他: 5件

Step 5パフォーマンスの注意点

Python
# 1. 同じQuerySetを複数回count()するとSQLも複数回発行される
# BAD:
total = Product.objects.count()          # SQL発行
active = Product.objects.filter(is_active=True).count()  # SQL発行
# → 2回SQLが発行される

# GOOD: aggregateで1回にまとめる
result = Product.objects.aggregate(
    total=Count("id"),
    active=Count("id", filter=Q(is_active=True)),
)
# → 1回のSQLで済む

# 2. テンプレートでのカウント
# BAD: テンプレート内で{{ products|length }}を使う
# GOOD: ビューでcount()してcontextに渡す
def product_list(request):
    products = Product.objects.filter(is_active=True)
    return render(request, "product/list.html", {
        "products": products,
        "total": products.count(),  # ビューでカウント
    })

# 3. distinct()でユニークカウント
unique_categories = Product.objects.values("category").distinct().count()
print(f"カテゴリ数: {unique_categories}")

まとめ

  • count()はSQLのCOUNTを使うためlen()より効率的
  • 存在チェックだけならexists()が最も高速
  • aggregate()で複数のカウントを1回のSQLにまとめられる
  • annotate()Count()でグループごとのカウントができる
  • Count(filter=Q(...))で条件付きカウントが可能