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


95

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

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

回答:


176

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

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/を参照してください

注意点:Countを使用する場合、Countに渡される値は集計に影響せず、最終的な値に与えられる名前だけに影響します。アグリゲーターは、Countに渡される値ではなく、値の一意の組み合わせ(上記のとおり)でグループ化します。次のクエリは同じです。

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

私にとってはとして機能しましたTransaction.objects.all().values('actor').annotate(total=Count('actor')).order_by('total')、django.db.modelsからCountをインポートすることを忘れないでください。ありがとう
イヴァンチョ2014年

3
注意してください:(Countそしておそらく他のアグリゲーター)を使用している場合、渡された値Countは集計に影響を与えません。アグリゲータは、values(上記のように)の一意の組み合わせでグループ化され、に渡される値ではグループ化されませんCount
kronosapiens 2015年

これをpostgresの検索結果クエリセットに使用してファセットを作成することもできます。
yekta 2016年

2
@kronosapiens少なくとも最近は影響があります(私はDjango 2.1.4を使用しています)。この例でtotalは、は指定された名前であり、SQLで使用されるカウントCOUNT('actor')はこの場合は重要ではありませんが、たとえばvalues('x', 'y').annotate(count=Count('x'))、取得するか、取得COUNT(x)しないCOUNT(*)COUNT(x, y)、試してみるだけです./manage.py shell
timdiels

33

@AlvaroがDjangoの直接の同等のfor 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()句は適切な結果を得るために必須です。順序付けを意図していない場合でも、スキップすることはできません。

さらに、高品質のコードの場合、がない場合でも、常にの.order_by()annotate()に句を置くことをお勧めしclass Meta: orderingます。このようなアプローチにより、ステートメントは将来に対応できるようになり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()この動作をクリアして適切な集計結果を得るには、この句を使用する必要があります。

参照:Djangoの公式ドキュメントのデフォルトの順序またはorder_by()との相互作用


3
.order_by()orderingメタで私を救った。
Babken Vardanyan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.