やりたいこと
class MyModel(models.Model):
code = models.CharField(max_length=10)
こんな感じのモデルで、
こんな感じで同じコードが設定されている数をカウントしたい。
ナイーブな解法と問題点
class MyModelAdmin(admin.ModelAdmin):
list_display = (
'id',
'code',
'code_count',
)
def code_count(self, obj):
return MyModel.objects.filter(code=obj.code).count()
adminやmodelにメソッド追加すればできはするが
こんな感じでレコードの数だけsqlが発行される。
(n+1問題が発生する)
SubqueryとOuterRefを使用したannotate
SubqueryとOuterRefを使って自分自身のcodeを使ったsubqueryを発行すればできる。
from django.contrib import admin
from django.db.models import Count, OuterRef, Subquery
from .models import MyModel
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_display = (
'id',
'code',
'code_count',
)
def get_queryset(self, request):
sub_query = MyModel.objects.filter(
code=OuterRef('code')
).values('code').annotate(count=Count('code')).values('count')
return MyModel.objects.annotate(code_count=Subquery(sub_query))
def code_count(self, obj):
return obj.code_count
code=OuterRef('code')
で外側にある自分自身のcodeで絞る。
codeの数でカウントを取りたいので .values('code')
と annotate(Count('code'))
でGroup化する。
sub_queryの結果は1つじゃないといけないので、 最後に .values('count')
で code
のカウント数だけ返す。
あとは code_count
という名前で先程作ったsub_queryをannotateしてあげる。
これで objectに code_count
というcode件数のattributeがつくので、methodでその値を返してあげると完成。
行数分発行されていたqueryが1つにまとまった。
発行されるsql文はこんな感じ。
外側のcodeを使って、内側で code_count
を作っているのがわかる。
調べてもわからなかったのにコード書いたあとで下記のキーワードでググったら同じようなコードが見つかった。
django count outerref