DjangoのORMでSQLのCASE WHEN文に相当する条件分岐を行うには、CaseとWhenを使用します。条件に応じて異なる値を返したり、条件ごとに計算を変えたりする処理をデータベース側で効率的に実行できます。
基本的な使い方
views.py
sales_exists = Sales.objects.filter(name=OuterRef('pk'))
person_model = Person.objects.annotate(
has_sales=Case(
When(Exists(sales_exists), then=Value('売り上げあり')),
default=Value('売り上げなし'),
)
)
print(person_model.values('name', 'has_sales'))
説明
Step 1Case/When式の基本
Djangoでは、Case/When式を使用してORM内で条件分岐を行うことができます。基本的な構文は以下の通りです:
from django.db.models import Case, When, Value Case( When(条件, then=条件に一致したときの値), default=条件に一致しなかったときの値, )
この式はSQL文の「CASE WHEN...THEN...ELSE...END」に相当し、データベースレベルで条件分岐を実行します。
Step 2基本的な使用例
例えば、Personモデルに紐づいているSalesの有無によって値を分岐させる場合:
from django.db.models import Case, When, Value, Count, CharField
# Personモデルに、Salesが存在するかどうかのフラグを追加
persons = Person.objects.annotate(
sales_count=Count('sales'),
has_sales=Case(
When(sales_count__gt=0, then=Value('売り上げあり')),
default=Value('売り上げなし'),
output_field=CharField()
)
)
上の例では、自身に紐づいたSalesが1つでもあれば「売り上げあり」、なければ「売り上げなし」がhas_salesフィールドに代入されています。
Step 3複数の条件分岐
When句を複数使って、複数の条件分岐を作ることもできます:
from django.db.models import Case, When, Value, IntegerField
# 売上金額に応じてランク付け
persons = Person.objects.annotate(
total_sales=Sum('sales__amount'),
sales_rank=Case(
When(total_sales__gte=1000000, then=Value(1)), # 100万以上はランク1
When(total_sales__gte=500000, then=Value(2)), # 50万以上はランク2
When(total_sales__gte=100000, then=Value(3)), # 10万以上はランク3
default=Value(4), # それ以下はランク4
output_field=IntegerField()
)
)
When句は上から順に評価され、最初に条件が一致したところのthen値が採用されます。
Step 4フィルタリングと組み合わせる
Case/When式の結果でフィルタリングすることもできます:
# ランク1とランク2の人だけを取得
top_performers = Person.objects.annotate(
total_sales=Sum('sales__amount'),
sales_rank=Case(
When(total_sales__gte=1000000, then=Value(1)),
When(total_sales__gte=500000, then=Value(2)),
When(total_sales__gte=100000, then=Value(3)),
default=Value(4),
output_field=IntegerField()
)
).filter(sales_rank__lte=2)
Step 5様々な条件式の例
Case/When式ではさまざまな条件を使用できます:
# 文字列フィールドに基づく条件
persons = Person.objects.annotate(
department_category=Case(
When(department__startswith='営業', then=Value('営業系')),
When(department__startswith='技術', then=Value('技術系')),
When(department__startswith='管理', then=Value('管理系')),
default=Value('その他'),
output_field=CharField()
)
)
# 複数フィールドの組み合わせ条件
from django.db.models import Q
persons = Person.objects.annotate(
status=Case(
When(Q(age__gte=60) & Q(years_of_service__gte=20), then=Value('定年退職対象')),
When(Q(age__gte=50) & Q(years_of_service__gte=15), then=Value('早期退職可能')),
default=Value('通常雇用'),
output_field=CharField()
)
)
Step 6実践的な使用例
views.pyでのCase/When式の使用例:
from django.shortcuts import render
from django.db.models import Case, When, Value, Sum, Count, CharField, IntegerField
from .models import Person, Sales
def sales_analysis(request):
# 売上実績に基づく分析
persons = Person.objects.annotate(
# 売上件数
sales_count=Count('sales'),
# 売上合計
total_sales=Sum('sales__amount'),
# 売上状況の分類
sales_status=Case(
When(sales_count=0, then=Value('未売上')),
When(sales_count__gte=10, then=Value('優良営業')),
default=Value('通常営業'),
output_field=CharField()
),
# 売上金額に基づくランク
sales_rank=Case(
When(total_sales__gte=1000000, then=Value('S')),
When(total_sales__gte=500000, then=Value('A')),
When(total_sales__gte=100000, then=Value('B')),
When(total_sales__gt=0, then=Value('C')),
default=Value('D'),
output_field=CharField()
),
# 売上達成率に応じたボーナス計算
bonus_percentage=Case(
When(total_sales__gte=2000000, then=Value(20)), # 200万以上は20%ボーナス
When(total_sales__gte=1000000, then=Value(15)), # 100万以上は15%ボーナス
When(total_sales__gte=500000, then=Value(10)), # 50万以上は10%ボーナス
When(total_sales__gt=0, then=Value(5)), # 売上あれば5%ボーナス
default=Value(0),
output_field=IntegerField()
)
).order_by('-total_sales')
return render(request, 'persons/sales_analysis.html', {
'persons': persons
})
テンプレートでの使用例(sales_analysis.html):
<h1>営業担当者売上分析</h1>
<table>
<tr>
<th>担当者名</th>
<th>売上件数</th>
<th>売上合計</th>
<th>売上状況</th>
<th>ランク</th>
<th>ボーナス率</th>
</tr>
{% for person in persons %}
<tr>
<td>{{ person.name }}</td>
<td>{{ person.sales_count }}件</td>
<td>{{ person.total_sales|default:0|floatformat:0 }}円</td>
<td>{{ person.sales_status }}</td>
<td>{{ person.sales_rank }}</td>
<td>{{ person.bonus_percentage }}%</td>
</tr>
{% endfor %}
</table>
重要ポイント:
- Case/When式はデータベースレベルで実行されるため、Pythonコードで条件分岐するよりも効率的です。
- output_fieldパラメータで、結果の型を指定する必要があります(CharFieldやIntegerFieldなど)。
- 条件が複雑な場合はQオブジェクトを使用して柔軟な条件式を作成できます。
- When句は上から順に評価されるため、条件の順序が重要です(最初に合致した条件のthen値が採用されます)。
- defaultを指定しないと、どの条件にも一致しない場合にNoneが返されます。
まとめ
Case/WhenでSQLのCASE WHEN文をORM内で表現できるWhen(条件, then=値)で条件と返す値を指定するdefault引数でどの条件にも合わない場合の値を指定できるoutput_fieldで結果のデータ型を明示的に指定するannotate()と組み合わせて条件別の計算フィールドを追加できるfilter()やorder_by()内でも使用可能