00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.