Aggregazioni e Annotazioni
Le aggregazioni e le annotazioni di Django permettono di eseguire calcoli sul database direttamente nelle query, come somme, medie, conteggi e molto altro, senza caricare tutti i dati in Python.
aggregate(): Calcoli sull’Intero QuerySet
Il metodo aggregate() restituisce un dizionario con i risultati del calcolo sull’intero set di dati:
from django.db.models import Count, Sum, Avg, Max, Min
from blog.models import Articolo
# Conteggio totale
risultato = Articolo.objects.aggregate(totale=Count('id'))
# {'totale': 42}
# Media delle visualizzazioni
risultato = Articolo.objects.aggregate(media_views=Avg('visualizzazioni'))
# {'media_views': 156.7}
# Valori multipli
risultato = Articolo.objects.aggregate(
totale=Count('id'),
somma_views=Sum('visualizzazioni'),
media_views=Avg('visualizzazioni'),
max_views=Max('visualizzazioni'),
min_views=Min('visualizzazioni'),
)
# {'totale': 42, 'somma_views': 6581, 'media_views': 156.7, ...}
annotate(): Calcoli per Ogni Oggetto
Il metodo annotate() aggiunge un campo calcolato a ogni oggetto del QuerySet:
from django.db.models import Count
# Contare i commenti per ogni articolo
articoli = Articolo.objects.annotate(
num_commenti=Count('commenti')
)
for articolo in articoli:
print(f'{articolo.titolo}: {articolo.num_commenti} commenti')
# Filtrare in base all'annotazione
articoli_popolari = Articolo.objects.annotate(
num_commenti=Count('commenti')
).filter(num_commenti__gte=10)
Differenza tra aggregate e annotate
La differenza fondamentale e:
- aggregate(): restituisce un singolo dizionario con il risultato globale.
- annotate(): aggiunge un campo calcolato a ogni riga del QuerySet.
# aggregate: un risultato per l'intero QuerySet
Articolo.objects.aggregate(totale=Count('id'))
# {'totale': 42}
# annotate: un valore per ogni categoria
categorie = Categoria.objects.annotate(
num_articoli=Count('articoli')
)
for cat in categorie:
print(f'{cat.nome}: {cat.num_articoli}')
Espressioni F nelle Annotazioni
Le espressioni F permettono di usare i valori dei campi nei calcoli:
from django.db.models import F, ExpressionWrapper, FloatField
# Calcolare un rapporto tra due campi
articoli = Articolo.objects.annotate(
rapporto=ExpressionWrapper(
F('commenti_count') * 100.0 / F('visualizzazioni'),
output_field=FloatField(),
)
)
# Ordinare per un valore calcolato
articoli = Articolo.objects.annotate(
engagement=F('commenti_count') + F('likes')
).order_by('-engagement')
Aggregazioni Condizionali
Puoi contare o sommare solo elementi che soddisfano una condizione usando filter dentro le funzioni di aggregazione:
from django.db.models import Q, Count
# Contare articoli pubblicati e bozze per ogni categoria
categorie = Categoria.objects.annotate(
pubblicati=Count('articoli', filter=Q(articoli__pubblicato=True)),
bozze=Count('articoli', filter=Q(articoli__pubblicato=False)),
)
Subquery e OuterRef
Per query piu complesse, puoi usare Subquery e OuterRef per creare sottoquery correlate:
from django.db.models import Subquery, OuterRef
# L'ultimo commento per ogni articolo
ultimo_commento = Commento.objects.filter(
articolo=OuterRef('pk')
).order_by('-data_creazione')
articoli = Articolo.objects.annotate(
ultimo_commento_testo=Subquery(
ultimo_commento.values('testo')[:1]
),
ultimo_commento_data=Subquery(
ultimo_commento.values('data_creazione')[:1]
),
)
OuterRef fa riferimento a un campo della query esterna, mentre Subquery incorpora il risultato della sottoquery come annotazione.
Raggruppamento con values() e annotate()
Combinando values() con annotate() si ottiene l’equivalente di GROUP BY in SQL:
# Contare articoli per ogni stato
per_stato = Articolo.objects.values('stato').annotate(
conteggio=Count('id')
).order_by('-conteggio')
# [{'stato': 'pubblicato', 'conteggio': 25}, {'stato': 'bozza', 'conteggio': 17}]
# Somma visualizzazioni per autore
per_autore = Articolo.objects.values('autore__nome').annotate(
totale_views=Sum('visualizzazioni')
).order_by('-totale_views')
Conclusione
Le aggregazioni e le annotazioni sono strumenti essenziali per analizzare i dati direttamente nel database, evitando di caricare grandi quantita di dati in Python. Con aggregate() calcoli valori globali, con annotate() arricchisci ogni oggetto con dati calcolati, e con Subquery e OuterRef risolvi anche le query piu complesse. Questi strumenti rendono l’ORM di Django adatto anche a esigenze di reporting avanzato.