Last chance! 50% off DataCamp Premium
Sale ends in
Probabilmente hai sentito parlare dei database relazionali o magari ci hai già lavorato. Il formato a righe e tabelle è la struttura più popolare e intuitiva per archiviare informazioni. Purtroppo, non puoi memorizzare tutti i dati che ti arrivano in righe e tabelle. Anzi, molti problemi del mondo reale richiedono database non relazionali. Quindi, esistono alternative?
La risposta è SÌ! Esistono quattro tipi di database che non usano righe o tabelle. Si chiamano database NoSQL, perché non puoi usare SQL per interrogarli. Sono:
Questo articolo si concentra sui database a documenti e su come usare un server chiamato MongoDB. Ma prima di entrare nei dettagli tecnici, vediamo i casi d’uso dei database a documenti. Puoi consultare la nostra guida dedicata ai database a grafo per maggiori informazioni su di essi.
Uno dei principali motivi per scegliere i database a documenti è quando hai dati che non si adattano bene a uno schema predefinito come una tabella. Molti processi o applicazioni nei vari settori archiviano questo tipo di dati. Ecco alcuni esempi:
Fermati un attimo a pensare a come i dati raccolti in questi settori si inserirebbero in tabelle. Ad esempio, le piattaforme di e-commerce avrebbero difficoltà a memorizzare i cataloghi prodotti in uno schema predefinito. Prodotti diversi hanno attributi diversi o, peggio, numeri di attributi diversi. Ti servono 10 colonne per memorizzare 10 attributi fisici di droni di 100 marche diverse o bastano 5–6 per le informazioni sui libri?
In questi scenari, i database basati su tabelle non sono d’aiuto. Usando database a documenti come MongoDB, ottieni i seguenti vantaggi:
Ora, diamo un’occhiata ai concetti chiave dei database a documenti e di MongoDB.
Ho ripetuto più volte "database a documenti" finora, ma cosa sono esattamente? Ecco i concetti principali:
Ecco una bella tabella che riassume le differenze tra database relazionali e database a documenti:
| Caratteristica | Database a documenti (ad es., MongoDB) | Database relazionali (ad es., MySQL, PostgreSQL) |
|---|---|---|
| Struttura dei dati | Memorizza i dati come documenti (ad es., JSON, BSON), consentendo strutture flessibili e gerarchiche. | Memorizza i dati in tabelle con righe e colonne, seguendo uno schema predefinito. |
| Flessibilità dello schema | Senza schema: i documenti possono avere strutture variabili, con campi e tipi di dato diversi. | Schema fisso: richiede uno schema predefinito con colonne e tipi di dato specifici. |
| Linguaggio di query | Usa MongoDB Query Language (MQL) o simili, basati su oggetti e più flessibili. | Usa SQL (Structured Query Language) per interrogare dati strutturati. |
| Join | Evita le join annidando i dati correlati all’interno dei documenti (denormalizzazione). | Supporta join complesse tra tabelle (normalizzazione). |
| Prestazioni | Letture e scritture più veloci per dati non strutturati o semi-strutturati. Evita l’overhead delle join. | Buone prestazioni per dati strutturati, ma le join possono rallentare le query. |
| Scalabilità | Scalabile orizzontalmente: può distribuire i dati su più server tramite sharding. | Tipicamente scalabile verticalmente: si affida a hardware più potente, anche se alcuni supportano la scalabilità orizzontale (ad es., tramite partizioni). |
| Supporto alle transazioni | Supporta transazioni ACID multi-documento (da MongoDB 4.0 in poi), ma inizialmente era pensato per operazioni non transazionali. | Pieno supporto a transazioni conformi ad ACID, garantendo forte coerenza e affidabilità. |
| Casi d’uso | Ideale per dati non strutturati o semi-strutturati come profili utente, log, cataloghi e strutture dati flessibili. | Ideale per dati strutturati con relazioni chiare, come registri finanziari o sistemi ERP. |
| Relazioni tra dati | Supporta dati incorporati (denormalizzazione), facilitando il recupero di informazioni correlate con una singola query. | I database relazionali si basano su chiavi esterne per stabilire relazioni tra tabelle (normalizzazione). |
| Indicizzazione | Supporta l’indicizzazione ma non offre la stessa varietà e sofisticazione dei database relazionali. | Capacità di indicizzazione avanzate, con più tipi di indice (ad es., B-tree, hash) per ottimizzare le prestazioni. |
| Coerenza | Fornisce coerenza eventuale in configurazioni distribuite ma offre anche forte coerenza quando necessario (tramite transazioni ACID). | Garantisce forte coerenza nella maggior parte dei casi grazie a transazioni ACID e integrità relazionale. |
| Scalare il volume di dati | Scala facilmente per gestire grandi quantità di dati aggiungendo server (sharding). | Può scalare verticalmente, ma la scalabilità orizzontale richiede configurazioni più complesse (ad es., partizionamento). |
| Integrità dei dati | L’integrità dei dati è gestita all’interno di ciascun documento, ma gestire le relazioni tra documenti può essere più impegnativo. | Solido supporto integrato all’integrità dei dati tramite chiavi primarie ed esterne, oltre a vincoli come UNIQUE e NOT NULL. |
| Facilità per gli sviluppatori | Developer-friendly: modellazione dati flessibile, funziona bene con applicazioni moderne (ad es., JSON, REST API). | Modellazione dati rigida ma ben compresa dagli sviluppatori familiari con SQL e dati strutturati. |
Passiamo davvero a lavorare con i documenti in MongoDB!
Per interrogare i database a documenti, dobbiamo installare il server MongoDB. Ecco le istruzioni specifiche per piattaforma:
$ sudo apt-get install -y mongodb
Poi, all’interno di un ambiente virtuale, installa le librerie pymongo e requests. pymongo è l’adapter Python ufficiale per il server MongoDB. Ci servirà la libreria requests per prelevare dati da un’API.
$ pip install pymongo
$ pip install requests
Quindi, dal terminale, avvia il server MongoDB con il seguente comando:
$ sudo service mongodb start
Ora siamo pronti a caricare dei dati in un database a documenti. Ci sono due scenari:
Li copriremo entrambi. Per prima cosa, carichiamo in locale una collezione chiamata drone_races.json. Ecco lo snippet per farlo:
import json
from pymongo import MongoClient
# Establish connection to MongoDB
client = MongoClient("localhost", 27017)
# Create a database named "drones"
drones = client["drones"]
# Create a collection named "races"
races = drones["races"]
# Load dataset into MongoDB
with open("data/drone_races.json", "r") as file:
data = json.load(file)
races.insert_many(data)
Gli oggetti più importanti per noi sono drones (un database) e races (una collezione). La maggior parte delle funzioni e dei metodi riguarderà le collezioni. Gli oggetti database servono per lo più a gestire le collezioni.
Ora vediamo come caricare gli stessi dati usando un’API. Ho pubblicato le informazioni come API usando un servizio chiamato Mockaroo. Ecco lo snippet:
import requests
from pymongo import MongoClient
# Fetch data from the API
api_url = (
"https://my.api.mockaroo.com/drone_race_matches.json?key=6f5a6b50"
)
response = requests.get(api_url)
if response.status_code == 200:
data = response.json() # Get the JSON data from the API
# Establish a connection to MongoDB
client = MongoClient()
# Access or create a specific database
drones = client["drones"]
# Access or create a specific collection within the database
races = drones["races"]
# Insert the fetched data into the MongoDB collection
races.insert_many(data)
else:
print("Failed to fetch data from the API.")
Abbiamo caricato dei dati nella collezione races del database a documenti drones, o no? Verifichiamo con delle query!
Per capire se esistono dati in una collezione dobbiamo contarne i documenti. Useremo il metodo count_documents per farlo:
>>> races.count_documents({})
9040
Nota il dizionario vuoto passato a count_documents. Quel dizionario si chiama filtro in MongoDB. Proseguendo nel tutorial, impareremo a popolare il dizionario per creare vari tipi di filtri. Al momento, non abbiamo filtri. Il codice sopra equivale a SELECT COUNT(*) FROM table_name in SQL.
Abbiamo 9040 documenti — evviva! Ora diamo un’occhiata ad alcuni dati.
Per visualizzare un documento con pymongo, possiamo usare il metodo find_one:
from pprint import pprint
>>> pprint(races.find_one())
{'_id': ObjectId('659d31e9255ec0cf4bab529d'),
'laps': 3,
'league': 'F1 Drones',
'location': {'city': 'Ford',
'country': 'United Kingdom',
'date': 'error: invalid date "2024-10-25"',
'venue': 'Manhattan Seas'},
'name': 'Honorable',
'pilots': {'drone': 'DJI3-old',
'finishing_position': 66,
'name': 'Kariotta Cow',
'qualification_time': 27.39,
'team': 'Sky Crusaders',
'telemetry': {'altitude': 34.3,
'battery_voltage': 12.1,
'speed': 68.3,
'timestamp': 'error: invalid date '
'"2024-10-25T14:09:26Z"'}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat'],
'weather_conditions': 'snowy'}
Prendi nota dei campi (chiavi) di questo documento. Memorizza informazioni su una singola gara di droni e include dettagli come:
Il documento ha anche un campo obbligatorio _id, che è un hash univoco.
count_documents restituisce sempre un numero, ma a volte vogliamo vedere i dati che la nostra query corrisponde. Per farlo, possiamo usare il fratello maggiore di find_one, ovvero find:
from pprint import pprint
for race in races.find():
pprint(race)
break
{'_id': ObjectId('659d31e9255ec0cf4bab529d'),
'laps': 3,
'league': 'F1 Drones',
'location': {'city': 'Ford',
'country': 'United Kingdom',
'date': 'error: invalid date "2024-10-25"',
'venue': 'Manhattan Seas'},
'name': 'Honorable',
'pilots': {'drone': 'DJI3-old',
'finishing_position': 66,
'name': 'Kariotta Cow',
'qualification_time': 27.39,
'team': 'Sky Crusaders',
'telemetry': {'altitude': 34.3,
'battery_voltage': 12.1,
'speed': 68.3,
'timestamp': 'error: invalid date '
'"2024-10-25T14:09:26Z"'}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat'],
'weather_conditions': 'snowy'}
find con una query vuota (nessun argomento) restituisce i documenti uno alla volta, ma non è ciò che vogliamo! Vogliamo eseguire query per rispondere a domande interessanti sui nostri dati. Qui entrano in gioco i documenti filtro.
Iniziamo con i filtri più semplici — trovare documenti in cui un certo campo è uguale a un certo valore. Sarebbe l’equivalente di:
SELECT *
FROM table_name
WHERE field = value
Facciamolo in MongoDB:
criteria = {"sponsors": "Fat Shark"}
fat_shark_races = races.count_documents(criteria)
fat_shark_races
6194
Sopra, stiamo selezionando le gare con "Fat Shark" tra gli sponsor. La sintassi è semplicemente un dizionario che mappa il campo sponsors a "Fat Shark".
Il linguaggio di query di MongoDB non sarebbe un linguaggio se non avesse alcuni comuni operatori di disuguaglianza. Ecco come usare l’operatore "minore di":
criteria = {"pilots.qualification_time": {"$lt": 10}}
quick_races = races.count_documents(criteria)
quick_races
3061
La query sopra introduce quattro nuove caratteristiche del linguaggio di query MongoDB (MQL):
pilots.qualification_time estrae il tempo di qualifica annidato all’interno del campo pilots.$lt è l’operatore "less than" (minore di).Quindi, il risultato ci dice che ci sono state 3061 gare in cui un pilota ha avuto un tempo di qualifica inferiore a 10 secondi. Questa query è stata possibile con l’operatore $lt. Ecco i suoi fratelli e sorelle:
$lte: minore o uguale$gt: maggiore di$gte: maggiore o uguale.Hanno la stessa sintassi di $lt.
MQL include anche operatori condizionali logici come $and e $or. Iniziamo dal secondo.
Recupereremo le gare con il Regno Unito come località oppure Etisalat come sponsor:
criteria = {
"$or": [
{"location.country": "United Kingdom"},
{"sponsors": "Etisalat"},
]
}
>>> races.count_documents(criteria)
6223
Ancora, usa il pulsante Explain code per una spiegazione dettagliata.
Ci sono 6223 documenti che corrispondono ai nostri criteri. Per usare una logica OR con più valori per lo stesso campo, possiamo usare l’operatore $in.
Ad esempio, possiamo verificare le condizioni meteo avverse in questo modo:
criteria = {
"weather_conditions": {"$in": ["rainy", "snowy", "cloudy"]}
}
>>> races.count_documents(criteria)
5508
Questa query sarebbe stata scomoda da scrivere con l’operatore $or. Passiamo a $and.
Questa volta, vogliamo trovare le gare con l’Australia come località E Fat Shark come sponsor. Ecco come farlo con $and:
criteria = {
"$and": [
{"location.country": "Australia"},
{"sponsors": "Fat Shark"},
]
}
>>> races.count_documents(criteria)
193
Ma in pratica userai raramente $and perché si può ottenere lo stesso risultato in modo molto più semplice:
criteria = {
"location.country": "Australia",
"sponsors": "Fat Shark",
}
races.count_documents(criteria)
193
Basta aggiungere più coppie chiave-valore al documento filtro per ottenere l’operatore logico AND.
Infine, c’è l’operatore $nin, che controlla la non appartenenza. Ad esempio, possiamo restituire tutte le gare che non si sono tenute negli Stati Uniti, nel Regno Unito o in Australia:
criteria = {
"location.country": {
"$nin": ["United States", "United Kingdom", "Australia"]
}
}
>>> races.count_documents(criteria)
126
Questo lascia solo gli Emirati Arabi Uniti come paese, quindi la query sopra potrebbe in realtà essere scritta così:
criteria = {"location.country": "United Arab Emirates"}
>>> races.count_documents(criteria)
126
Ma hai capito l’idea.
Controllare valori null o mancanti è un’operazione universale in tutte le attività di analisi dei dati. Per questo in MongoDB c’è un operatore apposito — $exists. Ecco due esempi che verificano se un certo campo esiste:
criteria = {"location.district": {"$exists": True}}
>>> races.count_documents(criteria)
0
Hmm, a quanto pare il campo district non esiste in nessun documento. Il campo laps, invece, deve esistere in tutti i documenti perché è un’informazione chiave sulle gare.
criteria = {"laps": {"$exists": True}}
races.count_documents(criteria)
9040
Come previsto, tutti i documenti hanno il campo laps. Ma che dire dei campi che esistono ma hanno valore null? Possiamo verificarlo anche questo:
criteria = {"pilots.finishing_position": None}
races.count_documents(criteria)
0
Usando l’oggetto built-in None di Python, possiamo verificare la mancanza di valore in qualsiasi campo.
Ci sono anche scenari avanzati che richiedono controlli di null o esistenza. Ad esempio, potresti voler verificare se esistono determinati elementi in grandi array annidati.
Per farlo, possiamo usare la sintassi di indicizzazione degli array in MQL. Per esempio, per trovare le gare con un solo sponsor, dobbiamo verificare se esiste il secondo elemento dell’array sponsors:
# Counting starts with 0 as always
criteria = {"sponsors.1": {"$exists": False}}
races.count_documents(criteria)
2929
Ed è facile come aggiungere il numero di indice dell’elemento alla chiave. Quindi, nella nostra collezione, quasi 3000 gare sono state sponsorizzate da un solo soggetto.
Questa sintassi di indicizzazione degli array funziona per molti altri operatori, non solo per $exists.
Un’ultima cosa che tratteremo nel tutorial sono le proiezioni. Finora, i risultati delle nostre query includevano tutti i campi di ciascun documento. Questo non è l’ideale quando i tuoi documenti hanno centinaia di campi. Immagina che pugno nell’occhio sarebbero le stampe!
Quindi, per scegliere i campi da restituire, possiamo usare le proiezioni. Ecco come:
criteria = {"pilots.telemetry.speed": {"$gte": 20}}
projection = {
"sponsors": 1,
"location.country": 1,
"pilots.telemetry.speed": 1,
"pilots.name": 1,
}
fast_pilots = races.find(criteria, projection)
for pilot in fast_pilots:
pprint(pilot)
break
Nel caso sopra, scriviamo i nostri criteri di filtro come al solito ma, questa volta, definiamo un altro documento con quattro campi impostati a 1. Se passiamo questo documento di projection come secondo argomento a find o count_documents, otterremo in output solo i campi impostati a 1.
{'_id': ObjectId('659d31e9255ec0cf4bab529d'),
'location': {'country': 'United Kingdom'},
'pilots': {'name': 'Kariotta Cow', 'telemetry': {'speed': 68.3}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat']}
Anche se abbiamo scelto solo quattro campi, il fastidioso campo _id si è infilato comunque. Per sopprimere questo comportamento, impostalo a 0 nel dizionario projection:
criteria = {"pilots.telemetry.speed": {"$gte": 20}}
projection = {
"sponsors": 1,
"location.country": 1,
"pilots.telemetry.speed": 1,
"pilots.name": 1,
"_id": 0,
}
fast_pilots = races.find(criteria, projection)
for pilot in fast_pilots:
pprint(pilot)
break
{'location': {'country': 'United Kingdom'},
'pilots': {'name': 'Kariotta Cow', 'telemetry': {'speed': 68.3}},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat']}
Adesso è più pulito.
Infine, per restituire tutti i campi tranne alcuni, possiamo impostare quei campi a 0:
projection = {"_id": 0, "league": 0, "pilots": 0}
# Empty criteria for this one
races.find_one({}, projection)
{'name': 'Honorable',
'location': {'venue': 'Manhattan Seas',
'city': 'Ford',
'country': 'United Kingdom',
'date': 'error: invalid date "2024-10-25"'},
'sponsors': ['Fat Shark', 'DJI', 'Etisalat'],
'laps': 3,
'weather_conditions': 'snowy'}
Come vedi, questa volta abbiamo tutti i campi tranne _id, league e pilots.
Questo tutorial non rende giustizia all’enorme portata di MongoDB come strumento di gestione dei database. Oggi abbiamo trattato solo le query GET (per recuperare informazioni), ma MongoDB consente anche a chi lavora con i dati di inserire, aggiornare o eliminare informazioni nei database a documenti. Abbiamo anche tralasciato un’intera classe di query — le aggregazioni.
Tutti questi argomenti esulano dallo scopo dell’articolo e richiedono risorse aggiuntive per essere approfonditi. Perché non dai un’occhiata a questi:
I database a documenti, come MongoDB, memorizzano i dati in documenti (spesso in formati simili a JSON), che possono contenere strutture annidate. Questo differisce dai database relazionali, che memorizzano i dati in righe e tabelle con uno schema fisso. I database a documenti consentono maggiore flessibilità poiché lo schema è dinamico, il che significa che ogni documento può avere campi e tipi di dato diversi. Questo rende MongoDB adatto a dati non strutturati o semi-strutturati, a differenza dei database relazionali, che richiedono uno schema predefinito.
MongoDB è utile quando lavori con dati che non si adattano facilmente a una struttura tabellare. Usa MongoDB se i tuoi dati hanno uno schema flessibile, se prevedi cambiamenti frequenti nella struttura dei dati o se devi gestire grandi volumi di dati non strutturati. È anche una buona scelta per applicazioni che richiedono operazioni di lettura e scrittura ad alta velocità su larga scala, come e-commerce, logging e sistemi di gestione dei contenuti.
MongoDB offre compatibilità con un’ampia gamma di linguaggi di programmazione, tra cui Python, Java, JavaScript, Node.js, Go, Ruby e C#, tramite driver e librerie ufficiali. La libreria Python pymongo è comunemente usata per interagire con MongoDB nelle applicazioni di data science. MongoDB si integra bene anche con framework moderni come Django, Flask ed Express.js.
MongoDB è progettato per la scalabilità orizzontale tramite sharding, in cui i dati vengono distribuiti su più server per gestire in modo efficiente dati su larga scala. Man mano che i tuoi dati crescono, MongoDB può distribuire il carico su più macchine, consentendo migliori prestazioni e capacità. Questo rende MongoDB ideale per applicazioni big data o per quelle che registrano una rapida crescita del volume dei dati.
Sì, MongoDB può gestire query complesse, ma il suo linguaggio di query (MQL, MongoDB Query Language) è piuttosto diverso da SQL. MongoDB supporta filtri, proiezioni, operatori logici e aggregazioni per eseguire query sofisticate, permettendoti di recuperare, filtrare e trasformare i dati. Tuttavia, a differenza dei database SQL, MongoDB non supporta le join allo stesso modo, poiché è progettato per denormalizzare i dati in strutture di documenti flessibili.
MongoDB può essere utilizzato per analisi in tempo reale, ma le prestazioni dipendono in gran parte da come i dati sono strutturati e indicizzati. Usando il potente sistema di indicizzazione e il framework di aggregazione di MongoDB, puoi eseguire query in tempo reale e generare insight in modo efficiente. Tuttavia, per attività analitiche più complesse, potresti considerare l’integrazione di MongoDB con strumenti come Apache Spark o l’uso del suo framework di aggregazione per gestire l’elaborazione in tempo reale su larga scala.
MongoDB offre diverse funzionalità di sicurezza, tra cui autenticazione, autorizzazione (controllo degli accessi basato sui ruoli), crittografia (in transito e a riposo) e audit. La Enterprise Edition di MongoDB offre funzionalità di sicurezza aggiuntive come l’integrazione LDAP e l’autenticazione Kerberos per la sicurezza a livello enterprise. Queste funzionalità aiutano a proteggere i dati sensibili mantenendo la conformità alle normative del settore.
Sì, MongoDB supporta transazioni conformi ad ACID, in particolare dalla versione 4.0 in poi. Questo consente transazioni multi-documento, simili a quelle dei database relazionali, garantendo atomicità, coerenza, isolamento e durabilità per operazioni che coinvolgono più documenti o collezioni. Questo rende MongoDB più adatto a scenari che richiedono garanzie transazionali.
Sebbene il JSON sia un formato leggibile dall’uomo comunemente usato per rappresentare i dati, il BSON (Binary JSON) è il formato di archiviazione di MongoDB. BSON consente una memorizzazione e un recupero dei dati più efficienti e supporta tipi di dato aggiuntivi come date e dati binari, che JSON non gestisce nativamente. BSON aggiunge anche più metadati, migliorando le prestazioni durante la memorizzazione e il recupero dei documenti.
Sono un creator di contenuti sulla data science con oltre 2 anni di esperienza e uno dei più seguiti su Medium. Mi piace scrivere articoli dettagliati su AI e ML con un pizzico di sarcasmo, perché bisogna pur fare qualcosa per renderli un po’ meno noiosi. Ho pubblicato oltre 130 articoli e un corso su DataCamp, con un altro in arrivo. I miei contenuti sono stati visti da oltre 5 milioni di persone, 20.000 delle quali sono diventate follower sia su Medium che su LinkedIn.
Inizia oggi il tuo percorso nei database non relazionali su DataCamp!
Corso
Corso
Corso
blog
Tim Lu
12 min

blog
Abid Ali Awan
10 min

blog
Abid Ali Awan
15 min