djangoでGROUP BYとしてクエリする方法は?


333

モデルをクエリします。

Members.objects.all()

そしてそれは返します:

Eric, Salesman, X-Shop
Freddie, Manager, X2-Shop
Teddy, Salesman, X2-Shop
Sean, Manager, X2-Shop

私が欲しいのはgroup_by、データベースにクエリを実行するためのDjangoの最良の方法を知ることです。

Members.objects.all().group_by('designation')

もちろん、これは機能しません。でいくつかのトリックを実行できることはわかっていますがdjango/db/models/query.py、パッチを適用せずにそれを実行する方法を知りたいだけです。

回答:


484

集計を行う場合は、ORMの集計機能を使用できます。

from django.db.models import Count
Members.objects.values('designation').annotate(dcount=Count('designation'))

これにより、次のようなクエリが生成されます。

SELECT designation, COUNT(designation) AS dcount
FROM members GROUP BY designation

出力は次の形式になります

[{'designation': 'Salesman', 'dcount': 2}, 
 {'designation': 'Manager', 'dcount': 2}]

6
@ハリー:連鎖できます。次のようなものMembers.objects.filter(date=some_date).values('designation').annotate(dcount=Count('designation'))
Eli

57
私は質問があります、このクエリは指定とdcountのみを返します。テーブルの他の値も取得したい場合はどうなりますか?
AJ

19
並べ替えが指定以外のフィールドである場合、並べ替えをリセットしないと機能しません。stackoverflow.com/a/1341667/202137を
Gidgidonihah 2014

12
@Gidgidonihah真のは、例が読むべきMembers.objects.order_by('disignation').values('designation').annotate(dcount=Count('designation'))
bjunix

7
私は質問があります、このクエリは指定とdcountのみを返します。テーブルの他の値も取得したい場合はどうなりますか?
Yann叶

55

簡単な解決策ですが、適切な方法ではありませんが、生のSQLを使用します

results = Members.objects.raw('SELECT * FROM myapp_members GROUP BY designation')

別の解決策は、group_byプロパティを使用することです:

query = Members.objects.all().query
query.group_by = ['designation']
results = QuerySet(query=query, model=Members)

これで、results変数を反復処理して結果を取得できます。group_by文書化されておらず、Djangoの将来のバージョンで変更される可能性があることに注意してください。

そして...なぜあなたは使いたいのgroup_byですか?集約を使用しない場合は、を使用order_byして同様の結果を得ることができます。


order_byを使用して行う方法を教えていただけますか?
単に厳しい2009年

2
こんにちは、集約を使用していない場合は、order_byを使用してgroup_byをエミュレートし、不要なエントリを削除できます。もちろん、これはエミュレーションであり、大量のデータを使用しない場合にのみ使用できます。彼は集約について語っていなかったので、それが解決策になるのではないかと思いました。
マイケル

ちょっとこれは素晴らしいです-あなたが使用EXECUTE_SQLにそれが仕事に表示されませんどのように説明してくださいすることができます。..
rh0dium

8
これはDjango 1.9では動作しないことに注意してください。 stackoverflow.com/questions/35558120/...
grokpot

1
これは一種のハックっぽいORMの使い方です。古いクエリセットを手動で渡してインスタンス化する必要はありません。
Ian Kirkpatrick

32

regroupテンプレートタグを使用して、属性でグループ化することもできます。ドキュメントから:

cities = [
    {'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'},
    {'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'},
    {'name': 'New York', 'population': '20,000,000', 'country': 'USA'},
    {'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'},
    {'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'},
]

...

{% regroup cities by country as country_list %}

<ul>
    {% for country in country_list %}
        <li>{{ country.grouper }}
            <ul>
            {% for city in country.list %}
                <li>{{ city.name }}: {{ city.population }}</li>
            {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

このように見えます:

  • インド
    • ムンバイ:19,000,000
    • カルカッタ:15,000,000
  • 米国
    • ニューヨーク:20,000,000
    • シカゴ:7,000,000
  • 日本
    • 東京:33,000,000

それはQuerySet私が信じているものにも働きます。

ソース:https : //docs.djangoproject.com/en/2.1/ref/templates/builtins/#regroup

編集:辞書のリストがキーでソートされていない場合、regroupタグ期待どおりに機能しないことに注意してください。それは繰り返し動作します。そのため、regroupタグに渡す前に、ハタのキーでリスト(またはクエリセット)を並べ替えます。


1
これは完璧です!私はこれを行う簡単な方法をたくさん検索しました。そして、それはクエリセットでも機能し、それが私がそれを使用した方法です。
CarmenA

1
これは、データベースから大きなデータセットを読み取って、集約された値を使用するだけの場合は完全に誤りです。
SławomirLenart

@SławomirLenart確かに、これは単純なDBクエリほど効率的ではないかもしれません。しかし、単純なユースケースの場合は、これが良い解決策になる可能性があります。
イノスティア

これは、結果がテンプレートに表示されている場合に機能します。ただし、JsonResponseまたはその他の間接応答の場合。このソリューションは機能しません。
Willy satrio nugroho

1
あなたはビューでそれをやってみたかった場合@Willysatrionugroho、例えば、stackoverflow.com/questions/477820/...はあなたのために働くかもしれない
inostia

7

このスニペットに例示されているように、カスタムSQLを実行する必要があります。

サブクエリによるカスタムSQL

または、オンラインのDjangoドキュメントに示されているカスタムマネージャーで:

追加のManagerメソッドの追加


1
一種の往復ソリューション。もし私がそれを拡張して使っていたら、私はそれを使用したでしょう。しかし、ここでは、指定ごとのメンバー数がすべて必要です。
単に厳しい2009年

問題ない。1.1アグリゲーション機能について言及することを考えましたが、リリースバージョンを使用していることを前提としました:)
Van Gale

DjangoのORMの弱点を示す生のクエリを使用することがすべてです。
SławomirLenart

5

Djangoは無料のgroup byクエリをサポートしていません。私は非常に悪い方法でそれを学びました。ORMは、カスタムSQLを使用せずに、やりたいことなどをサポートするようには設計されていません。次の制限があります。

  • RAW sql(つまり、MyModel.objects.raw())
  • cr.execute 文(および結果の手作りの解析)。
  • .annotate() (group by文は、.annotate()の子モデルで実行されます(例:lines_count = Count( 'lines'))の集計など)。

クエリセットの上にqsあなたが呼び出すことができますqs.query.group_by = ['field1', 'field2', ...]が、あなたはクエリはあなたが編集しているか知っているし、それが動作し、クエリセットオブジェクトの内部を破壊しないという保証がありませんならば、それは危険です。その上、これは内部(文書化されていない)APIであり、将来のDjangoバージョンとの互換性がなくなるリスクを冒すことなく、直接アクセスしないでください。


実際、無料のgroup-byだけに制限されているわけではないので、Django ORMの代わりにSQLAlchemyを試してください。
SławomirLenart

5

Djangoモデルをグループ化して、結果のQuerySetを操作できるようにするモジュールがあります。https//github.com/kako-nawao/django-group-by

例えば:

from django_group_by import GroupByMixin

class BookQuerySet(QuerySet, GroupByMixin):
    pass

class Book(Model):
    title = TextField(...)
    author = ForeignKey(User, ...)
    shop = ForeignKey(Shop, ...)
    price = DecimalField(...)

class GroupedBookListView(PaginationMixin, ListView):
    template_name = 'book/books.html'
    model = Book
    paginate_by = 100

    def get_queryset(self):
        return Book.objects.group_by('title', 'author').annotate(
            shop_count=Count('shop'), price_avg=Avg('price')).order_by(
            'name', 'author').distinct()

    def get_context_data(self, **kwargs):
        return super().get_context_data(total_count=self.get_queryset().count(), **kwargs)

「book / books.html」

<ul>
{% for book in object_list %}
    <li>
        <h2>{{ book.title }}</td>
        <p>{{ book.author.last_name }}, {{ book.author.first_name }}</p>
        <p>{{ book.shop_count }}</p>
        <p>{{ book.price_avg }}</p>
    </li>
{% endfor %}
</ul>

annotate/ aggregate基本的なDjangoクエリとの違いは、関連フィールドの属性の使用ですbook.author.last_name

グループ化されているインスタンスのPKが必要な場合は、次の注釈を追加します。

.annotate(pks=ArrayAgg('id'))

注:ArrayAggDjango 1.9以降で利用可能なPostgres固有の関数です:https : //docs.djangoproject.com/en/1.10/ref/contrib/postgres/aggregates/#arrayagg


このdjango-group-byは、valuesメソッドの代替です。それは私が思う別の目的のためです。
LShi 2017

1
@LShiもちろん値の代わりにはなりません。valuesSQLいるselect間、group_bySQLがあるgroup by(名前が示すように...)。なぜ反対票か。このようなコードを本番環境で使用して、複雑なgroup_byステートメントを実装しています。
Risadinha 2017

そのドキュメントは、group_by「値のメソッドとほとんど同じように動作しますが、1つの違いがあります...」GROUP BYと述べています。このドキュメントはSQLについては触れていませんGROUP BY。私は誰かがこれを明らかにしたときに反対票を取り下げますが、その文書は本当に誤解を招くものです。
LShi 2017

のドキュメントをvalues読んだ後、valuesそれ自体がGROUP BYのように機能することを忘れてしまいました。それは私のせいです。不十分なitertools.groupby場合valuesは、このdjango-group-byよりも簡単に使用できると思います。
LShi 2017

1
データベースからすべてをフェッチするかどうかに関係なくgroup by、単純なvalues呼び出しで上記のことを行うことは不可能annotateです。itertools.groupby小さなデータセットでは機能するが、ページングしたい数千のデータセットでは機能しないという提案。もちろん、その時点でとにかく、準備された(既にグループ化された)データを含む特別な検索インデックスについて考える必要があります。
リサディーニャ

0

文書は、あなたがグループにクエリセットを値を使用することができることを言います。

class Travel(models.Model):
    interest = models.ForeignKey(Interest)
    user = models.ForeignKey(User)
    time = models.DateTimeField(auto_now_add=True)

# Find the travel and group by the interest:

>>> Travel.objects.values('interest').annotate(Count('user'))
<QuerySet [{'interest': 5, 'user__count': 2}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited for 2 times, 
# and the interest(id=6) had only been visited for 1 time.

>>> Travel.objects.values('interest').annotate(Count('user', distinct=True)) 
<QuerySet [{'interest': 5, 'user__count': 1}, {'interest': 6, 'user__count': 1}]>
# the interest(id=5) had been visited by only one person (but this person had 
#  visited the interest for 2 times

次のコードを使用して、すべての本を検索し、名前でグループ化できます。

Book.objects.values('name').annotate(Count('id')).order_by() # ensure you add the order_by()

ここでチートシートを見ることができます


-1

私があなたが使うことができると誤解していないなら、whatever-query-set .group_by = [' field ']


8
少なくともDjango 1.6では、これは当てはまりません。「QuerySet」オブジェクトには属性「group_by」がありません
Facundo Olano

1
queryset.query.group_by = [...]を適切に使用すると、クエリのセマンティクスが壊れ、期待どおりに機能しなくなります。
ルイス・マスエリ

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