Equivalente Django per count e group by


91

Ho un modello che assomiglia a questo:

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

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

Voglio selezionare il conteggio (solo il conteggio) degli elementi per ogni categoria, quindi in SQL sarebbe semplice come questo:

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

C'è un equivalente di fare questo "alla maniera di Django"? O il semplice SQL è l'unica opzione? Conosco il metodo count () in Django, tuttavia non vedo come group by si adatterebbe lì.



@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 com'è questo un duplicato? questa domanda è stata posta nel 2008 e quella a cui ti riferisci è di 2 anni dopo.
Sergey Golovchenko

Il consenso attuale è chiudere per "qualità": < meta.stackexchange.com/questions/147643/… > Dato che la "qualità" non è misurabile, vado solo per voti positivi. ;-) Probabilmente dipende da quale domanda ha colpito le migliori parole chiave di Google per principianti nel titolo.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Risposte:


131

Ecco, come ho appena scoperto, come farlo con l'API di aggregazione Django 1.1:

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

3
come la maggior parte delle cose in Django, niente di tutto questo ha senso da guardare ma (a differenza della maggior parte delle cose in Django) una volta che l'ho provato, è stato fantastico: P
jsh

3
nota che devi usare order_by()if 'category'non è l'ordine predefinito. (Vedi la risposta più completa di Daniel.)
Rick Westera

Il motivo per cui funziona è perché .annotate()funziona in modo leggermente diverso dopo.values() : "Tuttavia, quando viene utilizzata una clausola values ​​() per vincolare le colonne restituite nel set di risultati, il metodo per la valutazione delle annotazioni è leggermente diverso. Invece di restituire un annotato risultato per ogni risultato nel QuerySet originale, i risultati originali vengono raggruppati in base alle combinazioni univoche dei campi specificati nella clausola values ​​(). "
mgalgs

58

( Aggiornamento : il supporto completo per l'aggregazione ORM è ora incluso in Django 1.1 . Fedele all'avvertimento di seguito sull'uso di API private, il metodo qui documentato non funziona più nelle versioni successive alla 1.1 di Django. Non ho approfondito per capire perché; se sei su 1.1 o versioni successive dovresti comunque utilizzare l' API di aggregazione reale .

Il supporto per l'aggregazione principale era già presente nella 1.0; è solo non documentato, non supportato e non dispone ancora di un'API amichevole. Ma ecco come puoi usarlo comunque fino all'arrivo della 1.1 (a tuo rischio e con la piena consapevolezza che l'attributo query.group_by non fa parte di un'API pubblica e potrebbe cambiare):

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

Se poi si itera su query_set, ogni valore restituito sarà un dizionario con una chiave "categoria" e una chiave "conteggio".

Non devi ordinare per conteggio qui, è incluso solo per dimostrare come è fatto (deve essere fatto nella chiamata .extra (), non altrove nella catena di costruzione del set di query). Inoltre, potresti anche dire count (id) invece di count (1), ma quest'ultimo potrebbe essere più efficiente.

Nota anche che quando si imposta .query.group_by, i valori devono essere i nomi delle colonne DB effettivi ('category_id') e non i nomi dei campi Django ('category'). Questo perché stai modificando gli interni della query a un livello in cui tutto è in termini DB, non termini Django.


+1 per il vecchio metodo. Anche se attualmente non supportato, è a dir poco illuminante. Incredibile, davvero.
attacco aereo

Dai un'occhiata all'API di aggregazione Django su docs.djangoproject.com/en/dev/topics/db/aggregation/… altre attività complesse possono essere eseguite con esso, troverai alcuni esempi potenti.
serfer2

@ serfer2 sì, quei documenti sono già collegati dall'inizio di questa risposta.
Carl Meyer

56

Dato che ero un po 'confuso su come funziona il raggruppamento in Django 1.1, ho pensato di approfondire qui come esattamente lo usi. Innanzitutto, per ripetere ciò che ha detto Michael:

Ecco, come ho appena scoperto, come farlo con l'API di aggregazione Django 1.1:

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

Nota anche che devi from django.db.models import Count!

Questo selezionerà solo le categorie e quindi aggiungerà un'annotazione chiamata category__count. A seconda dell'ordinamento predefinito, potrebbe essere tutto ciò di cui hai bisogno, ma se l'ordinamento predefinito utilizza un campo diverso da categoryquesto non funzionerà . La ragione di ciò è che anche i campi richiesti per l'ordinazione sono selezionati e rendono unica ogni riga, quindi non otterrai cose raggruppate come desideri. Un modo rapido per risolvere questo problema è ripristinare l'ordine:

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

Questo dovrebbe produrre esattamente i risultati desiderati. Per impostare il nome dell'annotazione puoi utilizzare:

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

Quindi avrai un'annotazione chiamata mycountnei risultati.

Tutto il resto sul raggruppamento è stato molto semplice per me. Assicurati di controllare l' API di aggregazione Django per informazioni più dettagliate.


1
per eseguire lo stesso insieme di azioni sul campo chiave esterna Item.objects.values ​​('category__category'). annotate (Count ('category__category')). order_by ()
Mutant

Come si determina qual è il campo di ordinamento predefinito?
Bogatyr

2

Com'è questo? (Altro che lento.)

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

Ha il vantaggio di essere breve, anche se recupera molte righe.


Modificare.

L'unica versione della query. BTW, questo è spesso più veloce di SELECT COUNT (*) nel database. Provalo per vedere.

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

È carino e breve, tuttavia vorrei evitare di avere una chiamata al database separata per ogni categoria.
Sergey Golovchenko

Questo è un ottimo approccio per casi semplici. Cade quando si dispone di un set di dati di grandi dimensioni e si desidera ordinare + limite (ovvero impaginare) in base a un conteggio, senza estrarre tonnellate di dati non necessari.
Carl Meyer

@Carl Meyer: Vero - può essere alla pecorina per un set di dati di grandi dimensioni; è necessario eseguire un benchmark per essere sicuri di ciò, tuttavia. Inoltre, non si basa nemmeno su cose non supportate; funziona nel frattempo fino a quando le funzioni non supportate sono supportate.
S.Lott
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.