web-dev-qa-db-ja.com

DjangoでSELECT COUNT(*)GROUP BYとORDER BYを行う方法は?

システムを通過するすべてのイベントを追跡するためにトランザクションモデルを使用しています

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField() 
    ......

システム内の上位5つのアクターを取得するにはどうすればよいですか?

SQLでは、基本的に

SELECT actor, COUNT(*) as total 
FROM Transaction 
GROUP BY actor 
ORDER BY total DESC
71
ninja

ドキュメントによると、次を使用する必要があります。

from Django.db.models import Count
Transaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')

values():「グループ化」に使用される列を指定します

Djangoのドキュメント:

「values()句を使用して結果セットに返される列を制約する場合、注釈を評価する方法はわずかに異なります。元のQuerySetの各結果に対して注釈付きの結果を返す代わりに、元の結果はvalues()句で指定されたフィールドの一意の組み合わせに」

annotate():グループ化された値に対する操作を指定します

Djangoのドキュメント:

集計値を生成する2番目の方法は、QuerySetの各オブジェクトの独立した集計を生成することです。たとえば、書籍のリストを取得する場合、各書籍に貢献した著者の数を知りたい場合があります。各本は著者と多対多の関係にあります。 QuerySet内の各本のこの関係を要約します。

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

句による順序は自明です。

要約すると、作成者のクエリセットを生成してグループ化し、注釈を追加し(返される値に追加のフィールドが追加されます)、最後に、この値で並べ替えます

詳細については、 https://docs.djangoproject.com/en/dev/topics/db/aggregation/ を参照してください

133
Alvaro

@AlvaroがDjangoのGROUP BYステートメントに直接相当するものに答えたように:

SELECT actor, COUNT(*) AS total 
FROM Transaction 
GROUP BY actor

次のようにvalues()およびannotate()メソッドを使用します。

Transaction.objects.values('actor').annotate(total=Count('actor')).order_by()

ただし、もう1つ指摘する必要があります。

モデルにclass Metaで定義されたデフォルトの順序がある場合、.order_by()句は適切な結果のために必須です。順序付けが意図されていない場合でもスキップします。

さらに、高品質のコードでは、class Meta: orderingがない場合でも、.order_by()の後に常にannotate()句を配置することをお勧めします。このようなアプローチにより、ステートメントは将来にわたって使用可能になります。class Meta: orderingの将来の変更に関係なく、意図したとおりに機能します。


例を挙げましょう。モデルに以下があった場合:

class Transaction(models.Model):
    actor = models.ForeignKey(User, related_name="actor")
    acted = models.ForeignKey(User, related_name="acted", null=True, blank=True)
    action_id = models.IntegerField()

    class Meta:
        ordering = ['id']

次に、そのようなアプローチはうまくいきません:

Transaction.objects.values('actor').annotate(total=Count('actor'))

これは、DjangoがGROUP BYのすべてのフィールドで追加のclass Meta: orderingを実行するためです。

クエリを印刷する場合:

>>> print Transaction.objects.values('actor').annotate(total=Count('actor')).query
  SELECT "Transaction"."actor_id", COUNT("Transaction"."actor_id") AS "total"
  FROM "Transaction"
  GROUP BY "Transaction"."actor_id", "Transaction"."id"

集計が意図したとおりに機能しないことは明らかであるため、.order_by()句を使用してこの動作をクリアし、適切な集計結果を取得する必要があります。

参照: デフォルトの順序付けまたはorder_by()との対話 公式Djangoドキュメント。

25
Krzysiek