Django'da GROUP BY olarak nasıl sorgulanır?


333

Bir modeli sorgularım:

Members.objects.all()

Ve geri dönüyor:

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

Ne istiyorum group_bygibi benim veritabanı bir sorgu ateş için en iyi Django yolu bilmek , gibi:

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

Tabii ki işe yaramıyor. Bazı numaralar yapabileceğimizi biliyorum django/db/models/query.py, ama sadece yama yapmadan nasıl yapılacağını merak ediyorum.

Yanıtlar:


484

Toplama yapmak istiyorsanız , ORM'nin toplama özelliklerini kullanabilirsiniz :

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

Bu, benzer bir sorgu ile sonuçlanır

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

ve çıktı şu şekildedir

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

6
@Harry: Zincirleyebilirsiniz. Şunun gibi bir şey:Members.objects.filter(date=some_date).values('designation').annotate(dcount=Count('designation'))
Eli

57
bir sorum var, bu sorgu yalnızca atama ve dcount döndürüyor, tablonun diğer değerlerini almak istiyorsanız ne olur?
AJ

19
Sıralama, atama dışında bir alansa, sıralamayı sıfırlamadan çalışmaz. Bkz. Stackoverflow.com/a/1341667/202137
Gidgidonihah

12
@Gidgidonihah Doğru, örnek okumalıMembers.objects.order_by('disignation').values('designation').annotate(dcount=Count('designation'))
bjunix

7
bir sorum var, bu sorgu yalnızca atama ve dcount döndürüyor, tablonun diğer değerlerini almak istiyorsanız ne olur?
Yann 叶

55

Kolay bir çözüm, ancak doğru yol değil ham SQL kullanmaktır :

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

Başka bir çözüm group_byözelliği kullanmaktır :

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

Sonuçlarınızı almak için artık results değişkeni üzerinden yineleme yapabilirsiniz. Bunun group_bybelgelenmediğini ve gelecekteki Django sürümünde değiştirilebileceğini unutmayın.

Ve ... neden kullanmak istiyorsun group_by? Toplama kullanmıyorsanız order_by, benzer bir sonuç elde etmek için kullanabilirsiniz.


Bana order_by kullanarak nasıl yapılacağını söyleyebilir misiniz?
simplyharsh

2
Merhaba, toplama kullanmıyorsanız, bir order_by kullanarak group_by öykünebilir ve ihtiyacınız olmayan girdileri ortadan kaldırabilirsiniz. Tabii ki, bu bir emülasyon ve sadece çok fazla veri kullanılmadığında kullanılabilir. Toplamadan bahsetmediği için bunun bir çözüm olabileceğini düşündüm.
Michael

Hey bu harika - execute_sql kullanımı için nasıl çalışmıyor açıklayabilir misiniz ..
rh0dium

8
Bunun artık Django 1.9'da çalışmadığını unutmayın. stackoverflow.com/questions/35558120/…
grokpot

1
Bu, ORM'yi kullanmanın bir tür hack-ish yoludur. Eskiden manuel olarak geçen yeni sorgu kümelerini başlatmanız gerekmemelidir.
Ian Kirkpatrick

32

Özelliklere regroupgöre gruplandırmak için şablon etiketini de kullanabilirsiniz . Dokümanlardan:

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>

Buna benzer:

  • Hindistan
    • Mumbai: 19.000.000
    • Kalküta: 15.000.000
  • Amerika Birleşik Devletleri
    • New York: 20.000.000
    • Chicago: 7.000.000
  • Japonya
    • Tokyo: 33.000.000

Ayrıca QuerySetinanıyorum s üzerinde çalışır .

kaynak: https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#regroup

düzenleme: not regroupetiketi yok sen sözlükleri listeniz anahtar sıralanmış değilse o beklediğiniz gibi çalışır. Yinelemeli olarak çalışır. Bu nedenle, regroupetiketinizi geçirmeden önce listenizi (veya sorgu kümesini) gruplandırıcının anahtarına göre sıralayın .


1
Bu harika! Bunu yapmanın basit bir yolunu aradım. Querysets üzerinde de çalışıyor, bu şekilde kullandım.
CarmenA

1
veritabanı büyük veri kümesinden okuduysanız ve daha sonra yalnızca toplanmış değerleri kullanırsanız bu tamamen yanlıştır.
Sławomir Lenart

@ SławomirLenart, bu düz bir DB sorgusu kadar etkili olmayabilir. Ancak basit kullanım durumları için güzel bir çözüm olabilir
inostia

Sonuç şablonda gösteriliyorsa çalışır. Ancak, JsonResponse veya diğer dolaylı yanıtlar için. bu çözüm çalışmaz.
Willy satrio nugroho

1
@Willysatrionugroho bir görünümde yapmak isterseniz, örneğin, stackoverflow.com/questions/477820/… sizin için işe yarayabilir
inostia

7

Bu snippet'te örneklendiği gibi özel SQL yapmanız gerekir:

Alt sorgu yoluyla özel SQL

Veya çevrimiçi Django dokümanlarında gösterildiği gibi özel bir yöneticide:

Ekstra Yönetici yöntemleri ekleme


1
Bir tür gidiş-dönüş çözümü. Eğer uzun bir kullanımı olsaydı, onu kullanardım. Ama burada sadece atama başına üye sayısına ihtiyacım var.
simplyharsh

Sorun değil. 1.1 toplama özelliklerinden bahsetmeyi düşündüm ama sürüm sürümünü kullandığınızı
varsaydım

Her şey Django'nun ORM'sinin zayıflığını gösteren ham sorguları kullanmakla ilgilidir.
Sławomir Lenart

5

Django, sorgulara göre ücretsiz grubu desteklemez . Çok kötü bir şekilde öğrendim. ORM, özel SQL kullanmadan yapmak istediğiniz şeyleri desteklemek için tasarlanmamıştır. Şunlarla sınırlısınız:

  • RAW sql (örn. MyModel.objects.raw ())
  • cr.execute cümleler (ve sonucun el yapımı ayrıştırılması).
  • .annotate() (cümlelere göre grup .annotate () için alt modelde, lines_count = Count ('lines') toplaması gibi örneklerde gerçekleştirilir)).

Bir sorgu kümesi qsüzerinde çağırabilirsiniz, qs.query.group_by = ['field1', 'field2', ...]ancak hangi sorguyu düzenlediğinizi bilmiyorsanız ve QuerySet nesnesinin iç kısımlarını bozmayacağının ve çalışacağının garantisi yoksa risklidir. Ayrıca, kodun artık gelecekteki Django sürümleriyle uyumlu olmadığını riske atmadan doğrudan erişmemeniz gereken dahili (belgesiz) bir API'dir.


aslında sadece ücretsiz gruplama ile sınırlı değilsiniz, bu yüzden Django ORM yerine SQLAlchemy'yi deneyin.
Sławomir Lenart

5

Sonuçta Django modellerini gruplandırmanıza ve yine de bir QuerySet ile çalışmanıza izin veren bir modül var: https://github.com/kako-nawao/django-group-by

Örneğin:

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)

'Kitap / kitaplar.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>

Farkı annotate/ aggregatetemel Django sorguları örneğin ilgili bir alanda, niteliklerini kullanılmasıdır book.author.last_name.

Birlikte gruplandırılmış örneklerin PK'lerine ihtiyacınız varsa, aşağıdaki notu ekleyin:

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

NOT: ArrayAggDjango 1.9'dan itibaren kullanılabilen Postgres'e özgü bir işlevdir: https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/aggregates/#arrayagg


Bu django-group-by , valuesyönteme bir alternatiftir . Sanırım farklı bir amaç için.
LShi

1
@LShi Bu elbette değerlere bir alternatif değil. valuesbir SQL selectiken group_bybir SQL'dir group by(adından da anlaşılacağı gibi ...). Neden inişli çıkışlı? Üretimde bu tür kodları karmaşık group_byifadeleri uygulamak için kullanıyoruz .
Risadinha

Onun doc diyor group_by"çoğunlukla değerleri yöntemi gibi davranacağını, ama bir farkla ..." doc SQL söz etmez GROUP BYve buna SQL ile ilgisi önermez sağlar kullanım örneğini GROUP BY. Birisi bunu açıklığa kavuşturduğunda aşağı oyu geri çekeceğim, ama bu doktor gerçekten yanıltıcı.
LShi

Dokümanıvalues okuduktan sonra , valueskendisinin GROUP BY gibi çalıştığını özledim . Bu benim hatam. Bence itertools.groupbybu django-by-by grubunun valuesyetersiz olduğu durumlarda kullanmak daha basit .
LShi

1
Veritabanından her şeyi getirerek veya almadan group by, basit bir valuesçağrı ile yukarıdan yapmak imkansızdır annotate. Öneriniz itertools.groupbyküçük veri kümeleri için çalışıyor, ancak muhtemelen sayfalamak istediğiniz binlerce veri kümesi için değil. Elbette, bu noktada, yine de hazırlanmış (zaten gruplandırılmış) verileri içeren özel bir arama dizinini düşünmeniz gerekir.
Risadinha

0

Belge Gruba Sorgu Kümesi değerleri kullanabilirsiniz söylüyor.

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

Bu kitapları kullanarak tüm kitapları bulabilir ve isimlerine göre gruplandırabilirsiniz:

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

Burada biraz cheet sayfası izleyebilirsiniz .


-1

Yanlış bir şey yapmıyorsam, hangi-query-set .group_by = [' field '] kullanabilirsiniz


8
En azından Django 1.6'da durum böyle değil: 'QuerySet' nesnesinin 'group_by' özelliği yok
Facundo Olano

1
Uygun bir kullanım queryset.query.group_by = [...] olabilir, ancak bu sorgunun anlambilimini bozar ve beklendiği gibi çalışmaz.
Luis Masuelli

-2
from django.db.models import Sum
Members.objects.annotate(total=Sum(designation))

önce Sum'u içe aktarmanız gerekir o zaman ..

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.