Grazie a Julian Motz per aver gentilmente aiutato a peer review questo articolo.
Una delle maggiori differenze tra database SQL e NoSQL è JOIN. Nei database relazionali, la clausola SQL JOIN consente di combinare righe da due o più tabelle utilizzando un campo comune tra di loro. Ad esempio, se si dispone di tabelle di books
e publishers
, è possibile scrivere comandi SQL come:
SELECT book.title, publisher.nameFROM bookLEFT JOIN book.publisher_id ON publisher.id;
In altre parole, la tabella book
ha un campo publisher_id
che fa riferimento al campo id
nella tabella publisher
.
Questo è pratico, dal momento che un singolo editore potrebbe offrire migliaia di libri. Se abbiamo mai bisogno di aggiornare i dettagli di un editore, possiamo cambiare un singolo record. La ridondanza dei dati è ridotta al minimo, poiché non è necessario ripetere le informazioni dell’editore per ogni libro. La tecnica è nota come normalizzazione.
I database SQL offrono una gamma di funzionalità di normalizzazione e vincolo per garantire il mantenimento delle relazioni.
NoSQL = = Nessun JOIN?
Non sempre
Database orientati ai documenti come MongoDB sono progettati per memorizzare dati denormalizzati. Idealmente, non ci dovrebbe essere alcuna relazione tra le collezioni. Se gli stessi dati sono richiesti in due o più documenti, devono essere ripetuti.
Questo può essere frustrante, dal momento che ci sono poche situazioni in cui non hai mai bisogno di dati relazionali. Fortunatamente, MongoDB 3.2 introduce un nuovo operatore $lookup
che può eseguire un’operazione simile a LEFT-OUTER-JOIN su due o più raccolte. Ma c’è un problema
L’aggregazione MongoDB
$lookup
è consentita solo nelle operazioni di aggregazione. Pensa a questi come a una pipeline di operatori che interrogano, filtrano e raggruppano un risultato. L’output di un operatore viene utilizzato come input per il successivo.
L’aggregazione è più difficile da capire rispetto alle query find
più semplici e generalmente viene eseguita più lentamente. Tuttavia, sono potenti e un’opzione inestimabile per operazioni di ricerca complesse.
L’aggregazione è meglio spiegata con un esempio. Presumo che stiamo creando una piattaforma di social media con una collezione user
. Memorizza i dettagli di ogni utente in documenti separati. Ad esempio:
{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email: "[email protected]", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z")}
Possiamo aggiungere tutti i campi necessari, ma tutti i documenti MongoDB richiedono un campo _id
che ha un valore univoco. _id
è simile a una chiave primaria SQL e verrà inserita automaticamente se necessario.
Il nostro social network ora richiede una collezione post
, che memorizza numerosi aggiornamenti penetranti dagli utenti. I documenti memorizzano il testo, la data, una valutazione e un riferimento all’utente che lo ha scritto in un campo user_id
:
{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date: ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important"}
Ora vogliamo mostrare gli ultimi venti post con una valutazione “importante” da parte di tutti gli utenti in ordine cronologico inverso. Ogni documento restituito deve contenere il testo, l’ora del post e il nome e il paese dell’utente associato.
Alla query aggregata MongoDB viene passato un array di operatori di pipeline che definiscono ogni operazione in ordine. Innanzitutto, dobbiamo estrarre tutti i documenti dalla raccolta post
che hanno la valutazione corretta utilizzando il filtro $match
:
{ "$match": { "rating": "important" } }
Ora dobbiamo ordinare gli elementi corrispondenti in ordine di data inversa utilizzando l’operatore $sort
:
{ "$sort": { "date": -1 } }
Poiché richiediamo solo venti post, possiamo applicare una fase $limit
, quindi MongoDB deve solo elaborare i dati che vogliamo:
{ "$limit": 20 }
Ora possiamo unire i dati della raccolta user
utilizzando il nuovo operatore $lookup
. Richiede un oggetto con quattro parametri:
-
localField
: il campo di ricerca nel documento di input -
from
: la collezione a cui aderire -
foreignField
: il campo da cercare nella raccoltafrom
-
as
: il nome del campo di output.
Il nostro operatore è quindi:
{ "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo"} }
Questo creerà un nuovo campo nel nostro output chiamato userinfo
. Contiene un array in cui ogni valore corrisponde al documento user
:
"userinfo":
Abbiamo una relazione one-to-one tra post.user_id
e user._id
, poiché un post può avere un solo autore. Pertanto, il nostro array userinfo
conterrà sempre e solo un elemento. Possiamo usare l’operatore $unwind
per decostruirlo in un sotto-documento:
{ "$unwind": "$userinfo" }
L’uscita dovrà ora essere convertito in un pratico formato, che possono avere altri operatori applicata:
"userinfo": { "name": "User One", "email: "[email protected]", …}
Infine, siamo in grado di restituire il testo, il tempo del post, il nome dell’utente e il paese utilizzando un $project
fase della pipeline:
{ "$project": { "text": 1, "date": 1, "userinfo.name": 1, "userinfo.country": 1} }
Mettere Tutto Insieme
la Nostra ultima query di aggregazione corrisponde post, sorta in ordine, limiti per gli ultimi venti elementi, si unisce dati utente, appiattisce l’utente array e restituisce i campi necessari solo. Il comando completo:
db.post.aggregate();
Il risultato è una raccolta di fino a venti documenti. Biru:
Ottimo! Posso finalmente passare a NoSQL!
MongoDB $lookup
è utile e potente, ma anche questo esempio di base richiede una query aggregata complessa. Non è un sostituto per la clausola JOIN più potente offerta in SQL. Né MongoDB offre vincoli; se un documento user
viene eliminato, i documenti orfani post
rimarranno.
Idealmente, l’operatore $lookup
dovrebbe essere richiesto raramente. Se ne hai bisogno molto, probabilmente stai usando l’archivio dati sbagliato…
Se si dispone di dati relazionali, utilizzare un database relazionale (SQL)!
Detto questo, $lookup
è una gradita aggiunta a MongoDB 3.2. Può superare alcuni dei problemi più frustranti quando si utilizzano piccole quantità di dati relazionali in un database NoSQL.