UNIVERSITÀ DEGLI STUDI DI MILANO
Dipartimento di Informatica
MongoDB e basi di dati NoSQL
Insegnamento di Analisi dei dati su larga scala
Anno accademico 2013/14
Un buon posto per valutare le differenze tra le varie soluzioni è questo post.
MongoDB (http://www.mongodb) è un sistema open source per la gesione di database NoSQL (document-oriented) sviluppato da 10gen (http://www.10gen.com/)
| Nome | Cognome | Targa |
|---|---|---|
| Paolino | Paperino | 313 |
| Topolino | 113 |
{
"nome": "Paolino",
"cognome": "Paperino",
"targa": 313
},
{
"nome": "Topolino",
"targa": 113
}
Il formato in cui effettivamente MongoDB memorizza l'informazione è BSON (Binary JSON, http://bsonspec.org/), che permette di
È possibile inserire un documento all'interno di un campo BSON
{
"nome": "Paolino",
"cognome": "Paperino",
"auto": {"modello": "American Bantam", "targa": 313}
}
In BSON gli array sono oggetti di prima classe
{
"nome": "Paolino",
"cognome": "Paperino",
"auto": {"modello": "American Bantam", "targa": 313},
"familiari": ["qui", "quo", "qua"]
}
mongodmongoIstruzioni per l'installazione a http://docs.mongodb.org/manual/installation/
$ mongod all output going to: /usr/local/var/log/mongodb/mongo.log
$ mongo MongoDB shell version: 2.4.1 connecting to: test >
Per iniziare a capirci qualcosa può essere più semplice usare la shell online a http://www.mongodb.org
> use fumetti switched to db fumetti
> db.personaggi.insert({nome: "Paolino", cognome: "Paperino",
... genere: 'M', auto: {modello: "American Bantam", targa: 313},
... anno_nascita: 1920, prima_apparizione: 1934})
> db.personaggi.find()
{ "_id" : ObjectId("51618534b8810192ed114356"),
"nome" : "Paolino", "cognome" : "Paperino",
"auto" : { "modello" : "American Bantam", "targa" : 313 },
"anno_nascita" : 1920, "prima_apparizione" : 1934 }
> db.personaggi.findOne()
{
"_id" : ObjectId("5161a459b356cd6125b74e7b"),
"nome" : "Paolino",
"cognome" : "Paperino",
"genere" : "M",
...
}
_id contiene un valore generato automaticamente in termini di ObjectId
> ObjectId()
ObjectId("51618f81b8810192ed114357")
> ObjectId()
ObjectId("51618f83b8810192ed114358")
> ObjectId()
ObjectId("51618f84b8810192ed114359")
> db.personaggi.insert({nome: "nonna papera", genere: 'F',
... "auto": {modello: "Detroit Electric"}, anno_nascita: 1833,
... prima_apparizione: 1943, passatempi: ['cucina', 'agricoltura']})
> db.personaggi.insert({nome: "Paperon", cognome: "De Paperoni",
... genere: 'M' anno_nascita: 1867, prima_apparizione: 1947,
... passatempi: ['alta finanza', 'risparmio',
... 'nuoto in depositi aurei']})
> db.personaggi.insert({nome: "Archimede", cognome: "Pitagorico",
... genere: 'M', prima_apparizione: 1952, passatempi: ['invenzioni',
... 'studio']})
> db.personaggi.insert({nome: "Pico", cognome: "De Paperis",
... genere: 'M', prima_apparizione: 1961, passatempi: ['studio']})
> db.personaggi.find({auto: {$exists: true}})
{ ..., "nome" : "Paolino", "cognome" : "Paperino", ... }
{ ..., "nome" : "Nonna papera",... }
> db.personaggi.find({genere: 'F'})
{ ..., "nome" : "Nonna papera", ... }
> db.personaggi.find({prima_apparizione: 1961})
{ ..., "nome" : "Pico", "cognome" : "De Paperis"... }
> db.personaggi.find({prima_apparizione: {$gt: 1950}})
{ ..., "nome" : "Archimede", "cognome" : "Pitagorico", ... }
{ ..., "nome" : "Pico", "cognome" : "De Paperis", ... }
> db.personaggi.find({prima_apparizione: {$in: [1947, 1961]}})
{ ..., "nome" : "Paperon", "cognome" : "De Paperoni", ... }
{ ..., "nome" : "Pico", "cognome" : "De Paperis", ... }
> db.personaggi.find({prima_apparizione: {$lt: 1950}, genere: 'M'})
{ ..., "nome" : "Paolino", "cognome" : "Paperino", ... }
{ ..., "nome" : "Paperon", "cognome" : "De Paperoni", ... }
> db.personaggi.find({$or: [{anno_nascita: {$gt: 1900}},
... {genere: 'F'}]})
{ ..., "nome" : "Paolino", "cognome" : "Paperino", ... }
{ ..., "nome" : "Nonna papera", ... }
> db.personaggi.find({passatempi: 'studio'})
{ ..., "nome" : "Archimede", "cognome" : "Pitagorico", ... }
{ ..., "nome" : "Pico", "cognome" : "De Paperis", ... }
> db.personaggi.find({passatempi: ['studio']})
{ ..., "nome" : "Pico", "cognome" : "De Paperis", ... }
> db.personaggi.find({'passatempi.1': 'studio'})
{ ..., "nome" : "Archimede", "cognome" : "Pitagorico", ... }
> db.personaggi.find({'auto.modello': 'American Bantam'})
{ ..., "nome" : "Paolino", "cognome" : "Paperino", ... }
> db.personaggi.find({nome: /.*aper.*/})
{ ..., "nome" : "Nonna papera", ... }
{ ..., "nome" : "Paperon", "cognome" : "De Paperoni", ... }
l'uso di espressioni regolari e di altri strumenti come $lt e $gt può essere fonte di inefficienza durante l'esecuzione delle query$near, $within)è possibile visualizzare campi specifici dei documenti selezionati specificando un secondo argomento per find
> db.personaggi.find({"passatempi.1": 'studio'}, {nome: 1,
... cognome: 1})
{ "_id" : ObjectId("5161a459b356cd6125b74e7e"),
"nome" : "Archimede", "cognome" : "Pitagorico" }
> db.personaggi.find({"passatempi.1": 'studio'}, {cognome: 0,
... genere: 0, anno_nascita: 0, prima_apparizione: 0,
... passatempi: 0})
{ "_id" : ObjectId("5161a459b356cd6125b74e7e"),
"nome" : "Archimede" }
_id, che altrimenti viene sempre visualizzato
> db.personaggi.find({"passatempi.1": 'studio'}, {nome: 1,
... cognome: 1, _id: 0})
{ "nome" : "Archimede", "cognome" : "Pitagorico" }
db.personaggi.ensureIndex({nome: 1, prima_apparizione: -1})
db.personaggi.find({nome: /P.*/, prima_apparizione: {$lt, 1950}},
{_id: 0, nome: 1, prima_apparizione: 1})
find restituisce un cursore ai suoi risultatinext e hasNext
var cursor = db.personaggi.find()
> var personaggio = cursor.next()
> personaggio.auto
{ "modello" : "American Bantam", "targa" : 313 }
> personaggio.auto.targa
313
> cursor.hasNext()
true
> cursor[0]
{
"_id" : ObjectId("5161a459b356cd6125b74e7c"),
"nome" : "Nonna papera",
"genere" : "F",
"auto" : {
"modello" : "Detroit Electric"
},
"anno_nascita" : 1833,
"prima_apparizione" : 1943,
"passatempi" : [
"cucina",
"agricoltura"
]
}
limitskipsort> db.personaggi.find().sort({prima_apparizione: 1})
countupdate va usato con attenzione
> db.personaggi.update({name: 'Archimede'}, {prima_apparizione: 1950})
> db.personaggi.find({name: 'Archimede'})
>
$set
> db.personaggi.update({nome: 'Archimede'}, {$set:
... {prima_apparizione: 1950}})
> db.personaggi.find({nome: 'Archimede'})
{ ..., "nome" : "Archimede", ..., "prima_apparizione" : 1950 }
$inc incrementa (o decrementa) una quantità$push aggiunge elementi a un array
> db.personaggi.update({nome: 'Archimede'},
... {$push: {passatempi: 'lampadine'}})
> db.personaggi.find({nome: 'Archimede'})
{ ..., "nome" : "Archimede", ...,
"passatempi" : [ "invenzioni", "studio", "lampadine" ] }
updatetrue il terzo argomento di update, l'operazione di aggiornamento crea una collezione qualora quella da modificare non esista (upsert)update aggiorna un solo documento, a meno che non venga impostato a true il suo quarto argomentodb.personaggi.remove({nome: 'Pico'})
db.personaggi.remove({nome: 'Pico'}, 1)
(equivale a LIMIT 1)
db.personaggi.remove()
db.personaggi.drop()
db.dropDatabase()
resource date
index Jan 20 2010 4:30
index Jan 20 2010 5:30
about Jan 20 2010 6:00
index Jan 20 2010 7:00
about Jan 21 2010 8:00
about Jan 21 2010 8:30
index Jan 21 2010 8:30
about Jan 21 2010 9:00
index Jan 21 2010 9:30
index Jan 22 2010 5:00
resource year month day count index 2010 1 20 3 about 2010 1 20 1 about 2010 1 21 3 index 2010 1 21 2 index 2010 1 22 1
usiamo un algoritmo MapReduce in cui
> db.hits.insert({resource:'index', date:new Date(2010,0,20,4,30)})
> db.hits.insert({resource:'index', date:new Date(2010,0,20,5,30)})
> db.hits.insert({resource:'about', date:new Date(2010,0,20,6,0)})
> db.hits.insert({resource:'index', date:new Date(2010,0,20,7,0)})
> db.hits.insert({resource:'about', date:new Date(2010,0,21,8,0)})
> db.hits.insert({resource:'index', date:new Date(2010,0,21,8,30)})
> db.hits.insert({resource:'about', date:new Date(2010,0,21,9,0)})
> db.hits.insert({resource:'index', date:new Date(2010,0,21,9,30)})
> db.hits.insert({resource:'index', date:new Date(2010,0,22,5,0)})
> var map = function() {
... var key = {
... resource: this.resource,
... year: this.date.getFullYear(),
... month: this.date.getMonth(),
... day: this.date.getDate()
... };
... emit(key, {count: 1});
...};
> var reduce = function(key, values) {
... var sum = 0;
... values.forEach(function(value) {
... sum += value['count'];
... });
... return {count: sum};
...}
> db.hits.mapReduce(map, reduce, {out: 'hit_stats'})
{
"result" : "stats",
"timeMillis" : 52,
"counts" : {
"input" : 9,
"emit" : 9,
"reduce" : 3,
"output" : 5
},
"ok" : 1,
}
> db.hit_stats.find()
{ "_id" : { "resource" : "about", "year" : 2010, "month" : 0,
"day" : 20 }, "value" : { "count" : 1 } }
{ "_id" : { "resource" : "about", "year" : 2010, "month" : 0,
"day" : 21 }, "value" : { "count" : 2 } }
{ "_id" : { "resource" : "index", "year" : 2010, "month" : 0,
"day" : 20 }, "value" : { "count" : 3 } }
{ "_id" : { "resource" : "index", "year" : 2010, "month" : 0,
"day" : 21 }, "value" : { "count" : 2 } }
{ "_id" : { "resource" : "index", "year" : 2010, "month" : 0,
"day" : 22 }, "value" : { "count" : 1 } }
MongoDB prevede forme più deboli di aggregazione che non richiedono l'utilizzo di algoritmi MapReduce, utilizzando il metodo aggregate in congiunzione con operatori come $match, $project, $group, $sort, ecc.
> db.personaggi.find({cognome: 'Paperino'})
{ "_id" : ObjectId("5161a459b356cd6125b74e7b"), ...,
"cognome" : "Paperino", ... }
> db.personaggi.insert({nome: 'qui', ...,
... capofamiglia: ObjectId("5161a459b356cd6125b74e7b")})
> db.personaggi.insert({nome: 'quo', ...,
... capofamiglia: ObjectId("5161a459b356cd6125b74e7b")})
> db.personaggi.insert({nome: 'qua', ...,
... capofamiglia: ObjectId("5161a459b356cd6125b74e7b")})
> db.personaggi.insert({nome: 'Paolino', cognome: 'Paperino', ...,
... familiari: [ObjectId("516405b8b356cd6125b74e89"),
... ObjectId("516405b8b356cd6125b74e8a"),
... ObjectId("516405b8b356cd6125b74e8b")]})
> db.personaggi.find({familiari:
... ObjectId("516405b8b356cd6125b74e8a")})
> db.personaggi.insert({nome: 'Paolino', cognome: 'Paperino', ...,
... familiari: [{nome: 'Qui', ...}, {nome: 'Quo', ...},
... {nome: 'Qua', ...}]})
> db.personaggi.find({'familiari.nome': 'Quo'})
...non c'è una soluzione universale, e molto spesso la scelta dipende oltre che dal problema che si vuole risolvere dal proprio stile di progettazione.