Se siete curiosi di entrare nel mondo del database MongoDB, questo è il tutorial che fa per voi. Scoprirete come installare il software, manipolare i dati e applicare le tecniche di progettazione dei dati alle vostre applicazioni.
Tutti gli esempi sono stati sviluppati con MongoDB 5, ma la maggior parte funzionerà anche con versioni precedenti o successive. Il codice può essere inserito direttamente in un’applicazione client o nella shell di MongoDB (mongo o mongosh) per interrogare e aggiornare il database.
Cos’è MongoDB?
MongoDB è un database NoSQL open source. NoSQL significa che il database non utilizza tabelle relazionali come un database SQL tradizionale.
Esistono diversi tipi di database NoSQL, ma MongoDB memorizza i dati in oggetti simili a JavaScript, noti come documenti, il cui contenuto è simile a questo:
{
_id: "123",
name: "Craig"
}
Sebbene MongoDB sia diventato sinonimo di framework Node.js basato su JavaScript, i driver ufficiali del database MongoDB sono disponibili per la maggior parte dei framework, linguaggi e runtime, tra cui Node.js, PHP e Python. Si può anche optare per librerie come Mongoose, che offrono un livello di astrazione più elevato o funzioni di mappatura relazionale degli oggetti (ORM).
A differenza delle tabelle SQL, non ci sono limiti strutturali a ciò che si può memorizzare in MongoDB. Gli schemi dei dati non vengono applicati: potete memorizzare quello che volete dove volete. Questo rende MongoDB ideale per le strutture di dati più organiche o disordinate.
Considerate una rubrica di contatti. Spesso le persone hanno più numeri di telefono. Potreste definire tre campi telefono in una tabella SQL, ma sarebbero troppi per alcuni contatti e troppo pochi per altri. In ultima analisi, vi servirà una tabella telefonica separata, che aggiungerà ulteriore complessità.
In MongoDB, i numeri di telefono possono essere definiti come un array illimitato di oggetti nello stesso documento:
{
_id: "123",
name: "Craig",
telephone: [
{ home: "0123456789" },
{ work: "9876543210" },
{ cell: "3141592654" }
]
}
È importante notare che MongoDB utilizza una notazione simile a quella degli oggetti JavaScript per gli aggiornamenti dei dati e le query, il che può rappresentare una sfida se si è abituati a usare l’SQL.
Elementi di MongoDB
Prima di andare avanti, diamo un’occhiata agli elementi che caratterizzano MongoDB. Utilizzeremo questo vocabolario in tutto l’articolo.
- Documento: Un singolo oggetto in un archivio di dati, analogo a un record o a una riga in una tabella di un database SQL.
- Campo: Un singolo dato all’interno di un documento, come un nome o un numero di telefono, analogo a un campo o a una colonna di una tabella SQL.
- Raccolta: Un insieme di documenti simili, analogo a una tabella SQL. Anche se potreste inserire tutti i vostri documenti in un’unica raccolta, di solito è più pratico raggrupparli in tipi specifici. In una rubrica di contatti potreste avere una raccolta per le persone e una per le aziende.
- Database: Una raccolta di dati correlati, identica nel significato a un database SQL.
- Schema: Uno schema definisce le strutture dei dati. Nei database SQL è necessario definire le definizioni delle tabelle con i relativi campi e tipi prima di poter memorizzare i dati. In MongoDB questo non è necessario, anche se è possibile creare uno schema che convalidi i documenti prima che possano essere aggiunti a una raccolta.
- Indice: Una struttura di dati utilizzata per migliorare le prestazioni delle query, identica nel significato agli indici di SQL.
- Chiave primaria: Un identificatore unico per ogni documento. MongoDB aggiunge automaticamente un campo _id unico e indicizzato a ogni documento di una raccolta.
- Denormalizzazione: Nei database SQL, la “normalizzazione” è una tecnica utilizzata per organizzare i dati ed eliminare le duplicazioni. In MongoDB, la “denormalizzazione” è incoraggiata. I dati si ripetono attivamente e un singolo documento potrebbe contenere tutte le informazioni necessarie.
- Joins: SQL offre un operatore JOIN che consente di recuperare i dati da più tabelle normalizzate con un’unica query. Le unioni non erano possibili in MongoDB fino alla versione 3.6 e le limitazioni permangono. Questo è un altro motivo per cui i dati dovrebbero essere denormalizzati in documenti autonomi.
- Transazioni: Quando un aggiornamento modifica due o più valori su un singolo documento, MongoDB si assicura che vadano tutti a buon fine o che falliscano tutti. Gli aggiornamenti di due o più documenti devono essere racchiusi in una transazione. MongoDB supporta le transazioni dalla versione 4.0, ma è necessario un set di repliche multi-server o un cluster sharded. Gli esempi di installazione riportati di seguito utilizzano un singolo server e quindi le transazioni non sono possibili.
Come Installare MongoDB
Esistono tre opzioni per utilizzare MongoDB sul proprio computer locale, e ve le illustreremo tutte, una per una.
1. Utilizzare Docker (Consigliato)
Docker è uno strumento di gestione del software che può installare, configurare ed eseguire MongoDB o qualsiasi altra applicazione in pochi minuti.
Installate Docker e Docker Compose e create una cartella di progetto con un unico file chiamato docker-compose.yml con i seguenti contenuti (è importante notare che i trattini sono essenziali):
version: '3'
services:
mongodb:
image: mongo:5
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=pass
- MONGO_INITDB_DATABASE=mongodemo
container_name: mongodb
volumes:
- dbdata:/data/db
ports:
- "27017:27017"
adminer:
image: dehy/adminer
container_name: adminer
depends_on:
- mongodb
ports:
- "8080:80"
volumes:
dbdata:
Accedete alla cartella dalla riga di comando ed eseguite:
docker-compose up
L’ultima versione di MongoDB 5 verrà scaricata e lanciata. La prima volta ci vorranno alcuni minuti, ma le esecuzioni successive saranno molto più rapide.
Da notare che:
- Viene definito un account amministratore di MongoDB con ID “root” e password “pass”.
- I dati vengono salvati tra un riavvio e l’altro in un volume Docker chiamato dbdata.
- Viene fornito anche il client del database Adminer.
Si può utilizzare qualsiasi client per database MongoDB per connettersi a localhost:27017 utilizzando l’ID “root” e la password “pass”. In alternativa, si può accedere ad Adminer all’indirizzo http://localhost:8080/ ed effettuare il login con le seguenti credenziali:
- Sistema: MongoDB (alpha)
- Server: host.docker.internal
- Nome utente: root
- Password: pass
Adminer permette di ispezionare raccolte e documenti. Tenete presente, tuttavia, che le raccolte vengono chiamate “tables”:
Per eseguire i comandi, è possibile utilizzare la Shell MongoDB (mongosh
) o l’ambiente REPL (Read Eval Print Loop) della riga di comando mongo
.
Accedete alla shell bash del contenitore Docker MongoDB:
docker exec -it mongodb bash
Avviate quindi la shell MongoDB con l’ID e la password:
mongosh -u root -p pass
(Se preferite, potete utilizzare il comando tradizionale mongo
.)
A questo punto potrete impartire a MongoDB comandi come i seguenti:
show dbs;
– Mostrare tutti i databaseuse mongodemo;
– Usare un database specificoshow collections;
– Elencare le raccolte di un databasedb.person.find();
– Elencare tutti i documenti di una raccoltaexit;
– Uscire/chiudere la shell
Chiudete MongoDB eseguendo il seguente comando dalla directory del progetto:
docker-compose down
2. Utilizzare un Provider Cloud (Senza Installazione)
È anche possibile utilizzare un’istanza di MongoDB in hosting, quindi non c’è bisogno di installare nulla in locale. Una connessione a internet è essenziale e la velocità di risposta dipenderà dall’host e dalla larghezza di banda. La maggior parte dei servizi applica una tariffa mensile e/o in Megabyte.
Di solito l’host fornisce i dettagli per poter amministrare il database da remoto utilizzando il software client MongoDB.
3. Installare MongoDB in locale
MongoDB può essere installato e configurato su Linux, Windows o Mac OS. Sono disponibili due edizioni:
- Una versione commerciale Enterprise
- Una Community Edition open source (utilizzata in questo tutorial)
La pagina di installazione di MongoDB fornisce istruzioni per i vari sistemi operativi. In generale:
- Le versioni per Linux vengono installate utilizzando un gestore di pacchetti come apt su Ubuntu
- Le versioni per Mac OS sono installate con brew
- Le versioni per Windows sono installate con un programma di installazione .msi
Assicuratevi di seguire attentamente le istruzioni affinché l’installazione abbia successo!
Come Accedere al Database MongoDB
Ora che il database MongoDB è installato, è il momento di imparare a gestirlo. Vediamo quali sono i passaggi per accedere e lavorare con il vostro database.
1. Installare un Client MongoDB
Per amministrare i database è necessaria un’applicazione client MongoDB. Se state utilizzando un’installazione cloud o locale, vi consigliamo di installare la shell MongoDB a riga di comando.
Adminer è un client per database basato sul web che supporta MongoDB, anche se al momento è limitato all’ispezione delle raccolte. Adminer è scaricabile come singolo script PHP, ma è già configurato se utilizzate l’installazione Docker o avete installato DevKinsta.
Un’applicazione client con interfaccia grafica offre un’interfaccia migliore per aggiornare e ispezionare i dati. Ci sono diverse opzioni disponibili, tra cui MongoDB Compass, gratuito e multipiattaforma:
Studio 3T, un altro concorrente della GUI, offre un’applicazione commerciale che garantisce funzionalità limitate gratuitamente:
Si può accedere al proprio database MongoDB utilizzando uno qualsiasi tra i seguenti strumenti:
- Il nome di rete della macchina, l’URL o l’indirizzo IP (localhost per un’installazione locale).
- La porta di MongoDB (27017 per impostazione predefinita).
- Un ID utente e una password. Di solito viene definito un utente root durante l’installazione.
2. Impostare e Salvare le Credenziali di Accesso al Database
L’amministratore root ha accesso illimitato a tutti i database. In generale, dovreste utilizzare un utente personalizzato con privilegi specifici per limitare l’accesso e aumentare la sicurezza.
Ad esempio, il seguente comando crea un utente chiamato myuser con la password mypass che ha accesso in lettura e scrittura al database mydb:
use mydb;
db.createUser({
user: "myuser",
pwd: "mypass",
roles: [
{ role: "readWrite", db: "mydb" }
]
});
Come Inserire Nuovi Documenti in MongoDB
Non è necessario definire un database o una raccolta prima di inserire il primo documento. Utilizzando un qualsiasi client MongoDB, basta passare a un database chiamato mongodemo:
use mongodemo;
Quindi inserite un singolo documento in una nuova raccolta di persone:
db.person.insertOne(
{
name: 'Abdul',
company: 'Alpha Inc',
telephone: [
{ home: '0123456789' },
{ work: '9876543210' }
]
}
);
Visualizzate il documento eseguendo una query che restituisca tutti i risultati della raccolta di persone:
db.person.find({});
Il risultato sarà simile a questo:
{
"_id" : ObjectId("62442429854636a03f6b8534"),
name: 'Abdul',
company: 'Alpha Inc',
telephone: [
{ home: '0123456789' },
{ work: '9876543210' }
]
}
Come Inserire più Documenti
Potete inserire più documenti in una raccolta passando un array a insertMany(). Il codice seguente crea altri documenti di persone e una nuova raccolta azienda:
db.person.insertMany([
{
name: 'Brian',
company: 'Beta Inc'
},
{
name: 'Claire',
company: 'Gamma Inc',
telephone: [
{ cell: '3141592654' }
]
},
{
name: 'Dawn',
company: 'Alpha Inc'
},
{
name: 'Esther',
company: 'Beta Inc',
telephone: [
{ home: '001122334455' }
]
},
{
name: 'George',
company: 'Gamma Inc'
},
{
name: 'Henry',
company: 'Alpha Inc',
telephone: [
{ work: '012301230123' },
{ cell: '161803398875' }
]
},
]);
db.company.insertMany([
{
name: 'Alpha Inc',
base: 'US'
},
{
name: 'Beta Inc',
base: 'US'
},
{
name: 'Gamma Inc',
base: 'GB'
},
]);
Da Dove Viene l’_id?
MongoDB assegna automaticamente un _id a ogni documento di una raccolta. Si tratta di un ObjectID, un valore BSON (Binary Javascript Object Notation) contenente:
- L’epoch Unix in secondi al momento della creazione (4 byte)
- Un ID macchina/processo a 5 byte
- Un contatore a 3 byte che inizia con un valore casuale
Questa è la chiave primaria del documento. Il valore esadecimale di 24 caratteri è garantito come unico per tutti i documenti del database e non può essere modificato una volta inserito.
MongoDB fornisce anche una funzione getTimeStamp() che permette di ottenere la data/ora di creazione del documento senza dover impostare esplicitamente un valore. In alternativa, si può definire il valore _id univoco quando viene creato un documento.
Denormalizzazione dei Dati
I record inseriti in precedenza impostano l’azienda di ogni utente come una stringa come “Alpha Inc”. Questo non è consigliato nei database SQL normalizzati:
- È facile commettere un errore: un utente è assegnato ad “Alpha Inc” mentre un altro è “Alpha Inc.” (carattere di punto aggiuntivo). Vengono trattati come aziende diverse.
- Aggiornare il nome di un’azienda potrebbe significare aggiornare molti record.
La soluzione SQL consiste nel creare una tabella di aziende e associare un’azienda a una persona utilizzando la sua chiave primaria (probabilmente un numero intero). La chiave rimarrà la stessa indipendentemente dai cambiamenti del nome dell’azienda e il database potrà applicare delle regole per garantire l’integrità dei dati.
La denormalizzazione è incoraggiata in MongoDB. I dati devono essere ripetuti attivamente e un singolo documento può contenere tutte le informazioni necessarie. Questo comporta diversi vantaggi:
- I documenti sono autonomi e più facili da leggere: non c’è bisogno di fare riferimento ad altre raccolte.
- Le prestazioni in scrittura possono essere più veloci rispetto a un database SQL perché vengono applicate meno regole di integrità dei dati.
- Lo sharding, ovvero la distribuzione dei dati su più macchine, diventa più semplice perché non è necessario fare riferimento ai dati di altre raccolte.
Query Semplici su MongoDB
Si possono elencare tutti i documenti di una raccolta, come ad esempio una persona, utilizzando una find() vuota:
db.person.find({})
Il metodo count() restituisce il numero di documenti (nel nostro caso, questo numero sarà 7):
db.person.find({}).count();
Il metodo sort() restituisce i documenti nell’ordine che preferite, ad esempio per nome in ordine alfabetico inverso:
db.person.find({}).sort({ name: -1 });
Si possono anche limitare il numero di documenti restituiti, ad esempio trovare i primi tre nomi:
db.person.find({}).sort({ name: 1 }).limit(2);
È possibile cercare record specifici definendo una query con uno o più campi, ad esempio trovare tutti i documenti di persone il cui nome è impostato su “Claire”:
db.person.find({ name: 'Claire' });
Sono supportati anche gli operatori logici come $e, $o, $non, $gt (maggiore di), $lt (minore di) e $ne (non uguale), ad esempio per individuare tutti i documenti di persone in cui l’azienda è “Alpha Inc” o “Beta Inc”:
db.person.find({
$or: [
{ company: 'Alpha Inc' },
{ company: 'Beta Inc' }
]
});
Nel database di esempio, lo stesso risultato potrebbe essere ottenuto con $nin (not in) per estrarre tutti i documenti in cui l’azienda non è “Gamma Inc”:
db.person.find({
company: { $nin: ['Gamma Inc'] }
});
Un secondo oggetto valore nel metodo find() imposta una proiezione che definisce i campi restituiti. In questo esempio, viene restituito solo il nome (nota che l’_id del documento viene sempre restituito a meno che non sia esplicitamente disattivato):
db.person.find(
{ name:'Claire' },
{ _id:0, name:1 }
);
Il risultato:
{
"name" : "Claire"
}
La query $elemMatch permette di trovare elementi in un array, ad esempio tutti i documenti in cui l’array del telefono ha un numero di lavoro. Lo stesso $elemMatch può essere utilizzato nella proiezione per mostrare solo il numero di lavoro:
db.person.find(
{
telephone: { $elemMatch: { work: { $exists: true }} }
},
{
_id: 0,
name:1,
telephone: { $elemMatch: { work: { $exists: true }}}
}
);
Il risultato:
{
"name" : "Abdul",
"telephone" : [
{ "work" : "9876543210" }
]
},
{
"name" : "Henry",
"telephone" : [
{ "work" : "012301230123" }
]
}
Usare i Cursori in MongoDB
La maggior parte dei driver di database consente di restituire i risultati di una query come un array o una struttura di dati simile. Tuttavia, se l’insieme contiene migliaia di documenti, questo potrebbe comportare problemi di memoria.
Come la maggior parte dei database SQL, MongoDB supporta il concetto di cursore. I cursori permettono a un’applicazione di leggere i risultati di una query uno alla volta prima di passare all’elemento successivo o di abbandonare la ricerca.
I cursori possono essere utilizzati anche da una shell di MongoDB:
let myCursor = db.person.find( {} );
while ( myCursor.hasNext() ) {
print(tojson( myCursor.next() ));
}
Come Creare Indici in MongoDB
La raccolta di persone contiene attualmente sette documenti, per cui qualsiasi query non sarà costosa dal punto di vista computazionale. Tuttavia, immaginate di avere un milione di contatti con nome e indirizzo e-mail. I contatti possono essere ordinati per nome, ma gli indirizzi e-mail saranno in un ordine apparentemente casuale.
Se si vuole cercare un contatto in base alla sua email, il database dovrà cercare fino a un milione di elementi prima di trovare una corrispondenza. L’aggiunta di un indice sul campo email crea una “tabella” di ricerca in cui le email sono memorizzate in ordine alfabetico. Il database può ora utilizzare algoritmi di ricerca più efficienti per individuare la persona corretta.
Gli indici diventano essenziali quando il numero di documenti aumenta. In generale, dovreste applicare un indice a tutti i campi che potrebbero essere citati in una query. Potreste applicare indici a tutti i campi, ma sappiate che questo rallenterebbe gli aggiornamenti dei dati e aumenterebbe lo spazio su disco necessario per la reindicizzazione.
MongoDB offre una serie di tipi di indici.
Indici a Campo Singolo
La maggior parte degli indici viene applicata a singoli campi, ad esempio indicizzando il campo nome in ordine alfabetico crescente:
db.person.createIndex({ name: 1 });
L’utilizzo di -1 inverte l’ordine. Questo sarebbe poco utile nel nostro esempio, ma potrebbe essere utile nel caso in cui aveste un campo “data” in cui gli eventi più recenti hanno la priorità.
Altri tre indici sono utili nel database di esempio di mongodemo:
db.person.createIndex( { company: 1 } );
db.company.createIndex( { name: 1 } );
db.company.createIndex( { base: 1 } );
Indici Composti su più Campi
Due o più campi possono essere specificati in un indice, ad esempio
db.person.createIndex( { name: 1, company: 1 } );
Questo può essere utile quando un campo viene regolarmente utilizzato insieme a un altro nelle query di ricerca.
Indici a più Chiavi su Elementi di Array o di Oggetti
I documenti possono essere complessi e spesso è necessario indicizzare i campi più profondi della struttura, come ad esempio il numero di telefono del lavoro:
db.products.createIndex( { 'telephone.work': 1 } );
Indici Jolly
Un indice jolly può indicizzare tutti i campi di un documento. Questo è generalmente pratico per i documenti più piccoli e semplici che possono essere interrogati in vari modi:
db.company.createIndex( { '$**': 1 } );
Indici Full-text
Un indice di testo permette di creare query simili a quelle di un motore di ricerca che possono esaminare il testo in tutti i campi delle stringhe e ordinarlo in base alla rilevanza. È possibile limitare l’indice di testo a campi specifici:
db.person.createIndex( { name: "text", company: "text" } );
… oppure creare un indice di testo su tutti i campi stringa:
db.person.createIndex( { "$**": "text" } );
L’operatore $text permette di effettuare ricerche in questo indice, ad esempio per trovare tutti i documenti in cui si fa riferimento a “Gamma”:
db.person.find({ $text: { $search: 'Gamma' } });
È bene notare che le ricerche full text richiedono generalmente cinque o più caratteri per restituire risultati utili.
Altri Tipi di Indice
MongoDB offre diversi altri tipi di indici specializzati:
- indice hashed
- indice 2d – punti su un piano bidimensionale
- indice 2dsphere – geometrie su una sfera simile alla Terra
Come Gestire gli Indici di MongoDB
Gli indici definiti su una raccolta possono essere esaminati con:
db.person.getIndexes();
Questo restituisce un array di risultati come:
[
{
"v" : 2.0,
"key" : { "_id" : 1.0 },
"name" : "_id_"
},
{
"v" : 2.0,
"key" : { "company" : 1.0 },
"name" : "company_1"
},
{
"v" : 2.0,
"key" : { "name" : 1.0 },
"name" : "name_1"
}
]
La “chiave” definisce il campo e l’ordine, mentre il “nome” è un identificatore unico per quell’indice, come ad esempio “azienda_1” per l’indice del campo azienda.
L’efficacia di un indice può essere esaminata aggiungendo un metodo .explain() a qualsiasi query, ad esempio
db.person.find({ name:'Claire' }).explain();
Questo metodo restituisce un’ampia serie di dati, ma l’oggetto “winningPlan” mostra l'”indexName” utilizzato nella query:
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : { "name" : 1.0 },
"indexName" : "name_1",
}
}
Se necessario, si può eliminare un indice facendo riferimento al suo nome:
db.person.dropIndex( 'name_1' );
o utilizzando il documento di specifica dell’indice:
db.person.dropIndex({ name: 1 });
Il metodo dropIndexes() permette di eliminare più di un indice con un unico comando.
Usare gli Schemi di Convalida dei Dati di MongoDB
A differenza di SQL, gli schemi di definizione dei dati non sono necessari in MongoDB. È possibile inviare qualsiasi dato a qualsiasi documento di qualsiasi raccolta in qualsiasi momento.
Questo offre una notevole libertà. Tuttavia, può capitare che si voglia insistere sul rispetto delle regole. Ad esempio, non dovrebbe essere possibile inserire un documento nella raccolta di persone se non contiene un nome.
Le regole di validazione possono essere specificate utilizzando un oggetto $jsonSchema che definisce un array di elementi obbligatori e le proprietà di ogni campo validato. La raccolta di persone è già stata creata, ma si può comunque definire uno schema che specifichi che è richiesta una stringa di nome:
db.runCommand({
collMod: 'person',
validator: {
$jsonSchema: {
required: [ 'name' ],
properties: {
name: {
bsonType: 'string',
description: 'name string required'
}
}
}
}
});
Provate a inserire un documento persona senza nome:
db.person.insertOne({ company: 'Alpha Inc' });
… e il comando fallirà:
{
"index" : 0.0,
"code" : 121.0,
"errmsg" : "Document failed validation",
"op" : {
"_id" : ObjectId("624591771658cd08f8290401"),
"company" : "Alpha Inc"
}
}
Gli schemi possono essere definiti anche se create una raccolta prima che venga utilizzata. Il seguente comando implementa le stesse regole di cui sopra:
db.createCollection('person', {
validator: {
$jsonSchema: {
required: [ 'name' ],
properties: {
name: {
bsonType: 'string',
description: 'name string required'
}
}
}
}
});
Questo esempio più complesso crea una raccolta di utenti che convalida il nome, l’indirizzo e-mail e almeno un numero di telefono:
db.createCollection('users', {
validator: {
$jsonSchema: {
required: [ 'name', 'email', 'telephone' ],
properties: {
name: {
bsonType: 'string',
description: 'name string required'
},
email: {
bsonType: 'string',
pattern: '^.+@.+$',
description: 'valid email required'
},
telephone: {
bsonType: 'array',
minItems: 1,
description: 'at least one telephone number required'
}
}
}
}
});
Come Aggiornare i Documenti Esistenti in MongoDB
MongoDB offre diversi metodi di aggiornamento, tra cui updateOne()
, updateMany()
e replaceOne()
. Questi vengono passati:
- Un oggetto filtro che individua i documenti da aggiornare
- Un oggetto update – o un array di oggetti update – che descrive i dati da modificare
- Un oggetto opzionale Opzioni. La proprietà più utile è upsert che permette di inserire un nuovo documento se non ne viene trovato nessuno.
L’esempio seguente aggiorna il documento persona il cui nome è impostato su “Henry”. Rimuove il numero di telefono del lavoro, aggiunge un numero di telefono di casa e imposta una nuova data di nascita:
db.person.updateOne(
{ name: 'Henry' },
[
{ $unset: [ 'telephone.work' ] },
{ $set: {
'birthdate': new ISODate('1980-01-01'),
'telephone': [ { 'home': '789789789' } ]
} }
]
);
Il prossimo esempio aggiorna il documento della persona il cui nome è impostato su “Ian”. Questo nome non esiste attualmente, ma impostando upsert su “true” lo si crea:
db.person.updateOne(
{ name: 'Ian' },
{ $set: { company: 'Beta Inc' } },
{ upsert: true }
);
È possibile eseguire comandi di query per esaminare gli aggiornamenti dei dati in qualsiasi momento.
Come Eliminare i Documenti in MongoDB
L’esempio di aggiornamento precedente ha utilizzato $unset per rimuovere il numero di telefono del lavoro dal documento con il nome “Henry”. Per rimuovere un intero documento, si può utilizzare uno dei diversi metodi di cancellazione, tra cui deleteOne()
, deleteMany()
e remove()
(che possono eliminare uno o più documenti).
Il documento appena creato per Ian può essere eliminato con un filtro appropriato:
db.person.deleteOne({ name: 'Ian' });
Usare le Operazioni di Aggregazione in MongoDB
L’aggregazione è un’operazione molto efficace ma può essere difficile da comprendere. Definisce una serie – o pipeline – di operazioni in un array. Ogni fase di questa pipeline esegue un’operazione come filtrare, raggruppare, calcolare o modificare un insieme di documenti. Uno stage può anche utilizzare un comportamento simile a una JOIN SQL con un’operazione di $lookup. I documenti risultanti vengono passati allo stadio successivo della pipeline per un’ulteriore elaborazione, se necessario.
L’aggregazione è meglio illustrata con un esempio. Costruiremo passo dopo passo una query che restituisca il nome, l’azienda e il numero di telefono (se disponibile) di chiunque lavori per un’organizzazione con sede negli Stati Uniti.
La prima operazione esegue un $match per filtrare le aziende con sede negli Stati Uniti:
db.company.aggregate([
{ $match: { base: 'US' } }
]);
Questo restituisce:
{
"_id" : ObjectId("62442429854636a03f6b853b"),
"name" : "Alpha Inc",
"base" : "US"
}
{
"_id" : ObjectId("62442429854636a03f6b853c"),
"name" : "Beta Inc",
"base" : "US"
}
Possiamo quindi aggiungere un nuovo operatore di pipeline $lookup che abbina il nome dell’azienda (localField) all’azienda (foreignField) nella raccolta di persone (from). Il risultato sarà aggiunto come array di dipendenti al documento di ogni azienda:
db.company.aggregate([
{ $match: { base: 'US' } },
{ $lookup: {
from: 'person',
localField: 'name',
foreignField: 'company',
as: 'employee'
}
}
]);
Ecco il risultato:
{
"_id" : ObjectId("62442429854636a03f6b853b"),
"name" : "Alpha Inc",
"base" : "US",
"employee" : [
{
"_id" : ObjectId("62442429854636a03f6b8534"),
"name" : "Abdul",
"company" : "Alpha Inc",
"telephone" : [
{ "home" : "0123456789" },
{ "work" : "9876543210" }
]
},
{
"_id" : ObjectId("62442429854636a03f6b8537"),
"name" : "Dawn",
"company" : "Alpha Inc"
},
{
"_id" : ObjectId("62442429854636a03f6b853a"),
"name" : "Henry",
"company" : "Alpha Inc",
"telephone" : [
{ "home" : "789789789" }
],
}
]
}
{
"_id" : ObjectId("62442429854636a03f6b853c"),
"name" : "Beta Inc",
"base" : "US",
"employee" : [
{
"_id" : ObjectId("62442429854636a03f6b8535"),
"name" : "Brian",
"company" : "Beta Inc"
},
{
"_id" : ObjectId("62442429854636a03f6b8538"),
"name" : "Esther",
"company" : "Beta Inc",
"telephone" : [
{ "home" : "001122334455" }
]
}
]
}
Un’operazione $project (proiezione) può ora rimuovere tutti gli array tranne quello dei dipendenti. Segue un’operazione $unwind per distruggere l’array e ottenere i documenti dei dipendenti separati:
db.company.aggregate([
{ $match: { base: 'US' } },
{ $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
{ $project: { _id: 0, employee: 1 } },
{ $unwind: '$employee' }
]);
Il risultato:
{
"employee" : {
"_id" : ObjectId("62442429854636a03f6b8534"),
"name" : "Abdul",
"company" : "Alpha Inc",
"telephone" : [
{ "home" : "0123456789" },
{ "work" : "9876543210" }
]
}
}
{
"employee" : {
"_id" : ObjectId("62442429854636a03f6b8537"),
"name" : "Dawn",
"company" : "Alpha Inc"
}
}
{
"employee" : {
"_id" : ObjectId("62442429854636a03f6b853a"),
"name" : "Henry",
"company" : "Alpha Inc",
"telephone" : [
{ "home" : "789789789" }
]
}
}
{
"employee" : {
"_id" : ObjectId("62442429854636a03f6b8535"),
"name" : "Brian",
"company" : "Beta Inc"
}
}
{
"employee" : {
"_id" : ObjectId("62442429854636a03f6b8538"),
"name" : "Esther",
"company" : "Beta Inc",
"telephone" : [
{ "home" : "001122334455" }
]
}
}
Infine, un’operazione $replaceRoot viene utilizzata per formattare ogni documento in modo da restituire solo il nome della persona, l’azienda e il numero di telefono del lavoro. Segue poi un’operazione $sort per restituire i documenti in ordine crescente di nome. La query aggregata completa:
db.company.aggregate([
{ $match: { base: 'US' } },
{ $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } },
{ $project: { _id: 0, employee: 1 } },
{ $unwind: '$employee' },
{ $replaceRoot: {
newRoot: {
$mergeObjects: [ {
name: "$employee.name",
company: '$employee.company',
work: { $first: '$employee.telephone.work' }
}, "$name" ]
} } },
{ $sort: { name: 1 } }
]);
Il risultato:
{
"name" : "Abdul",
"company" : "Alpha Inc",
"work" : "9876543210"
}
{
"name" : "Brian",
"company" : "Beta Inc",
}
{
"name" : "Dawn",
"company" : "Alpha Inc",
}
{
"name" : "Esther",
"company" : "Beta Inc"
}
{
"name" : "Henry",
"company" : "Alpha Inc"
}
Ci sono altri modi per ottenere questo risultato, ma il punto chiave è che MongoDB può svolgere la maggior parte del lavoro. Raramente è necessario leggere i documenti e manipolare i dati direttamente nel codice dell’applicazione.
Come Eseguire Operazioni di Massa con MongoDB
Per impostazione predefinita, MongoDB può gestire 1.000 operazioni contemporanee. È improbabile che questo sia un problema quando si utilizza MongoDB, ma le applicazioni possono raggiungere questo limite se effettuano una serie di manipolazioni di dati su singoli record. Le applicazioni Node.js sono particolarmente problematiche perché possono inviare rapidamente una serie di richieste asincrone senza dover aspettare che vengano completate.
Per aggirare questo problema, MongoDB fornisce un’API per le operazioni di massa che accetta un numero qualsiasi di aggiornamenti che possono essere eseguiti in ordine sparso.
Ecco un esempio di pseudocodice in Node.js:
// reference the mycollection collection
const bulk = db.collection('mycollection').initializeUnorderedBulkOp();
// make any number of data changes
bulk.insertOne(...);
bulk.insertMany(...)
bulk.updateOne(...);
bulk.deleteOne(...);
// etc...
bulk.execute();
L’ultima istruzione emette un’unica richiesta a MongoDB, in modo da ridurre le possibilità di raggiungere il limite di 1.000 operazioni.
Riepilogo
MongoDB offre un archivio flessibile per applicazioni come sistemi di gestione dei contenuti, rubriche e social network, dove le strutture di dati rigide sono troppo rigide e difficili da definire. La scrittura dei dati è veloce e lo sharding su più server diventa più semplice.
Scrivere applicazioni utilizzando un database MongoDB può anche essere liberatorio. È possibile memorizzare qualsiasi dato in qualsiasi documento, in qualsiasi raccolta e in qualsiasi momento, il che è particolarmente pratico se si sta sviluppando un prototipo o un Minimum Viable Product utilizzando metodologie agili in cui i requisiti si evolvono nel tempo.
Detto questo, le query complesse possono rappresentare una sfida, e i concetti di denormalizzazione sono difficili da accettare quando si sta migrando dal mondo SQL.
MongoDB è meno adatto alle applicazioni che hanno requisiti transazionali rigidi in cui l’integrità dei dati è essenziale, come nel caso dei sistemi bancari, contabili e di controllo delle scorte. Questi hanno campi di dati identificabili che devono essere progettati prima di iniziare la codifica.
Ci sono molti tipi di applicazioni che si collocano tra questi due estremi, quindi la scelta del database più adatto diventa più difficile. Fortunatamente, i database NoSQL, tra cui MongoDB, hanno iniziato ad adottare opzioni simili a SQL, tra cui JOIN e transazioni.
Al contrario, i database SQL come MySQL e PostgreSQL offrono ora campi dati JSON simili a quelli NoSQL. Anche questi potrebbero meritare la vostra attenzione, ma come sempre la scelta finale spetta solo a voi.