集約—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.0.x/topics/db/aggregation
移動先:案内検索

集約

Djangoのデータベース抽象化API のトピックガイドでは、個々のオブジェクトを作成、取得、更新、削除するDjangoクエリを使用する方法について説明しています。 ただし、オブジェクトのコレクションを要約または集約することによって導出された値を取得する必要がある場合があります。 このトピックガイドでは、Djangoクエリを使用して集計値を生成および返す方法について説明します。

このガイドでは、次のモデルを参照します。 これらのモデルは、一連のオンライン書店の在庫を追跡するために使用されます。

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()

class Publisher(models.Model):
    name = models.CharField(max_length=300)

class Book(models.Model):
    name = models.CharField(max_length=300)
    pages = models.IntegerField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    rating = models.FloatField()
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    pubdate = models.DateField()

class Store(models.Model):
    name = models.CharField(max_length=300)
    books = models.ManyToManyField(Book)

カンニングペーパー

急いで? 上記のモデルを想定して、一般的な集計クエリを実行する方法は次のとおりです。

# Total number of books.
>>> Book.objects.count()
2452

# Total number of books with publisher=BaloneyPress
>>> Book.objects.filter(publisher__name='BaloneyPress').count()
73

# Average price across all books.
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}

# Max price across all books.
>>> from django.db.models import Max
>>> Book.objects.all().aggregate(Max('price'))
{'price__max': Decimal('81.20')}

# Difference between the highest priced book and the average price of all books.
>>> from django.db.models import FloatField
>>> Book.objects.aggregate(
...     price_diff=Max('price', output_field=FloatField()) - Avg('price'))
{'price_diff': 46.85}

# All the following queries involve traversing the Book<->Publisher
# foreign key relationship backwards.

# Each publisher, each with a count of books as a "num_books" attribute.
>>> from django.db.models import Count
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
<QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
>>> pubs[0].num_books
73

# Each publisher, with a separate count of books with a rating above and below 5
>>> from django.db.models import Q
>>> above_5 = Count('book', filter=Q(book__rating__gt=5))
>>> below_5 = Count('book', filter=Q(book__rating__lte=5))
>>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
>>> pubs[0].above_5
23
>>> pubs[0].below_5
12

# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books
1323

QuerySetを介した集計の生成

Djangoには、集計を生成する2つの方法があります。 最初の方法は、QuerySet全体にわたって要約値を生成することです。 たとえば、販売可能なすべての本の平均価格を計算したいとします。 Djangoのクエリ構文は、すべての本のセットを記述するための手段を提供します。

>>> Book.objects.all()

必要なのは、このQuerySetに属するオブジェクトの要約値を計算する方法です。 これは、aggregate()句をQuerySetに追加することによって行われます。

>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}

この例では、all()は冗長であるため、次のように簡略化できます。

>>> Book.objects.aggregate(Avg('price'))
{'price__avg': 34.35}

aggregate()句の引数は、計算する集計値(この場合は、Bookモデルのpriceフィールドの平均)を記述します。 使用可能な集計関数のリストは、 QuerySetリファレンスにあります。

aggregate()は、QuerySetの終端句であり、呼び出されると、名前と値のペアの辞書を返します。 名前は集計値の識別子です。 値は計算された集計です。 名前は、フィールドの名前と集計関数から自動的に生成されます。 集計値の名前を手動で指定する場合は、集計句を指定するときにその名前を指定することで指定できます。

>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}

複数の集計を生成する場合は、aggregate()句に別の引数を追加します。 したがって、すべての本の最高価格と最低価格も知りたい場合は、次のクエリを発行します。

>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

QuerySetの各アイテムの集計を生成する

サマリー値を生成する2番目の方法は、 QuerySet 内のオブジェクトごとに独立したサマリーを生成することです。 たとえば、本のリストを取得する場合、各本に何人の著者が寄稿したかを知りたい場合があります。 各本は著者と多対多の関係を持っています。 QuerySetの各本についてこの関係を要約したいと思います。

オブジェクトごとの要約は、 annotate()句を使用して生成できます。 annotate()句が指定されている場合、QuerySet内の各オブジェクトには指定された値の注釈が付けられます。

これらのアノテーションの構文は、 Aggregate()句に使用される構文と同じです。 annotate()の各引数は、計算される集計を記述します。 たとえば、著者の数で本に注釈を付けるには、次のようにします。

# Build an annotated queryset
>>> from django.db.models import Count
>>> q = Book.objects.annotate(Count('authors'))
# Interrogate the first object in the queryset
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# Interrogate the second object in the queryset
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1

aggregate()と同様に、アノテーションの名前は、集計関数の名前と集計されるフィールドの名前から自動的に取得されます。 注釈を指定するときにエイリアスを指定することで、このデフォルト名を上書きできます。

>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1

aggregate()とは異なり、annotate()ではなく端末句です。 annotate()句の出力はQuerySetです。 このQuerySetは、filter()order_by()、またはannotate()への追加呼び出しなど、他のQuerySet操作を使用して変更できます。

複数の集計を組み合わせる

複数の集計をannotate()意思 :ticket: `間違った結果を出す<10060>` サブクエリの代わりに結合が使用されるため:

>>> book = Book.objects.first()
>>> book.authors.count()
2
>>> book.store_set.count()
3
>>> q = Book.objects.annotate(Count('authors'), Count('store'))
>>> q[0].authors__count
6
>>> q[0].store__count
6

ほとんどのアグリゲートでは、この問題を回避する方法はありませんが、 Count アグリゲートには次のようなdistinctパラメーターがあります。

>>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store', distinct=True))
>>> q[0].authors__count
2
>>> q[0].store__count
3

疑わしい場合は、SQLクエリを調べてください。

クエリで何が起こるかを理解するために、QuerySetqueryプロパティを調べることを検討してください。


結合と集約

これまで、クエリ対象のモデルに属するフィールドの集計を処理してきました。 ただし、集計する値が、クエリしているモデルに関連するモデルに属する場合があります。

集計関数で集計するフィールドを指定する場合、Djangoでは、フィルターで関連フィールドを参照するときに使用されるのと同じ二重アンダースコア表記を使用できます。 Djangoは、関連する値を取得して集計するために必要なテーブル結合を処理します。

たとえば、各店舗で提供されている本の価格帯を見つけるには、注釈を使用できます。

>>> from django.db.models import Max, Min
>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))

これにより、DjangoはStoreモデルを取得し、Bookモデルと(多対多の関係を介して)結合し、本モデルの価格フィールドで集計して最小値と最大値。

aggregate()句にも同じルールが適用されます。 いずれかの店舗で販売されている本の最低価格と最高価格を知りたい場合は、次の集計を使用できます。

>>> Store.objects.aggregate(min_price=Min('books__price'), max_price=Max('books__price'))

結合チェーンは、必要なだけ深くすることができます。 たとえば、販売可能な本の最年少著者の年齢を抽出するには、次のクエリを発行できます。

>>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))

関係を逆方向にたどる

リレーションシップにまたがるルックアップと同様に、クエリ対象のモデルまたはモデルのフィールドの集計と注釈には、「逆」リレーションシップのトラバースを含めることができます。 ここでも、関連するモデルの小文字の名前と二重下線が使用されています。

たとえば、それぞれの書籍ストックカウンターの合計で注釈が付けられたすべての出版社に問い合わせることができます('book'を使用してPublisher-> Book逆外部キーホップを指定する方法に注意してください) )::

>>> from django.db.models import Avg, Count, Min, Sum
>>> Publisher.objects.annotate(Count('book'))

(結果のQuerySetのすべてのPublisherには、book__countという追加の属性があります。)

また、すべての出版社が管理しているものの中で最も古い本を求めることもできます。

>>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))

(結果の辞書には、'oldest_pubdate'というキーがあります。 そのようなエイリアスが指定されていない場合は、かなり長い'book__pubdate__min'になります。)

これは外部キーだけに当てはまるわけではありません。 また、多対多の関係でも機能します。 たとえば、著者が(共)執筆したすべての本を考慮した総ページ数で注釈を付けて、すべての著者に質問できます('book'を使用してAuthorを指定する方法に注意してください- > Book多対多ホップを逆にする):

>>> Author.objects.annotate(total_pages=Sum('book__pages'))

(結果のQuerySetのすべてのAuthorには、total_pagesという追加の属性があります。 そのようなエイリアスが指定されていない場合は、かなり長いbook__pages__sumになります。)

または、私たちが登録している著者によって書かれたすべての本の平均評価を求めます。

>>> Author.objects.aggregate(average_rating=Avg('book__rating'))

(結果の辞書には、'average_rating'というキーがあります。 そのようなエイリアスが指定されていない場合は、かなり長い'book__rating__avg'になります。)


集計およびその他のQuerySet句

filter()およびexclude()

集計はフィルターに参加することもできます。 通常のモデルフィールドに適用されるfilter()(またはexclude())は、集計の対象となるオブジェクトを制約する効果があります。

annotate()句とともに使用すると、フィルターには、注釈が計算されるオブジェクトを制約する効果があります。 たとえば、次のクエリを使用して、タイトルが「Django」で始まるすべての書籍の注釈付きリストを生成できます。

>>> from django.db.models import Avg, Count
>>> Book.objects.filter(name__startswith="Django").annotate(num_authors=Count('authors'))

aggregate()句とともに使用すると、フィルターには、集計が計算されるオブジェクトを制約する効果があります。 たとえば、次のクエリを使用して、「Django」で始まるタイトルのすべての本の平均価格を生成できます。

>>> Book.objects.filter(name__startswith="Django").aggregate(Avg('price'))

注釈のフィルタリング

注釈付きの値もフィルタリングできます。 アノテーションのエイリアスは、他のモデルフィールドと同じように、filter()およびexclude()句で使用できます。

たとえば、複数の著者がいる本のリストを生成するには、次のクエリを発行できます。

>>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)

このクエリは、注釈付きの結果セットを生成し、その注釈に基づいてフィルターを生成します。

2つの別々のフィルターを持つ2つの注釈が必要な場合は、filter引数を任意の集計で使用できます。 たとえば、評価の高い本の数を含む著者のリストを生成するには、次のようにします。

>>> highly_rated = Count('book', filter=Q(book__rating__gte=7))
>>> Author.objects.annotate(num_books=Count('book'), highly_rated_books=highly_rated)

結果セットの各Authorには、num_booksおよびhighly_rated_books属性があります。

filterQuerySet.filter()のどちらかを選択します

filter引数を単一の注釈または集計で使用することは避けてください。 QuerySet.filter()を使用して行を除外する方が効率的です。 集計filter引数は、条件が異なる同じ関係で2つ以上の集計を使用する場合にのみ役立ちます。


annotate()およびfilter()句の順序

annotate()句とfilter()句の両方を含む複雑なクエリを開発する場合は、QuerySetに句が適用される順序に特に注意してください。

annotate()句がクエリに適用されると、アノテーションは、アノテーションが要求されるポイントまでのクエリの状態に対して計算されます。 これの実際的な意味は、filter()annotate()は可換演算ではないということです。

与えられた:

  • 出版社Aには、評価4と5の2冊の本があります。
  • 出版社Bには、評価1と4の2冊の本があります。
  • 出版社Cには、評価1の本が1冊あります。

Countアグリゲートの例を次に示します。

>>> a, b = Publisher.objects.annotate(num_books=Count('book', distinct=True)).filter(book__rating__gt=3.0)
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 2)

>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(num_books=Count('book'))
>>> a, a.num_books
(<Publisher: A>, 2)
>>> b, b.num_books
(<Publisher: B>, 1)

どちらのクエリも、評価が3.0を超える本を少なくとも1冊持っている出版社のリストを返すため、出版社Cは除外されます。

最初のクエリでは、注釈がフィルターの前にあるため、フィルターは注釈に影響を与えません。 クエリのバグを回避するには、distinct=Trueが必要です。

2番目のクエリは、各出版社の評価が3.0を超える本の数をカウントします。 フィルタは注釈の前にあるため、フィルタは注釈の計算時に考慮されるオブジェクトを制約します。

Avgアグリゲートを使用した別の例を次に示します。

>>> a, b = Publisher.objects.annotate(avg_rating=Avg('book__rating')).filter(book__rating__gt=3.0)
>>> a, a.avg_rating
(<Publisher: A>, 4.5)  # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 2.5)  # (1+4)/2

>>> a, b = Publisher.objects.filter(book__rating__gt=3.0).annotate(avg_rating=Avg('book__rating'))
>>> a, a.avg_rating
(<Publisher: A>, 4.5)  # (5+4)/2
>>> b, b.avg_rating
(<Publisher: B>, 4.0)  # 4/1 (book with rating 1 excluded)

最初のクエリでは、評価が3.0を超える本が少なくとも1冊ある出版社のすべての出版社の本の平均評価を求めています。 2番目のクエリは、3.0を超える評価のみについて、出版社の本の評価の平均を求めます。

ORMが複雑なクエリセットをSQLクエリに変換する方法を直感的に理解するのは難しいため、疑わしい場合は、str(queryset.query)を使用してSQLを検査し、多くのテストを記述してください。


order_by()

注釈は、注文の基礎として使用できます。 order_by()句を定義すると、指定した集計は、クエリのannotate()句の一部として定義された任意のエイリアスを参照できます。

たとえば、QuerySetの本を、その本に寄稿した著者の数で注文するには、次のクエリを使用できます。

>>> Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

values()

通常、注釈はオブジェクトごとに生成されます。注釈付きのQuerySetは、元のQuerySetのオブジェクトごとに1つの結果を返します。 ただし、values()句を使用して結果セットに返される列を制約する場合、注釈を評価する方法は少し異なります。 元のQuerySetの各結果に対して注釈付きの結果を返す代わりに、元の結果はvalues()句で指定されたフィールドの一意の組み合わせに従ってグループ化されます。 次に、一意のグループごとに注釈が付けられます。 注釈は、グループのすべてのメンバーに対して計算されます。

たとえば、各著者が書いた本の平均評価を見つけようとする著者クエリについて考えてみます。

>>> Author.objects.annotate(average_rating=Avg('book__rating'))

これにより、データベース内の著者ごとに1つの結果が返され、平均的な書籍の評価が注釈として付けられます。

ただし、values()句を使用すると、結果が少し異なります。

>>> Author.objects.values('name').annotate(average_rating=Avg('book__rating'))

この例では、作成者は名前でグループ化されるため、一意の作成者名ごとに注釈付きの結果のみが表示されます。 これは、同じ名前の2人の作成者がいる場合、それらの結果がクエリの出力で1つの結果にマージされることを意味します。 平均は、両方の著者によって書かれた本の平均として計算されます。

annotate()およびvalues()句の順序

filter()句と同様に、annotate()句とvalues()句がクエリに適用される順序は重要です。 values()句がannotate()の前にある場合、注釈はvalues()句で記述されたグループ化を使用して計算されます。

ただし、annotate()句がvalues()句の前にある場合、注釈はクエリセット全体に対して生成されます。 この場合、values()句は、出力時に生成されるフィールドのみを制約します。

たとえば、前の例のvalues()句とannotate()句の順序を逆にすると、次のようになります。

>>> Author.objects.annotate(average_rating=Avg('book__rating')).values('name', 'average_rating')

これにより、作成者ごとに1つの一意の結果が得られます。 ただし、出力データには作成者の名前とaverage_ratingアノテーションのみが返されます。

average_ratingは、返される値のリストに明示的に含まれていることにも注意してください。 これは、values()およびannotate()句の順序のために必要です。

values()句がannotate()句の前にある場合、注釈は結果セットに自動的に追加されます。 ただし、values()句がannotate()句の後に適用される場合は、集計列を明示的に含める必要があります。


デフォルトの順序またはorder_by()との相互作用

バージョン2.2以降非推奨: Django 3.1以降、モデルのMeta.orderingからの順序は、.annotate().values()などのGROUP BYクエリでは使用されません。 Django 2.2以降、これらのクエリは非推奨の警告を発行し、クエリセットに明示的なorder_by()を追加して警告を消音するように指示します。


クエリセットのorder_by()部分に記載されている(またはモデルのデフォルトの順序で使用されている)フィールドは、 [で特に指定されていない場合でも、出力データを選択するときに使用されます。 X210X]呼び出し。 これらの追加フィールドは、「いいね」の結果をグループ化するために使用され、それ以外の場合は同一の結果行が別々に見えるようにすることができます。 これは、特に物事を数えるときに現れます。

例として、次のようなモデルがあるとします。

from django.db import models

class Item(models.Model):
    name = models.CharField(max_length=10)
    data = models.IntegerField()

    class Meta:
        ordering = ["name"]

ここで重要なのは、nameフィールドのデフォルトの順序です。 それぞれの異なるdata値が表示される回数をカウントする場合は、次のことを試してください。

# Warning: not quite correct!
Item.objects.values("data").annotate(Count("id"))

Itemオブジェクトを共通のdata値でグループ化し、各グループのid値の数をカウントします。 それが完全に機能しないことを除いて。 nameによるデフォルトの順序もグループ化に影響するため、このクエリは個別の(data, name)ペアでグループ化されますが、これは目的ではありません。 代わりに、次のクエリセットを作成する必要があります。

Item.objects.values("data").annotate(Count("id")).order_by()

…クエリ内の順序をクリアします。 たとえば、dataで注文しても、有害な影響はありません。これは、クエリですでに役割を果たしているためです。

この動作は、 distinct()のクエリセットのドキュメントに記載されている動作と同じであり、一般的なルールも同じです。通常、結果に余分な列が含まれることはないため、順序を明確にします。 、または少なくとも、values()呼び出しで選択したフィールドのみに制限されていることを確認してください。

ノート

Djangoが無関係な列を削除しない理由を合理的に尋ねるかもしれません。 主な理由は、distinct()は、[ X217X] APIの安定性ポリシー)。


注釈の集約

注釈の結果に基づいて集計を生成することもできます。 aggregate()句を定義すると、指定した集計は、クエリのannotate()句の一部として定義された任意のエイリアスを参照できます。

たとえば、本ごとの平均著者数を計算する場合は、最初に一連の本に著者数で注釈を付け、次に注釈フィールドを参照してその著者数を集計します。

>>> from django.db.models import Avg, Count
>>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors'))
{'num_authors__avg': 1.66}