Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Aggregazione dei dati

Utilizzare le funzioni di aggregazione su MongoDB, imparando a conoscere l'Aggregation Framework, nonchè l'approccio più tradizionale basato su MapReduce.
Utilizzare le funzioni di aggregazione su MongoDB, imparando a conoscere l'Aggregation Framework, nonchè l'approccio più tradizionale basato su MapReduce.
Link copiato negli appunti

Quando si ha a che fare con una gran mole di dati – e MongoDB è nato proprio per questo scopo – una delle funzionalità più importanti è l’aggregazione, che permette di avere una visione globale dei dati e di ricavarne statistiche. L’aggregazione è oggi una funzionalità essenziale in ambiti come Business Intelligence e Big Data.

MongoDB supporta due meccanismi per effettuare aggregazioni. Uno è quello storicamente più noto in NoSQL: MapReduce. Di recente, però, è stato introdotto anche l’Aggregation Framework. Quest’ultimo è il meccanismo raccomandato, sia per la sua semplicità ma anche per ragioni di performance. Lo vedremo per primo.

Aggregation Framework

Partiamo subito da un esempio. Apriamo la shell Mongo e simuliamo un po’ di dati casuali, immaginando un database contenente i log di accesso ad un sito, allo stesso modo con cui l’abbiamo fatto nelle precedenti lezioni.

> for(var i=1; i<=20000; i++) {
    db.logs.insert( { tipo: "Login", gruppo: "Administrators", data: new Date() } );
    db.logs.insert( { tipo: "Login", gruppo: "Users", data: new Date() } );
}
> for(var i=1; i<=20000; i++) {
    db.logs.insert( { tipo: "Click", gruppo: "Users", page: "home.html", data: new Date(), operazioni: [5,2,4] } );
    db.logs.insert( { tipo: "Click", gruppo: "Users", page: "mongodb.html", data: new Date(), operazioni: [3,2,1] } );
 }

A questo punto, se siamo interessati a sapere quanti eventi di tipo Login ci sono stati nella nostra applicazione, raggruppati per gruppo di utente, possiamo lanciare il seguente comando:

> db.logs.aggregate( [
       { $match: { tipo: "Login" } },
       { $group: { _id: "$gruppo",  conteggio: { $sum: 1 } } },
])

Notiamo subito alcune cose: il comando usato è aggregate. Questo comando accetta come parametro una pipeline di operatori, cioè un elenco ordinato di operatori di aggregazione. L’effetto è che la collezione dei documenti (in questo caso logs) passa tra i vari stadi della pipeline. Per avere le migliori performance è quindi importante mettere all’inizio della pipeline le operazioni di filtraggio in modo da far passare meno elementi possibili attraverso il resto della pipeline.

In questo esempio abbiamo:

  • match: effettua un filtraggio dei documenti. La sintassi è la stessa che abbiamo visto nel filtro di find nelle precedenti lezioni;
  • group: effettua un raggruppamento dei dati. Il risultato del raggruppamento è espresso nell’oggetto BSON che segue. In particolare vediamo che come _id usiamo il nome del gruppo degli utenti, mentre nel campo conteggio sommiamo 1 per ogni valore trovato.

Accodando vari operatori di aggregazione si possono effettuare molte operazioni. Per un elenco esaustivo rimandiamo alla guida ufficiale. Alcuni operatori utili sono:

  • limit: limita il numero di elementi da passare allo stadio successivo. Esempio:
    $limit: 100
  • skip: ignora un certo numero di elementi nel passare allo stadio successivo. Ad esempio:
    $skip: 10
  • project: proietta il documento aggiungendo campi utili per elaborazioni successive o togliendo campi che non sono necessari, alleggerendo la pipeline.
  • sort: ordina i documenti in base ad uno o più campi. Ad esempio:
    $sort: { conteggio: 1 }
  • unwind: letteralmente “srotola” un documento contenente un array di N elementi, producendo un elenco di N documenti, ciascuno contenente un solo elemento.

Vediamo un esempio:

> db.logs.aggregate( [
       { $project: { tipo: 1, operazioni: 1 } },
       { $unwind: "$operazioni" },
       { $group: { _id: "$tipo",  sommaOperazioni: { $sum: "$operazioni" } } }
])

Dopo aver considerato solo i campi tipo e operazioni, il codice precedente trasforma ogni documento con l’array nel campo operazioni in altrettanti documenti quanti sono gli elementi dell’array. Ad esempio il documento seguente:

{ tipo: "Click", operazioni: [5,2,4] }

viene trasformato nei documenti:

{ tipo: "Click", operazioni: 5 }
{ tipo: "Click", operazioni: 2 }
{ tipo: "Click", operazioni: 4 }

Infine l’ultimo passo della pipeline effettua un raggruppamento con somma del valore di operazioni.

MapReduce

Come anticipato, attualmente Aggregation Framework è il framework raccomandato per effettuare aggregazioni sui dati in MongoDB. La sua semplicità è data dalla sintassi dichiarativa, mentre le migliori performance sono dovute al fatto che le operazioni possibili sono già codificate. MapReduce si basa invece fondamentalmente su un paradigma più imperativo e sulla flessibilità data dal JavaScript per dichiarare le operazioni da svolgere. Il vantaggio di MapReduce è che è più flessibile e che non tutte le operazioni che si possono implementare con MapReduce sono supportate dall’Aggregation Framework. Per cui la raccomandazione, quando si devono aggregare dati, è di utilizzare Aggregation Framework se esso permette di effettuare le operazioni richieste, altrimenti optare per con MapReduce.

MapReduce richiede che gli si forniscano due funzioni: map lavora su un singolo documento, mentre la funzione reduce opera sull’elenco risultante con lo scopo di realizzare l’aggregazione effettiva. Ad esempio se avessimo voluto implementare un calcolo analogo all’ultimo visto con Aggregation Framework, avremmo dovuto scrivere:

> db.logs.mapReduce(
    function() { if(this.operazioni) {
       var i; for(i=0; i<this.operazioni.length; i++)
          emit(this.tipo, this.operazioni[i] );
      }},
    function(key,values) { return Array.sum(values); },
    { out: {inline:1} }
)

Vediamo come nella funzione map (primo parametro) abbiamo effettuato un’operazione di emit per ogni elemento nell’array operazioni. La funzione emit stabilisce quali elementi inviare alla funzione reduce. Gli elementi devono essere di tipo chiave,valore e verranno raggruppati per chiave uguale. Il secondo parametro è la funzione reduce, che viene invocata con i parametri key,value inviati dagli emit; nel nostro esempio, essa effettua la somma dei valori. Il terzo parametro, le opzioni, contiene solo il campo out, che in questo caso istruisce MongoDB a mantenere i valori in memoria e a mostrarli a video. In realtà generalmente si specifica un nome di una collezione in cui andare a scrivere i risultati.

La funzione MapReduce ha molti altri parametri e offre ulteriori funzionalità. Dal momento che non la approfondiremo ulteriormente, rimandiamo alla guida ufficiale.

Ti consigliamo anche