LoginSignup
1
3

More than 3 years have passed since last update.

自分自身のフィールド値を利用したannotate処理

Last updated at Posted at 2021-03-22

やりたいこと

class MyModel(models.Model):
    code = models.CharField(max_length=10)

こんな感じのモデルで、

image.png

こんな感じで同じコードが設定されている数をカウントしたい。

ナイーブな解法と問題点

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にメソッド追加すればできはするが

image.png

こんな感じでレコードの数だけ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でその値を返してあげると完成。

image.png

行数分発行されていたqueryが1つにまとまった。

発行されるsql文はこんな感じ。

image.png

外側のcodeを使って、内側で code_count を作っているのがわかる。


調べてもわからなかったのにコード書いたあとで下記のキーワードでググったら同じようなコードが見つかった。

django count outerref

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3