Sayma ve gruplama için Django eşdeğeri


91

Şuna benzeyen bir modelim var:

class Category(models.Model):
    name = models.CharField(max_length=60)

class Item(models.Model):
    name = models.CharField(max_length=60)
    category = models.ForeignKey(Category)

Her kategori için öğe sayısını (yalnızca sayıyı) seçmek istiyorum, bu nedenle SQL'de bu kadar basit olurdu:

select category_id, count(id) from item group by category_id

Bunu "Django tarzı" yapmanın bir eşdeğeri var mı? Yoksa tek seçenek düz SQL mi? Django'daki count () yöntemine aşinayım , ancak oraya grupla nasıl sığacağını anlamıyorum .



@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 bu nasıl kopya oluyor? bu soru 2008'de sorulmuştu ve bahsettiğiniz soru 2 yıl sonra.
Sergey Golovchenko

Mevcut fikir birliği "kalite" ile kapatmaktır: < meta.stackexchange.com/questions/147643/… > "Kalite" ölçülemediğinden, sadece olumlu oylarla gidiyorum. ;-) Muhtemelen, başlıktaki en iyi acemi Google anahtar kelimelerini hangi sorunun bulduğuna bağlıdır.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Yanıtlar:


132

Burada, az önce keşfettiğim gibi, bunun Django 1.1 toplama API'si ile nasıl yapılacağı:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

3
Django'daki çoğu şey gibi, bunların hiçbirine bakmak pek mantıklı değil ama (
Django'daki

3
Not Eğer kullanım gerektiğini order_by()eğer 'category'varsayılan sıralama değil. (Daniel'in daha kapsamlı cevabına bakın.)
Rick Westera

Bunun .annotate()işe yaramasının.values() nedeni, a'dan sonra biraz farklı şekilde çalışmasıdır : "Bununla birlikte, sonuç kümesinde döndürülen sütunları sınırlamak için bir values ​​() yan tümcesi kullanıldığında, ek açıklamaları değerlendirme yöntemi biraz farklıdır. Bir açıklamalı döndürmek yerine orijinal QuerySet'teki her sonuç için orijinal sonuçlar, values ​​() yan tümcesinde belirtilen alanların benzersiz kombinasyonlarına göre gruplanır. "
mgalgs

58

( Güncelleme : Tam ORM toplama desteği artık Django 1.1'e dahil edilmiştir . Özel API'lerin kullanımıyla ilgili aşağıdaki uyarıya uygun olarak, burada belgelenen yöntem artık Django'nun 1.1 sonrası sürümlerinde çalışmamaktadır. Nedenini anlamak için araştırmadım; 1.1 veya sonraki bir sürümdeyseniz, yine de gerçek toplama API'sini kullanmalısınız .)

Çekirdek toplama desteği 1.0'da zaten vardı; sadece belgelenmemiş, desteklenmiyor ve henüz uyumlu bir API'ye sahip değil. Ancak 1.1 gelene kadar bunu nasıl kullanabileceğiniz aşağıda açıklanmıştır (kendi sorumluluğunuzdadır ve query.group_by özniteliğinin genel bir API'nin parçası olmadığını ve değişebileceğini bilerek):

query_set = Item.objects.extra(select={'count': 'count(1)'}, 
                               order_by=['-count']).values('count', 'category')
query_set.query.group_by = ['category_id']

Daha sonra sorgu_ kümesi üzerinde yinelerseniz, döndürülen her değer bir "kategori" anahtarı ve bir "sayı" anahtarı olan bir sözlük olacaktır.

Burada -count'a göre sipariş vermek zorunda değilsiniz, bu sadece nasıl yapıldığını göstermek için dahil edilmiştir (sorgu kümesi oluşturma zincirinin başka bir yerinde değil, .extra () çağrısında yapılması gerekir). Ayrıca count (1) yerine count (id) de diyebilirsiniz, ancak ikincisi daha verimli olabilir.

.Query.group_by'yi ayarlarken, değerlerin Django alan adları ('kategori') değil gerçek DB sütun adları ('kategori_kimliği') olması gerektiğini de unutmayın. Bunun nedeni, sorgu içlerini Django terimleriyle değil, her şeyin DB açısından olduğu bir düzeyde ince ayarlamanızdır.


Eski yöntem için +1. Şu anda desteklenmese bile, en azını söylemek aydınlatıcı. Şaşırtıcı, gerçekten.
hava saldırısı

Docs.djangoproject.com/en/dev/topics/db/aggregation/… adresinde Django toplama API'sine bir göz atın, onunla diğer karmaşık görevler de yapılabilir, burada bazı güçlü örnekler bulacaksınız.
serfer2

@ serfer2 evet, bu dokümanlar zaten bu cevabın tepesinden bağlantılı.
Carl Meyer

56

Django 1.1'de gruplamanın nasıl çalıştığı konusunda biraz kafam karıştığından, onu tam olarak nasıl kullanacağınızı burada detaylandıracağımı düşündüm. İlk olarak, Michael'ın söylediğini tekrarlamak gerekirse:

Burada, az önce keşfettiğim gibi, bunun Django 1.1 toplama API'si ile nasıl yapılacağı:

from django.db.models import Count
theanswer = Item.objects.values('category').annotate(Count('category'))

Ayrıca yapmanız gerektiğini unutmayın from django.db.models import Count!

Bu yalnızca kategorileri seçecek ve ardından adlı bir ek açıklama ekleyecektir category__count. Varsayılan sıralamaya bağlı olarak ihtiyacınız olan tek şey bu olabilir, ancak varsayılan sıralama bunun dışındaki bir alanı kullanıyorsa categoryçalışmayacaktır . Bunun nedeni, sıralama için gerekli alanların da seçilmesi ve her satırı benzersiz hale getirmesidir, böylece istediğiniz gibi gruplandırılmayacaksınız. Bunu düzeltmenin hızlı bir yolu, sıralamayı sıfırlamaktır:

Item.objects.values('category').annotate(Count('category')).order_by()

Bu tam olarak istediğiniz sonuçları vermelidir. Ek açıklamanın adını ayarlamak için şunları kullanabilirsiniz:

...annotate(mycount = Count('category'))...

Ardından mycount, sonuçlarda çağrılan bir not alacaksınız .

Gruplama ile ilgili diğer her şey benim için çok açıktı. Daha ayrıntılı bilgi için Django toplama API'sini kontrol ettiğinizden emin olun .


1
aynı eylem setini yabancı anahtar alanı Item.objects.values ​​('category__category') üzerinde gerçekleştirmek için. annotate (Count ('category__category')). order_by ()
Mutant

Varsayılan sıralama alanının ne olduğu nasıl belirlenir?
Bogatyr

2

Bu nasıl? (Yavaş dışında.)

counts= [ (c, Item.filter( category=c.id ).count()) for c in Category.objects.all() ]

Çok fazla satır getirse bile kısa olma avantajına sahiptir.


Düzenle.

Tek sorgu versiyonu. BTW, bu genellikle veritabanındaki SELECT COUNT (*) 'dan daha hızlıdır . Görmeyi dene.

counts = defaultdict(int)
for i in Item.objects.all():
    counts[i.category] += 1

Güzel ve kısa, ancak her kategori için ayrı bir veritabanı çağrısı yapmaktan kaçınmak istiyorum.
Sergey Golovchenko

Bu, basit durumlar için gerçekten iyi bir yaklaşımdır. Büyük bir veri kümeniz olduğunda ve bir sayıma göre + limit sipariş etmek (yani sayfalandırmak) istediğinizde, tonlarca gereksiz veriyi aşağı çekmeden düşer.
Carl Meyer

@Carl Meyer: Doğru - büyük bir veri kümesi için köpek gibi olabilir; bununla birlikte emin olmak için kıyaslama yapmanız gerekir. Ayrıca desteklenmeyen şeylere de güvenmiyor; desteklenmeyen özellikler desteklenene kadar bu arada çalışır.
S.Lott
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.