はじめに
Django(2.1.7)で集計処理( values().annotate()
)が Meta.ordering
を追加した途端に誤った結果を返すようになりました。
問題
例として以下の Foo
モデルを用意します。
ここで created_at
フィールドを Meta.ordering
に指定しておきます。
class Foo(models.Model): foo = models.CharField(max_length=255) # GROUP BYに指定するフィールド bar = models.IntegerField() # SUM()に指定するフィールド created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['created_at', ]
まず Foo
モデルに集計対象となるデータを追加します。
❯ python manage.py shell >>> from example.models import Foo >>> Foo.objects.create(foo="1", bar=10) >>> Foo.objects.create(foo="1", bar=10) >>> Foo.objects.create(foo="2", bar=10)
続けて、 foo
フィールドをGROUP BY項目として bar
フィールドを集計します。
しかし、結果として返される QuerySet
は集計されていません。
>>> from django.db.models import Sum >>> Foo.objects.all().values('foo').annotate(sum=Sum('bar')) <QuerySet [{'foo': '1', 'sum': 10}, {'foo': '1', 'sum': 10}, {'foo': '2', 'sum': 10}]>
原因
この事象については公式ドキュメントに記載があります。
Deprecated since version 2.2: Starting in Django 3.1, the ordering from a model’s Meta.ordering won’t be used in GROUP BY queries, such as .annotate().values(). Since Django 2.2, these queries issue a deprecation warning indicating to add an explicit order_by() to the queryset to silence the warning.
Django 2では Meta.ordering
が values().annotate()
の結果に影響するため明示的に order_by()
を指定する必要があります。
なお、Django 2.2からは警告が出力され、Django 3.1(2020/8リリース予定)では Meta.ordering
が無視されるように修正されるとのことです。
対策
order_by()
を追加し、期待通り集計できることを確認します。
>>> Foo.objects.all().values('foo').annotate(sum=Sum('bar')).order_by('foo') <QuerySet [{'foo': '1', 'sum': 20}, {'foo': '2', 'sum': 10}]> >>> Foo.objects.all().values('foo').annotate(sum=Sum('bar')).order_by('bar') <QuerySet [{'foo': '1', 'sum': 20}, {'foo': '2', 'sum': 10}]> >>> Foo.objects.all().values('foo').annotate(sum=Sum('bar')).order_by('created_at') # クエリに含まないフィールドの指定では不正な結果を返す <QuerySet [{'foo': '1', 'sum': 10}, {'foo': '1', 'sum': 10}, {'foo': '2', 'sum': 10}]>
なお、 Meta.ordering
で例えば foo
フィールドを指定していた場合には問題なく集計できます。
values().annotate()
で構成するクエリに含まないフィールドで order_by
が適用されてしまうことが問題のようです。
まとめ
Django 2を使用していて集計処理がうまくいかない場合は Meta.ordering
を疑ってみると良いかもしれません。