Corso
Come popolare database NoSQL, MongoDB offre un modo flessibile e scalabile per gestire i tuoi dati. Questa guida è il punto di partenza per padroneggiare la sua intuitiva Query API, che ti permette di interagire con i dati usando un semplice BSON (Binary JSON), ottimizzato per efficienza e tipi di dato.
In questo articolo vedremo le operazioni fondamentali create, read, update, delete (CRUD), approfondiremo le query di precisione con filtri e proiezione, esploreremo la potenza delle pipeline di aggregazione per la trasformazione dei dati e parleremo di indicizzazione per aumentare le prestazioni. Alla fine, avrai solide basi per recuperare e manipolare in modo efficiente i dati nelle tue collezioni MongoDB.
Concetti fondamentali: operazioni CRUD
L’ossatura di qualsiasi interazione con un database è CRUD: create, read, update e delete. Vediamo come eseguire ciascuna di queste in MongoDB.
Creare dati (insert)
Per aggiungere nuovi documenti a una collezione si usano i metodi insert. I documenti in MongoDB sono oggetti simili a JSON.
insertOne()
Questo metodo serve per aggiungere un singolo documento a una collezione.
// A sample document to insert
const newProduct = {
name: "Wireless Mouse",
brand: "Logitech",
price: 29.99,
inStock: true
};
// Insert the new document into the 'products' collection
db.products.insertOne(newProduct);
insertMany()
Questo metodo aggiunge più documenti in una volta, molto più efficiente che inserirli uno alla volta.
// An array of documents to insert
const newProducts = [
{ name: "Mechanical Keyboard", brand: "Corsair", price: 129.99, inStock: false },
{ name: "Gaming Headset", brand: "Razer", price: 99.99, inStock: true },
{ name: "Webcam", brand: "LogiTech", price: 79.99, inStock: true }
];
// Insert the array of documents into the 'products' collection
db.products.insertMany(newProducts);
Leggere dati (find)
Il metodo find() è il tuo strumento principale per recuperare documenti da una collezione. Quando chiami find(), non restituisce subito tutti i documenti. Invece, restituisce un cursore. È un modo efficiente per gestire set di risultati potenzialmente grandi senza caricarli tutti in memoria in una volta.
La chiamata più semplice a find() non prende argomenti e restituisce un cursore a ogni documento nella collezione.
// Find and return a cursor to all documents in the 'products' collection
db.products.find();
Ecco un esempio di come appare l’oggetto cursore in mongosh (la shell di MongoDB) e come potresti interagirci:
// Find all products and assign the returned cursor to a variable
const productCursor = db.products.find();
// The variable 'productCursor' now holds the cursor object, not the documents themselves.
// To get the documents, you need to iterate over the cursor.
// You can convert the cursor to an array to see all the documents
const allProducts = productCursor.toArray();
console.log(allProducts);
// Or, you can manually iterate through the documents
while (productCursor.hasNext()) {
const product = productCursor.next();
console.log(product);
}
Il cursore è un oggetto con vari metodi come toArray(), hasNext() e next() che ti permettono di recuperare e lavorare con i documenti nel set di risultati.
Aggiornare dati (update)
Per modificare i documenti esistenti si usano i metodi update. Questi metodi prendono due argomenti principali: un filtro per selezionare i documenti da aggiornare e un operatore di aggiornamento per specificare le modifiche. Un operatore comune è $set, che imposta il valore di un campo.
updateOne()
Questo metodo aggiorna il primo documento che corrisponde al filtro.
// Update the 'Wireless Mouse' document to set its price to 39.99
db.products.updateOne(
{ name: "Wireless Mouse" }, // Filter to find the document
{ $set: { price: 39.99 } } // Update operator
);
updateMany()
Questo metodo aggiorna tutti i documenti che corrispondono al filtro specificato.
// Update all 'LogiTech' brand products to be in stock
db.products.updateMany(
{ brand: "LogiTech" }, // Filter to find the documents
{ $set: { inStock: true } } // Update operator
);
Eliminare dati (delete)
Per rimuovere documenti si usano i metodi delete. Anche questi prendono un filtro per determinare quali documenti rimuovere.
deleteOne()
Questo metodo rimuove il primo documento che corrisponde al filtro.
// Delete the 'Webcam' document
db.products.deleteOne({ name: "Webcam" });
deleteMany()
Questo metodo rimuove tutti i documenti che corrispondono al filtro. Per eliminare tutti i documenti in una collezione, puoi passare un filtro vuoto {}.
// Delete all documents that are not in stock
db.products.deleteMany({ inStock: false });
Recupero di precisione: query filter
I filtri di query sono al cuore della potenza di MongoDB. Sono il primo argomento in find() e nei metodi di update e ti permettono di selezionare con precisione i documenti su cui vuoi lavorare.
Operatori di confronto
Questi operatori ti consentono di confrontare il valore di un campo con un valore specificato.
$eq (uguale a)
Trova documenti in cui un campo è uguale a un valore.
// Find all products that are made by 'Razer'
db.products.find({ brand: { $eq: "Razer" } });
// This can be simplified to:
// db.products.find({ brand: "Razer" });
$gt e $gte (maggiore di, maggiore o uguale a)
Trovano documenti in cui il valore di un campo è maggiore di ($gt) o maggiore o uguale a ($gte) un valore specificato.
// Find all products with a price greater than 100
db.products.find({ price: { $gt: 100 } });
$lt e $lte (minore di, minore o uguale a)
Trovano documenti in cui il valore di un campo è minore di ($lt) o minore o uguale a ($lte) un valore specificato.
// Find all products with a price less than or equal to 50
db.products.find({ price: { $lte: 50 } });
$ne (diverso da)
$ne trova documenti in cui un campo è diverso da un valore specificato.
// Find all products that are NOT made by 'LogiTech'
db.products.find({ brand: { $ne: "LogiTech" } });
$in e $nin (presente in un array, non presente in un array)
Trovano documenti in cui il valore di un campo è presente ($in) o non è presente ($nin) in un array di valori specificato.
// Find all products from 'Razer' or 'Corsair'
db.products.find({ brand: { $in: ["Razer", "Corsair"] } });
Operatori logici
Gli operatori logici combinano più condizioni per creare query complesse.
$and
$and combina condizioni di query con un AND logico. Tutte le condizioni devono essere vere.
// Find all products with a price greater than 50 AND are in stock
db.products.find({ $and: [{ price: { $gt: 50 } }, { inStock: true }] });
// This can be simplified by just listing the conditions:
// db.products.find({ price: { $gt: 50 }, inStock: true });
$or
Combina condizioni di query con un OR logico. Deve essere vera almeno una condizione.
// Find all products with a brand of 'Corsair' OR a price less than 40
db.products.find({ $or: [{ brand: "Corsair" }, { price: { $lt: 40 } }] });
Operatori per elementi e valutazione
Questi operatori offrono ancora più flessibilità per il filtraggio.
$exists
$exists corrisponde a documenti che contengono un campo specifico.
// Find all documents that have an 'inStock' field
db.products.find({ inStock: { $exists: true } });
$regex
Consente di usare espressioni regolari per il pattern matching nei campi stringa.
// Find all product names that start with 'Wireless'
db.products.find({ name: { $regex: /^Wireless/ } });
Dare forma all’output: proiezione
Quando esegui un’operazione find(), puoi scegliere di ricevere solo un sottoinsieme dei campi in ciascun documento. Questo si chiama proiezione ed è il secondo argomento che passi a find(). Aiuta a ridurre la quantità di dati trasferiti e processati.
Includere campi
Puoi specificare quali campi restituire impostando il loro valore a 1.
// Find all products and return only the 'name' and 'price' fields
db.products.find({}, { name: 1, price: 1 });
Escludere campi
Al contrario, puoi specificare quali campi nascondere impostando il loro valore a 0.
// Find all products and hide the 'inStock' field
db.products.find({}, { inStock: 0 });
Il campo _id viene sempre restituito per impostazione predefinita, a meno che tu non lo escluda esplicitamente.
Il campo _id
Per nascondere il campo _id devi impostarlo esplicitamente a 0.
// Find all products, returning only 'name' and 'price', and hiding '_id'
db.products.find({}, { _id: 0, name: 1, price: 1 });
Trasformazione avanzata dei dati: pipeline di aggregazione
L’aggregazione è un potente framework per l’elaborazione dei dati in MongoDB. Una pipeline di aggregazione è composta da una serie di stage che elaborano i documenti e passano i risultati da uno stage al successivo.
$match
Uno stage $match filtra i documenti in base a un filtro di query, proprio come in find(). Spesso è il primo stage per ridurre il numero di documenti da elaborare.
// Find all products with a brand of 'LogiTech'
db.products.aggregate([
{ $match: { brand: "LogiTech" } }
]);
$group
Lo stage $group raggruppa i documenti in base a un’espressione _id specificata e può eseguire varie operazioni di accumulazione, come calcolare la somma o la media di un campo.
// Group products by brand and calculate the total number of products for each brand
db.products.aggregate([
{ $group: { _id: "$brand", totalProducts: { $sum: 1 } } }
]);
$project
Uno stage $project rimodella ogni documento nello stream, in modo simile alla proiezione in find(). Puoi aggiungere nuovi campi, rimuoverne di esistenti e manipolare i dati.
// Project a new field 'nameAndPrice' for each document
db.products.aggregate([
{ $project: { name: 1, price: 1, nameAndPrice: { $concat: ["$name", " (", "$price", ")"] } } }
]);
$sort
Lo stage $sort riordina i documenti in base a un campo specificato. Il valore 1 indica l’ordine crescente e -1 quello decrescente.
// Sort all products by price in descending order
db.products.aggregate([
{ $sort: { price: -1 } }
]);
$limit e $skip
Questi stage vengono usati per la paginazione. $skip scarta un numero specificato di documenti dall’inizio della pipeline e $limit limita il numero di documenti passati allo stage successivo.
// Skip the first 10 products and then return the next 5
db.products.aggregate([
{ $skip: 10 },
{ $limit: 5 }
]);
Un semplice esempio di pipeline
Ecco un esempio completo che mostra come questi stage lavorano insieme.
// Find the average price of all products that are currently in stock,
// grouped by brand.
db.products.aggregate([
{ $match: { inStock: true } }, // Filter for products in stock
{ $group: { _id: "$brand", averagePrice: { $avg: "$price" } } } // Group and calculate average price
]);
Aumentare le prestazioni: indicizzazione
Gli indici sono strutture dati speciali che memorizzano una piccola porzione, facilmente attraversabile, dei dati di una collezione. Migliorano notevolmente le prestazioni delle query permettendo a MongoDB di trovare e recuperare rapidamente i documenti senza scansionare l’intera collezione.
createIndex()
Crei un indice su un campo che interroghi o ordini frequentemente.
// Create a single-field index on the 'brand' field
db.products.createIndex({ brand: 1 });
Il 1 indica l’ordine crescente per l’indice. Puoi usare -1 per l’ordine decrescente, utile per query che ordinano in quella direzione.
Creare un indice su un campo come brand renderà molto più rapide le query che filtrano per brand (ad es., db.products.find({ brand: "Razer" })), soprattutto man mano che la tua collezione cresce.
Considerazioni sulla scelta dei campi da indicizzare
In genere dovresti creare indici sui campi che usi spesso nelle query, come quelli in un filtro di find() o nello stage $match di un’aggregazione.
- Cardinalità: i campi con molti valori univoci (alta cardinalità), come l’indirizzo email di un utente, sono ottimi candidati per gli indici.
- Selettività della query: un campo che restituisce un piccolo sottoinsieme di documenti per una data query è anch’esso un ottimo candidato.
- Ordinamento: se ordini spesso i risultati delle query per un determinato campo, creare un indice su quel campo renderà l’operazione di sort molto più veloce.
Impatto sulle query
Gli indici hanno un impatto significativo sulle prestazioni. Una query con un indice sarà spesso ordini di grandezza più veloce della stessa query senza indice.
// Conceptual illustration of performance difference
// Without an index on 'brand':
// MongoDB performs a full collection scan, checking every document.
// This is slow, especially with millions of documents.
db.products.find({ brand: "Razer" });
// With an index on 'brand':
// MongoDB uses the index to quickly locate and return only the matching documents.
// This is very fast.
db.products.find({ brand: "Razer" });
Per ottimizzare ulteriormente le query, puoi usare il metodo db.collection.explain() per analizzare le prestazioni della query e vedere come vengono utilizzati gli indici.
Conclusione: il tuo percorso verso la padronanza di MongoDB
Hai appena completato un corso intensivo sulla Query API di MongoDB! Ora sai come eseguire le operazioni CRUD di base, usare potenti filtri per recuperare dati specifici, dare forma all’output con la proiezione e trasformare i dati con le pipeline di aggregazione. Hai anche una comprensione di base dell’indicizzazione per migliorare le prestazioni.
Questa guida fornisce solide basi, e il modo migliore per padroneggiare questi concetti è metterli in pratica. Ti invito a sperimentare con query diverse ed esplorare la documentazione completa per scoprire operatori e funzionalità più avanzati. Buon coding!
MongoDB Query API: FAQ
Qual è la principale differenza tra find() e aggregate()?
Il metodo find() viene usato per il recupero semplice di documenti con filtraggio e proiezione opzionali. Il metodo aggregate(), invece, è un potente framework per l’elaborazione dei dati in più stage, che ti consente di trasformare, raggruppare, ordinare e rimodellare i documenti in modo più complesso. Mentre find() serve per leggere i documenti così come sono, aggregate() serve per ricavare nuove informazioni dai tuoi dati.
Perché MongoDB restituisce un cursore da un’operazione find() invece dei documenti veri e propri?
Un cursore è un modo efficiente per gestire set di risultati potenzialmente grandi. Restituendo un cursore, MongoDB evita di caricare tutti i documenti in memoria in una volta sola. Questo è cruciale per prestazioni e scalabilità, soprattutto quando si lavora con milioni di documenti. Il cursore ti permette di iterare sui risultati quando serve, recuperando i documenti in batch.
Quando dovrei usare updateOne() vs. updateMany()?
Usa updateOne() quando devi modificare solo il primo documento che corrisponde al filtro specificato. È l’ideale quando ti aspetti che corrisponda un solo documento, ad esempio quando filtri per un identificatore univoco. Usa updateMany() quando vuoi aggiornare ogni documento che soddisfa i criteri del filtro.
In che modo gli indici migliorano le prestazioni delle query?
Gli indici sono strutture dati speciali che memorizzano un piccolo sottoinsieme organizzato dei dati di una collezione. Velocizzano notevolmente le query permettendo a MongoDB di localizzare rapidamente i documenti senza dover scansionare l’intera collezione. Invece di controllare ogni documento uno per uno, il database può usare l’indice per saltare direttamente ai documenti corrispondenti, rendendo l’operazione ordini di grandezza più rapida.
Posso combinare la proiezione con l’aggregazione?
Sì, la proiezione è una parte fondamentale della pipeline di aggregazione. Lo stage $project viene usato per rimodellare i documenti, aggiungere nuovi campi o rimuoverne di esistenti. Spesso è usato dopo altri stage come $match o $group per formattare l’output della pipeline, dandoti controllo fine sulla struttura finale dei dati.
Karen è una Data Engineer con la passione per la creazione di piattaforme dati scalabili. Ha esperienza nell'automazione dell'infrastruttura con Terraform ed è entusiasta di condividere ciò che ha imparato in post del blog e tutorial. Karen è una persona che costruisce comunità ed è appassionata nel favorire connessioni tra professionisti dei dati.


