Curs MongoDB 2014 al PUE

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

Temari

  	
0000020600   	Introducció a l'administració de BBDD NoSQL-MongoDB
2014-2015   	Curs. Form. Prof. Cicles Formatius de Grau Mitjà i Superior
   	
7/10/2014 - 11/11/2014 (30 hores) de 9:00h a 14:00h
U Projecte Universitat Empresa
Distrito 22@ - Av. Diagonal, 98-100 (Barcelona) 
   	   	
Requisits de certificació   	Assistència a, com a mínim, el 80% de les sessions presencials
 	 
Descripció   	L'objectiu d'aquest curs és explorar MongoDB, el motor de bases de dades NoSQL orientat a documents més popular. Segons el rànquing de BBDD de DB-Engines, MongoDB ocupa el 5è lloc en popularitat entre els motors d'emmagatzematge estructurat, sent amb ampli marge la solució NoSQL més popular.

Per tant, aquest curs desgranarà què hi ha darrere d'aquest àmpliament acceptat motor NoSQL: les seves principals característiques, avantatges i desavantatges enfront de motors de bases de dades relacionals.

IMPORTANT: No es necessària experiència prèvia amb MongoDB per a seguir el curs amb garanties, però sí que es recomana experiència en l'administració i ús d’algun motor de bases de dades relacionals.
Javascript és el llenguatge amb el que s’interactua amb la Shell de Mongo, per la qual cosa seria
interessant tenir com a mínim coneixements bàsics d’aquest llenguatge.
 	 
Objectius   	El curs començarà tractant els conceptes bàsics de la instal·lació de MongoDB, per a continuar després veient l’estructura interna de fitxers de Mongo i les extenses capacitats de la seva shell usant Javascript,
així com treballar amb documents JSON. Veurem com fer consultes, insercions i esborrats sobre
documents, com importar i exportar dades, com dissenyar esquemes o models, com fer indexació, com
treballar amb drivers per connectar les nostres aplicacions contra Mongo, ... Finalment, cobrirem les còpies de seguretat, com gestionar entorns de replicació i sharding, monitorització i recuperació de
dades.
 	 
Continguts   	Mòdul 1.- Introducció a mongoDB.
1.- Què és mongoDB?
2.- Primers passos.
2.1.- Conceptes bàsics: Documents, col·leccions i bases de dades.
2.2.- Instal·lació de mongoDB.
2.3.- Primers passos amb mongoDB.
2.4.- Introducció a la mongoDB Shell.
2.5.- Tipus de dades: JSON/BSON.
2.6.- Documentació de mongoDB.
3.- Creació, actualització i esborrat de documents.
3.1.- Insertar i guardar documents.
3.2.- Esborrar documents.
3.3.- Actualitzar documents.
3.4.- Establir una política de Write Concern.
4.- Consultes en mongoDB.
4.1.- Introducció a find i findOne.
4.2.- Criteris de consulta.
4.3.- Type-Specific Queries.
4.4.- $where Queries.
4.5.- Cursors.
4.6.- Comandes de bases de dades.

Mòdul 2.- Dissenyant la teva aplicació.
1.- Índexs en mongoDB.
1.1.- Introducció als índexs en mongoDB.
1.2.- Ús de les comandes explain() i hint().
1.3.- Quan no indexar?
1.4.- Tipus d'índexs.
1.5.- Administració d'índexs.
2.- Índexs especials i tipus de col·leccions.
2.1.- Capped Collections.
2.2.- Time-To-Live Indexes.
2.3.- Full-Text Indexes.
2.4.- Geospatial Indexing.
2.5.- Emmagatzemant arxius amb GridFS.
3.- Agregació en mongoDB.
3.1.- The Aggregation Framework.
3.2.- Pipeline Operations.
3.3.- MapReduce.
3.4.- Comandes d’agregació.
4.- Disseny de Schemas.
4.1.- Normalització versus Desnormalització.
4.2.- Optimitzacions per a la manipulació de dades.
4.3.- Gestió de la consistència.
4.4.- Migració de Schemas.
5.- mongoDB Drivers.
5.1.- Què son els drivers i com funcionen?.
5.2.- Exemples d'ús dels principals drivers.

Mòdul 3.- Replicació en mongoDB.
1.- Configuració d'un Replica Set.
1.1.- Introducció a la replicació.
1.2.- Muntatge de prova en un minut.
1.3.- Configuració d'un Replica Set.
1.4.- Modificant la configuració d'un Replica Set.
1.5.- Com dissenyar un Replica Set.
1.6.- Opcions de configuració dels components d'un Replica Set.
2.- Components d'un Replica Set.
2.1.- Sincronització.
2.2.- Heartbeats.
2.3.- Eleccions.
2.4.- Rollbacks.
3.- Administració de Replica Sets.
3.1.- Inicialitzant els membres en Standalone Mode.
3.2.- Manipulació dels estats dels membres.
3.3.- Monitorització de la replicació.
3.4.- Master-Slave.

Mòdul 4.- Sharding en mongoDB.
1.- Introducció a Sharding.
1.1.- Introducció a Sharding.
1.2.- Descripció dels components d'un clúster.
1.3.- Muntatge de prova en un minut.
2.- Configuració de Sharding.
2.1.- Quan aplicar Shard?
2.2.- Inici dels servidors.
2.3.- Com mongoDB rastreja les dades en un cluster.
2.4.- The Balancer.
3.- Escollir una Shard Key.
3.1.- Estratègies de Shard.
3.2.- Regles i directrius per escollir una Shard Key.
3.3.- Gestionant la distribució de les dades.
4.- Administració de Sharding.
4.1.- Visualització de l'estat actual.
4.2.- Gestió de connexions de xarxa.
4.3.- Administració del servidor.
4.4.- Equilibri de dades.

Sessió 1. 7-oct-2014

Jordi

LLibre que està bé per seguir:

refcardz: xuletaris de referència

Cada dia anirem seguint la carpeta slides. MongoDB és una base de dades NoSQL (not only SQL), orientada a documents. Base de dades documental. Però no és una base de dades orientada a arxius. És a dir, els documents no són arxius, sinó que són objectes.

Mongo ve de 'humongous', que significa 'enorme'.

MongoDB University -> https://university.mongodb.com/. Són cursos online de MongoDB amb pràctiques, amb el model de la plataforma edX.

Per ex, MongoDB for DBAs:

La database està formada per col.leccions (podem pensar-ho com a taules). I les col.leccions estan formades per Documents (podem pensar-ho com a files). Aquests Documents al final seran objectes. Aquests objectes es representen en JSON (Javascript Object Notation).

En JSON, els objectes estan entre les claus {}. Tot el que està entre claus és un objecte. Separat per comes tenim els atributs d'un objecte. Els atributs són string (entre dobles cometes), number, boolean, arrays, objectes (les dates seran objectes), i null.

{
	"name":"Joan",
	"cognoms":"Quintana Compte",
	"email": ["joanqc@gmail.com", "jquintana@jaumebalmes.net"],
	"telefon": "636 51 77 85"
}

No cal que tots els documents de la mateixa col.lecció tinguin la mateixa estructura. Hi ha documents amb més o menys atributs (i així no cal ficar atributs amb el valor null).

Aquests objectes es representen amb JSON, però es guarden en disc en format binari que es diu BSON. Aquest format és molt fàcil de recórrer programàticamen.

L'especificació oficial està en http://bsonspec.org.

La clau primària és obligatòria (_id), però MongoDB s'encarrega de gestionar-ho. El _id el pots posar tu o el posarà ell automàticament.

Auto-sharding: escalat horitzontal de dades. Les dades s'escalen i es repliquen entre diferents màquines, de forma fàcil.

Per provar la consola de mongo online:

Get started with `help` To try out an interactive tutorial type `tutorial`

> db
test

> show collections
>

no hi ha col.leccions.

> db.students.insert({"name":"Joan", age:44})

Creo una col.lecció students (si no existeix, es crea). Sobre la col.lecció students, existeix l'operació insert, per inserir documents. Hem de passar com a argument el document que volem inserir. I el document l'hem de passar en format JSON.

L'objecte db em permet accedir a la bd actual.

> show collections
students

> show tables
students

això és un recordatori de què les col.leccions serien com les taules, és una picada d'ull.

Per recuperar la informació que hem inserit:

> db.students.find()
{
	"_id" : ObjectId("5433a58e1cdcaf0e38bc9f79"),
	"age" : 44,
	"name" : "Joan"

ho hem passat sense paràmetres, però en podríem haver posat. Fixem-nos que s'ha creat un id.

L'estructura és dinàmica, ara creo un document amb una altra estructura.

> db.students.insert({"name":"Pere", "cognoms":"Quintana Viaplana", age:5})

> db.students.find()
{
	"_id" : ObjectId("5433a58e1cdcaf0e38bc9f79"),
	"age" : 44,
	"name" : "Joan"
}
{
	"_id" : ObjectId("5433a75140694756f62be08a"),
	"age" : 5,
	"name" : "Pere",
	"cognoms" : "Quintana Viaplana"
}
> db.student.count()
0
> db.students.count()
2
> show collections
students
student

Compte! Mirem què ha passat. La col.lecció student no existia (faltava la s), i automàticament, al demanar count(), s'ha creat la col.lecció. Ara tinc dues col.leccions.

En instal.lar mongo es crea la carpeta mongo/bin, i aquí dins estan els executables (bàsicament el servidor mongod i el client mongo)

http://www.mongodb.org/ -> downloads

Instal.lar el mongodb és senzillament copiar les carpetes (no cal l'instal.lable, descomprimir el zip en una carpeta és més senzill). Per tant, ho podem tenir en un pen i copiar-ho en una altra màquina. No hi ha cap dependència ni shell.

S'ha instal.lat a C:\Program Files\MongoDB 2.6 Standard\bin


Welcome to MongoDB!

COMPONENTS


El port per defecte és el 27017.

A windows, el primer que fem és crear una nova entrada del PATH amb el valor C:\Program Files\MongoDB 2.6 Standard\bin

S'ha d'obir la consola cmd de Windows amb permisos d'administració. El server té moltes opcions. Si no li passo cap opció, no trobarà el directori de dades (\data\db does not exist).

Hem de configurar correctament un Data Directory. Ell pressuposa que és C:\data\db.

El que farem servir és arrencar mongo amb l'opció --dbpath, per dir-li on és el nostre directori de dades. Si creem la carpeta \data\db dins de la nostra carpeta de la instal.lació de mongo, és més fàcil, doncs copiant tota la carpeta ja podem exportar mongo a una altra màquina.

C:\> mongod.exe --help
C:\> mongod.exe --dbpath "C:\Program Files\MongoDB 2.6 Standard\data\db"

(es creen carpetes i fitxers dins de db)

Es creen dues bases de dades per defecte de sistema: admin i local.

Ctrl-C matem el procés.

Per defecte els missatges de log surten per la sortida estàndar. Amb l'opció --logpath, els missatges de log es guarden en un arxiu. Això és útil quan tenim el mongod com un servei (on no es veu la consola).

La base de dades local està en els arxius local.0 i local.ns (fixem-nos que no existeixen els equivalents de la bd admin).


Creem la carpeta logs/ on es guardaran els logs.

C:\> mongod.exe --dbpath "C:\Program Files\MongoDB 2.6 Standard\data\db" --logpath "C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log" --logappend

El contingu del fitxer mongodb.log és:

2014-10-07T12:07:56.117+0200 [initandlisten] MongoDB starting : pid=1268 port=27017 dbpath=C:\Program Files\MongoDB 2.6 Standard\data\db 64-bit host=PUEPUE-1AVCOLIM
2014-10-07T12:07:56.118+0200 [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2
2014-10-07T12:07:56.118+0200 [initandlisten] db version v2.6.4
2014-10-07T12:07:56.118+0200 [initandlisten] git version: 3a830be0eb92d772aa855ebb711ac91d658ee910
2014-10-07T12:07:56.118+0200 [initandlisten] build info: windows sys.getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49
2014-10-07T12:07:56.118+0200 [initandlisten] allocator: system
2014-10-07T12:07:56.118+0200 [initandlisten] options: { storage: { dbPath: "C:\Program Files\MongoDB 2.6 Standard\data\db" }, systemLog: { destination: "file", logAppend: true, path: "C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log" } }

...

Fitxer de configuració:

Creem la carpeta config/. En la versió 2.6 s'utilitza la nova versió dels paràmetres de configuració (YAML). (el format anterior es manté per compatibilitat).

Fitxer de configuració mongodb.conf: (en el format antic, però que encara s'usa):

#Specifies a TCP port to listen for client connections.
port = 27017

#Specify the data directory path
dbpath = C:\Program Files\MongoDB 2.6 Standard\data\db

#Modify the storage pattern of the data directory to store each database’s files in a distinct folder.
directoryperdb = false

#Specify the path to a file name for the log file.
logpath = C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log

#Add new entries to the end of the logfile or overwriting the content of the log.
logappend = true

#Increases the amount of internal reporting returned on standard output or in the log file generated by logpath.
verbose = true
vvvvv = true
C:\> mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.conf"

El format YAML és bastant punyetero, s'ha d'anar amb compte. Per exemple, s'ha d'utiltizar espais i no tabulador; i entre una clau i un valor, després dels : ha d'anar un sol espai.

fitxer mongodb.cfg (amb format YAML):

# Archivo de configuración en formato YAML
# Tutorial: http://learnxinyminutes.com/docs/es-es/yaml-es/

net: 
    port: 27017

storage: 
    dbPath: C:\Program Files\MongoDB 2.6 Standard\data\db
    directoryPerDB: false

systemLog: 
    destination: file
    path: C:\Program Files\MongoDB 2.6 Standard\logs\mongodb.log
    logAppend: true
    verbosity: 4
C:\> mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.cfg"

Cercant a Google es troben diferents YAML validators.

Arrenquem mongo com a servei de Windows.

C:\> mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.cfg" 
     --install
	 --serviceName "MongoDB Server 2.6"
	 --serviceDisplayName "MongoDB Server 2.6 Service"
	 --serviceDescription "MongoDB Server 2.6 Service"
	 
mongod.exe --config "C:\Program Files\MongoDB 2.6 Standard\config\mongodb.cfg" --install --serviceName "MongoDB Server 2.6" --serviceDisplayName "MongoDB Server 2.6 Service" --serviceDescription "MongoDB Server 2.6 Service"

No arrenca el servei, senzillament el deixa instal.lat.

Aleshores anem a Equipo > botón dercho Administrar > Servicios. Mirem el servei de Mongo, i diem que s'arrenqui automàticament.

net start "MongoDB Server 2.6"

El servicio de MongoDB Server 2.6 se ha arrancado correctamente.

Si vaig a Servicios, veuré que està arrencat correctament. Per aturar-lo:

net stop "MongoDB Server 2.6"

Per eliminar el servei:

mongod.exe --remove --serviceName "MongoDB Server 2.6"

Finalment, torno a instal.lar manualment el servei, i el marco per fer un inici automàtic. El proper dia ja tindré arrencat el servidor de Mongo només arrencada la màquina.

Ara ens connectarem al servidor Mongo, amb el client mongo.exe.

mongo --help

Hem de dir a quin servidor, per quin port, i a quina base de dades ens volem connectar. Per defecte, ens connectem a test.

mongo
> exit

mongo --host localhost --port 27017
>

La versió curta
mongo localhost:27017
>

I ens connectem a una altra màquina
mongo --host 192.168.20.131 --port 27017

Ens connectem a la màquina local pel port per defecte.

Per especificar la base de dades a la que ens volem connectar, tres opcions:

mongo test
mongo localhost/test
mongo localhost:27017/test

The .mongorc.js file is executed after the mongo shell is started, so if you set a default here it will override a database specified on the command line.

Crearem l'arxiu .mongorc.js (ocult) dins la nostra carpeta d'usuari. És un script d'arrencada.

Per tant, en la shell utilitzarem bàsicament Javascript per interactuar amb Mongo. Dins el fitxer .mongorc.js:

function saluda() {
	print("Hola!");
}

La idea és que el fitxer .mongorc.js es llegeixi per defecte Una altra possibilitat_

mongo --shell "C:\Users\mongodb-m\.mongorc.js"

I una altra possibilitat:

> load("C:/Users/mongodb-m/.mongorc.js")

Per mirar quines bases de dades tinc:

> show dbs
admin  (empty)
local  0.078GB
test   (empty)
> show databases
admin  (empty)
local  0.078GB
test   (empty)

admin i test estan buides, i mentre estiguin buides no hi haurà cap fitxer en el directori. A local hi ha informació perquè de fet és on es guarden els logs.

Per canviar la base de dades

> use mydb

I si no existeix, es crea. De fet es crea l'àlies. Fins que no hi guardi cap dada (col.lecció o document) no es crearà físicament la base de dades.

Per saber la base de dades on estic:

> db
test

Ajuda en línia:


Finalment, torno a instal.lar manualment el servei, i el marco per fer un inici automàtic. El proper dia ja tindré arrencat el servidor de Mongo només arrencada la màquina.

Ara ens connectarem al servidor Mongo, amb el client mongo.exe.
<pre>
mongo --help

Hem de dir a quin servidor, per quin port, i a quina base de dades ens volem connectar. Per defecte, ens connectem a test.

mongo
> exit

mongo --host localhost --port 27017
>

La versió curta
mongo localhost:27017
>

I ens connectem a una altra màquina
mongo --host 192.168.20.131 --port 27017

Ens connectem a la màquina local pel port per defecte.

Per especificar la base de dades a la que ens volem connectar, tres opcions:

mongo test
mongo localhost/test
mongo localhost:27017/test

The .mongorc.js file is executed after the mongo shell is started, so if you set a default here it will override a database specified on the command line.

Crearem l'arxiu .mongorc.js (ocult) dins la nostra carpeta d'usuari. És un script d'arrencada.

Per tant, en la shell utilitzarem bàsicament Javascript per interactuar amb Mongo. Dins el fitxer .mongorc.js:

function saluda() {
	print("Hola!");
}

La idea és que el fitxer .mongorc.js es llegeixi per defecte Una altra possibilitat_

mongo --shell "C:\Users\mongodb-m\.mongorc.js"

I una altra possibilitat:

> load("C:/Users/mongodb-m/.mongorc.js")

Per mirar quines bases de dades tinc:

> show dbs
admin  (empty)
local  0.078GB
test   (empty)
> show databases
admin  (empty)
local  0.078GB
test   (empty)

admin i test estan buides, i mentre estiguin buides no hi haurà cap fitxer en el directori. A local hi ha informació perquè de fet és on es guarden els logs.

Per canviar la base de dades

> use mydb

I si no existeix, es crea. De fet es crea l'àlies. Fins que no hi guardi cap dada (col.lecció o document) no es crearà físicament la base de dades.

Per saber la base de dades on estic:

> db
test

Ajuda en línia:

> db.help()
DB methods:
        db.adminCommand(nameOrDocument) - switches to 'admin' db, and runs c
        db.auth(username, password)
        db.cloneDatabase(fromhost)
        db.commandHelp(name) returns the help for the command
        db.copyDatabase(fromdb, todb, fromhost)
        db.createCollection(name, { size : ..., capped : ..., max : ... } )
...

Sessió 2. 14-oct-2014

A part de la shell, es pot treballar amb alguna eina gràfica multiplataforma com ara Robomongo.

Nosaltres treballarem en paral.lel de les dues maneres.

c:\> mongo -> per defecte es connecta a la base de dades test La meva IP és 192.168.20.218. La del company és: 192.168.20.129

D'entrada, les úniques bd que existeixen són admin i local.

Des de Robomongo, podem fer open shell sobre una connexió, i d'aquesta manera podem veure les comandes que s'executaran per sota.

En la consola la paraula clau important és db, que apunta a la bd per defecte.

> db
test

En el Robomongo, Ctrl-Enter per tal d'executar una comanda de la shell. Amb Ctrl-Z podem recuperar les comandes que he executat anteriorment.

> db.help()

utilitzem use per canviar entre bases de dades.

Per començar a crear dades i col.leccions utilitzarem mongoimport. Partirem de documents JSON que volem carregar i que representa les dades que volem restaurar en el nostre servidor de mongo (que s'han exportat prèviament amb mongoexport). També es podria partir de fitxers CSV o TSV (tabuladors).

mongoimport està pensat per càrrega puntual de col.leccions. No és una eina per fer còpies de seguretat.

C:\> mongoimport --help
C:\> mongoimport --host localhost:27017 --db twitter --collection tweets --type json --file "D:\tweets.json" --drop --stopOnError

connected to: localhost:27017
2014-10-14T10:12:49.553+0200 dropping: twitter.tweets
2014-10-14T10:12:52.001+0200            Progress: 17468271/92307954     18%
2014-10-14T10:12:52.016+0200                    9700    3233/second
2014-10-14T10:12:55.002+0200            Progress: 42435022/92307954     45%
2014-10-14T10:12:55.002+0200                    23600   3933/second
2014-10-14T10:12:58.024+0200            Progress: 60890060/92307954     65%
2014-10-14T10:12:58.024+0200                    33900   3766/second
2014-10-14T10:13:01.010+0200            Progress: 84175208/92307954     91%
2014-10-14T10:13:01.010+0200                    46900   3908/second
2014-10-14T10:13:01.865+0200 check 9 51428
2014-10-14T10:13:01.866+0200 imported 51428 objects

Els arxius a importar han d'estar codificats amb UTF-8 per tal de què no hi hagi problemes.

Com que la base de dades twitter no existeix, es crearà. Recordem que tot és case-sensitive. Si la col.lecció no existeix, es crea. Hem d'indicar el format d'arxiu que volem importar (JSON, CSV, TSV,...)

--drop: si la col.lecció ja existeix, s'elimina i es torna a crear. --upsert: si ja existeix la col.lecció es pot actualitzar la informació a mida que es van afegint les noves dades. S'ha d'especificar en base a què vols actualtizar.

El format JSON en principi serà un objecte per fila. Però també podria ser un array d'objectes: tenim un array i els elements de l'array són els objectes que volem inserir. En aquest cas utilitzaríem l'opció jsonArray


S'han importat més de 51000 objectes que es correspon a tweets.

mongoimport --host localhost:27017
 --db geography
 --collection zips
 --type json
 --file "D:\zips.json" 
 --drop
 --stopOnError
	
mongoimport --host localhost:27017 --db geography --collection zips --type json --file "D:\zips.json"  --drop --stopOnError
mongoimport --host localhost:27017 --db geography --collection countries --type json --file "D:\countries.json"  --drop --stopOnError
mongoimport --host localhost:27017 --db geography --collection countries --type json --file "D:\countries.json"  --drop --stopOnError

connected to: localhost:27017
2014-10-14T10:22:59.970+0200 dropping: geography.countries
exception:BSON representation of supplied JSON is too large: code FailedToParse: FailedToParse: Expecting '{': offset:0 of:[
2014-10-14T10:22:59.997+0200 check 0 0
2014-10-14T10:22:59.999+0200 imported 0 objects
encountered 1 error(s)

falta el paràmetre --jsonArray, perquè el format del fitxer JSON és amb Array:

mongoimport --host localhost:27017 --db geography --collection countries --type json --file "D:\countries.json"  --drop --stopOnError --jsonArray
mongoimport --host localhost:27017 --db university --collection books --type json --file "D:\books.json"  --drop --stopOnError
mongoimport --host localhost:27017 --db university --collection scores --type json --file "D:\scores.json"  --drop --stopOnError

Encara ens falta la col.lecció students, que té format CSV.

mongoimport --host localhost:27017 --db university --collection students --type csv --file "D:\students.csv"  --drop --stopOnError --headerline --ignoreBlanks --fields "firstname,lastname1,lastname2,dni,gender,email,phone,phone_aux,birth_year"

connected to: localhost:27017
2014-10-14T10:45:42.718+0200 dropping: university.students
2014-10-14T10:45:43.072+0200 check 9 3244
2014-10-14T10:45:43.105+0200 imported 3243 objects

L'opció --headerline és per dir que la primera línia conté els noms dels atributs.

L'opció --ignoreBlanks és per dir que els atributs buits no s'assignin a l'objecte.

I finalment, amb l'opció --fields hem de donar la llista dels camps que volem carregar, i en quin ordre (camps separats per comes i sense espais en blanc).

Utilitzar el format CSV és desaconsellable, perquè està limitat. Amb CSV un objecte només pot tenir atributs primitius (números, strings, ...). No pot ser que un atribut sigui alhora un objecte amb d'altres atributs.

Si mirem la col.lecció books, veiem que author i tags són arrays (doncs un llibre pot tenir diversos autors, i un llibre diferents tags). Això no ho podem fer si importem des d'un document CSV.

> show dbs
admin       (empty)
geography   0.078GB
local       0.078GB
mydb        (empty)
twitter     0.203GB
university  0.078GB
db.listCommands()

La manera d'executar les comandes llistades:

db.runCommand(...)

i a runCommand se li ha de passar com a paràmetre un objecte en format json.

db.runCommand({"listCommands":1})

i obtenim la mateixa informació que abans obtenint la informació en format json.

Si vull saber com està implementada aquesta funció amb Javascript:

> db.listCommands -> sense parèntesi

function () {
    var x = this.runCommand("listCommands");
    for (var name in x.commands) {
        var c = x.commands[name];
        var s = name + ": ";
        switch (c.lockType) {
          case -1:
            s += "read-lock";
            break;
          case 0:
            s += "no-lock";
            break;
          case 1:
            s += "write-lock";
            break;
          default:
            s += c.lockType;
        }
        if (c.adminOnly) {
            s += " adminOnly ";
        }
        if (c.adminOnly) {
            s += " slaveOk ";
        }
        s += "\n  ";
        s += c.help.replace(/\n/g, "\n  ");
        s += "\n";
        print(s);
    }
}

Per llistar les bases de dades, hem de pensar que show dbs o show databases és un àlies que en realitat fa:

> use admin
switched to db admin
> db.runCommand({"listDatabases":1})
{
        "databases" : [
                {
                        "name" : "geography",
                        "sizeOnDisk" : 83886080,
                        "empty" : false
                },
                {
                        "name" : "local",
                        "sizeOnDisk" : 83886080,
                        "empty" : false
                },
                {
                        "name" : "twitter",
                        "sizeOnDisk" : 218103808,
                        "empty" : false
                },
                {
                        "name" : "university",
                        "sizeOnDisk" : 83886080,
                        "empty" : false
                },
                {
                        "name" : "admin",
                        "sizeOnDisk" : 1,
                        "empty" : true
                },
                {
                        "name" : "mydb",
                        "sizeOnDisk" : 1,
                        "empty" : true
                }
        ],
        "totalSize" : 469762048,
        "ok" : 1
}

Si vull executar una comanda com a administrador:

> db.adminCommand(...)
> db.adminCommand({"listDatabases":1})

Anem a mirar les col.leccions d'una bd, de diferents maneres:

> use university
> show collections -> alies1
> show tables	-> alies2

> db.getCollectionNames() -> la forma nadiua
[ "books", "scores", "students", "system.indexes" ]

> db.stats()
{
        "db" : "university",
        "collections" : 5,
        "objects" : 3786,
        "avgObjSize" : 288.2282091917591,
        "dataSize" : 1091232,
        "storageSize" : 3678208,
        "numExtents" : 14,
        "indexes" : 3,
        "indexSize" : 147168,
        "fileSize" : 67108864,
        "nsSizeMB" : 16,
        "dataFileVersion" : {
                "major" : 4,
                "minor" : 5
        },
        "extentFreeList" : {
                "num" : 0,
                "totalSize" : 0
        },
        "ok" : 1
}

Anem a trastejar una mica. La base de dades dummy no exiteix. Creo la col.lecció records. Inserto un document.

> use dummy
> db.records.insert({"_id":1, "name":"Joan", "ranking":Math.random()}) 
> show collections
> show databases

Fixem-nos que tinc accés en qualsevol moment a la llibreria de Javascript.

Hi ha una altra forma de crear una col.lecció, de forma explícita.

db.createCollection("test")

La gràcia de createCollection és passar-li paràmetres.

Una capped collection és la que té un límit de creixement, quant a tamany o quant a número màxim de documents.

db.createCollection("logs", {"capped":true, "size":5248000, "max":5000})

no admet un tamany d'arxiu de més de 5248000 bytes (obligatori si faig capped=true) i més de 5000 documents (opcional)

for (var i=0; i<=7000; i++) {
   db.logs.insert({"_id": i, "ranking":Math.random()});
}

Si mirem el document, veiem que ha manat el número màxim de documents. Es mostren els valors del 2000 al 7000, que són 5000 documents.

Anem a renombrar una col.lecció:

db.logs.renameCollection("records", 1) 

El 1 és per dir-li que si existeix, carrega-te-la.

També:

> use admin
> db.runCommand({"renameCollection":"dummy.records", "to":"twitter.records", "dropTarget":true})

és molt més potent, perquè hem mogut la col.lecció a una altra base de dades. També:

> db.adminCommand({"renameCollection":"twitter.records", "to":"dummy.records", "dropTarget":true})

Per eliminar una col.lecció:

> use twitter
> db.records.drop() -> de fet és un àlies

> use twitter
> db.runCommand ({"drop":"records"})

Finalment per eliminar una base de dades. Anem a eliminar dummy:

> db
dummy

> db.dropDatabase()
{ "dropped" : "dummy", "ok" : 1 }

evidentment s'han eliminat totes les col.leccions que contenia.

Eina copyTo:

> use twitter
switched to db twitter
> show collections
system.indexes
tweets
> db.tweets.copyTo("tweets.bsckup")
51428
> use twitter
> db.tweets.backup.drop()

Fer consultes amb find.

Sobre una col.lecció apliquem un criteri, i després un modificador sobre els resultats:

db.users.find({ age: { $gt:18} }).sort({age:1})

Les consultes només es fan sobre una col.lecció. No hi ha JOINs com en bases de dades relacionades. El que passa és que la col.lecció ja pot tenir una estructura complicada d'informació.

> use university
> db.students.find() -> em mostra el format json brut
> db.students.find().pretty() -> em mostra el format json ben estructurat
També:
> db.students.find({})

Estudiants que han nascut el 1980:

> db.students.find({"birth_year":1980})
...
> db.students.find({"birth_year":1980}).count()
94

També es podria fer:

> db.students.count({"birth_year":1980})

ProjecciÓ. </pre> db.users.find({criteria}, {projection}) </pre> la projecció és el que volem veure, o el que no volem veure, però no barrejar-ho. Ara bé, el _id sempre el veiem.

> db.students.find({"birth_year":1980}, {"dni":true})
{ "_id" : ObjectId("543ce2b60dd38f399584c571"), "dni" : "05270652H" }
{ "_id" : ObjectId("543ce2b60dd38f399584c57e"), "dni" : "09190721J" }
...

> db.students.find({"birth_year":1980}, {"dni":true, "_id":false})
{ "dni" : "05270652H" }
{ "dni" : "09190721J" }
...

El _id és l'únic que podem dir aquests sí..., però el _id no...

Sessió 3. 21-oct-2014

mongodb_qrc_booklet.pdf. xuleta de queries. Cercar per Queries and What They Match

> use twitter
> show tables

> db.tweets.count()
51428

Per saber què podem fer a nivell de col.lecció:

db.collection.help()
db.collection.find.help()

En aquesta sessió ens centrarem en el find per fer consultes, i també el Aggregation Framework que és molt més potent.

Anem a fer un exercici. Tenim la col.lecció bios penjada a Mongolab (un servidor gratis de que podem utilitzar a classe si creem un compte). L'exportarem amb mongoexport a una base de dades local (amb format JSON o CSV), i després la farem servir.

$ mongoexport --help

Les dades que necessitarem per fer la connexió són:

Per defecte els servidors de mongo no estan autenticats. Però aquest sí, hem de ficar usuari/pwd.

$ mongoexport -h ds039550.mongolab.com --port 39550 -d university -c bios -u demo -p demo -o bios.json
connected to: ds039550.mongolab.com:39550
exported 10 records

Hi ha altres opcions, com ara passar-lo una consulta per filtrar, limitar el número de resultats, ordenar la sortida.

L'exportació pot ser en format array, o bé un document per fila (opció per defecte).

  --jsonArray                           output to a json array rather than one
                                        object per line

El fitxer resultant:

{ "_id" : 1, "name" : { "first" : "John", "last" : "Backus" }, "birthYear" : 1924, "deathYear" : 2007, "contribs" : [ "Fortran", "ALGOL", "Backus-Naur Form", "FP" ], "awards" : [ { "award" : "W.W. McDowell Award", "year" : 1967, "by" : "IEEE Computer Society" }, { "award" : "National Medal of Science", "year" : 1975, "by" : "National Science Foundation" }, { "award" : "Turing Award", "year" : 1977, "by" : "ACM" }, { "award" : "Draper Prize", "year" : 1993, "by" : "National Academy of Engineering" } ] }
{ "_id" : 2, "name" : { "first" : "John", "last" : "McCarthy" }, "birthYear" : 1927, "deathYear" : 2011, "contribs" : [ "Lisp", "Artificial Intelligence", "ALGOL" ], "awards" : [ { "award" : "Turing Award", "year" : 1971, "by" : "ACM" }, { "award" : "Kyoto Prize", "year" : 1988, "by" : "Inamori Foundation" }, { "award" : "National Medal of Science", "year" : 1990, "by" : "National Science Foundation" } ] }
{ "_id" : 3, "name" : { "first" : "Grace", "last" : "Hopper" }, "title" : "Rear Admiral", "birthYear" : 1915, "deathYear" : 1992, "contribs" : [ "UNIVAC", "compiler", "FLOW-MATIC", "COBOL" ], "awards" : [ { "award" : "Computer Sciences Man of the Year", "year" : 1969, "by" : "Data Processing Management Association" }, { "award" : "Distinguished Fellow", "year" : 1973, "by" : " British Computer Society" }, { "award" : "W. W. McDowell Award", "year" : 1976, "by" : "IEEE Computer Society" }, { "award" : "National Medal of Technology", "year" : 1991, "by" : "United States" } ] }
{ "_id" : 4, "name" : { "first" : "Kristen", "last" : "Nygaard" }, "birthYear" : 1906, "deathYear" : 2002, "contribs" : [ "OOP", "Simula" ], "awards" : [ { "award" : "Rosing Prize", "year" : 1999, "by" : "Norwegian Data Association" }, { "award" : "Turing Award", "year" : 2001, "by" : "ACM" }, { "award" : "IEEE John von Neumann Medal", "year" : 2001, "by" : "IEEE" } ] }
{ "_id" : 5, "name" : { "first" : "Ole-Johan", "last" : "Dahl" }, "birthYear" : 1926, "deathYear" : 2002, "contribs" : [ "OOP", "Simula" ], "awards" : [ { "award" : "Rosing Prize", "year" : 1999, "by" : "Norwegian Data Association" }, { "award" : "Turing Award", "year" : 2001, "by" : "ACM" }, { "award" : "IEEE John von Neumann Medal", "year" : 2001, "by" : "IEEE" } ] }
{ "_id" : 6, "name" : { "first" : "Guido", "last" : "van Rossum" }, "birthYear" : 1931, "contribs" : [ "Python" ], "awards" : [ { "award" : "Award for the Advancement of Free Software", "year" : 2001, "by" : "Free Software Foundation" }, { "award" : "NLUUG Award", "year" : 2003, "by" : "NLUUG" } ] }
{ "_id" : 7, "name" : { "first" : "Dennis", "last" : "Ritchie" }, "birthYear" : 1956, "deathYear" : 2011, "contribs" : [ "UNIX", "C" ], "awards" : [ { "award" : "Turing Award", "year" : 1983, "by" : "ACM" }, { "award" : "National Medal of Technology", "year" : 1998, "by" : "United States" }, { "award" : "Japan Prize", "year" : 2011, "by" : "The Japan Prize Foundation" } ] }
{ "_id" : 8, "name" : { "first" : "Yukihiro", "aka" : "Matz", "last" : "Matsumoto" }, "birthYear" : 1941, "contribs" : [ "Ruby" ], "awards" : [ { "award" : "Award for the Advancement of Free Software", "year" : "2011", "by" : "Free Software Foundation" } ] }
{ "_id" : 9, "name" : { "first" : "James", "last" : "Gosling" }, "birthYear" : 1965, "contribs" : [ "Java" ], "awards" : [ { "award" : "The Economist Innovation Award", "year" : 2002, "by" : "The Economist" }, { "award" : "Officer of the Order of Canada", "year" : 2007, "by" : "Canada" } ] }
{ "_id" : 10, "name" : { "first" : "Martin", "last" : "Odersky" }, "contribs" : [ "Scala" ] }

Ara he d'importar aquestes dades:

$ mongoimport --host localhost:27017 --db university --collection bios --type json --file "C:\Users\mongodb-m\bios.json" --drop --stopOnError
connected to: localhost:27017
2014-10-21T09:42:19.228+0200 dropping: universit
2014-10-21T09:42:19.245+0200 imported 10 objects
> use university
switched to db university
> show collections
bios
books
scores
students
system.indexes
> db.bios.count()

Documentació oficial de MongoDB:

find() admet dos paràmetres: (tot en format JSON)

Després es pot jugar amb els modificadors del cursor, per ex, per ordenar.

Millor posar pretty al final:

db.students.find()
db.students.find().pretty()
db.students.find(
{ birth_year: { $gt:1970 } },
{ firstname: 1, email: 1 }
).limit(5).pretty()

      "_id" : ObjectId("543ce2b60dd38f399584c569"),
      "firstname" : "Joan Ignasi",
      "email" : "litke@talktalk.net"


      "_id" : ObjectId("543ce2b60dd38f399584c56a"),
      "firstname" : "Tania",
      "email" : "owarne000s@talk21.com"


      "_id" : ObjectId("543ce2b60dd38f399584c56b"),
      "firstname" : "Guillermo",
      "email" : "seo@webindustry.co.uk"


      "_id" : ObjectId("543ce2b60dd38f399584c56d"),
      "firstname" : "Isabel",
      "email" : "robert.douglas3@virgin.net"


      "_id" : ObjectId("543ce2b60dd38f399584c56e"),
      "firstname" : "Carlos",
      "email" : "dan@webindustry.co.uk"
	 

Només vull veure el dni (projecció) de tots els estudiants: (també mostrarà el id).

db.students.find( { }, { "dni": 1 } ).pretty()

> db.students.find( { }, { "dni": 1 } )
{ "_id" : ObjectId("543ce2b60dd38f399584c569"), "dni" : "01101634A" }
{ "_id" : ObjectId("543ce2b60dd38f399584c56a"), "dni" : "01749917P" }
{ "_id" : ObjectId("543ce2b60dd38f399584c56b"), "dni" : "02656865C" }
{ "_id" : ObjectId("543ce2b60dd38f399584c56c"), "dni" : "02666572K" }
{ "_id" : ObjectId("543ce2b60dd38f399584c56d"), "dni" : "02879308F" }
{ "_id" : ObjectId("543ce2b60dd38f399584c56e"), "dni" : "03448742F" }
{ "_id" : ObjectId("543ce2b60dd38f399584c56f"), "dni" : "05087108Z" }
{ "_id" : ObjectId("543ce2b60dd38f399584c570"), "dni" : "05206087Z" }
{ "_id" : ObjectId("543ce2b60dd38f399584c571"), "dni" : "05270652H" }
{ "_id" : ObjectId("543ce2b60dd38f399584c572"), "dni" : "05328197V" }
{ "_id" : ObjectId("543ce2b60dd38f399584c573"), "dni" : "05393518H" }
{ "_id" : ObjectId("543ce2b60dd38f399584c574"), "dni" : "05412086W" }
{ "_id" : ObjectId("543ce2b60dd38f399584c575"), "dni" : "05628736S" }
{ "_id" : ObjectId("543ce2b60dd38f399584c576"), "dni" : "05961345K" }
{ "_id" : ObjectId("543ce2b60dd38f399584c577"), "dni" : "07048302K" }
{ "_id" : ObjectId("543ce2b60dd38f399584c578"), "dni" : "07244064F" }
{ "_id" : ObjectId("543ce2b60dd38f399584c579"), "dni" : "07252345P" }
{ "_id" : ObjectId("543ce2b60dd38f399584c57a"), "dni" : "07500482K" }
{ "_id" : ObjectId("543ce2b60dd38f399584c57b"), "dni" : "07517306D" }
{ "_id" : ObjectId("543ce2b60dd38f399584c57c"), "dni" : "08838930F" }
Type "it" for more
>

</pre> Per no mostrar el id: "_id":0

db.students.find( { }, { "_id":0, "dni": 1 } ).pretty()

{ "dni" : "01101634A" }
{ "dni" : "01749917P" }
{ "dni" : "02656865C" }
{ "dni" : "02666572K" }
{ "dni" : "02879308F" }
{ "dni" : "03448742F" }
{ "dni" : "05087108Z" }
{ "dni" : "05206087Z" }
{ "dni" : "05270652H" }
{ "dni" : "05328197V" }
{ "dni" : "05393518H" }
{ "dni" : "05412086W" }
{ "dni" : "05628736S" }
{ "dni" : "05961345K" }
{ "dni" : "07048302K" }
{ "dni" : "07244064F" }
{ "dni" : "07252345P" }
{ "dni" : "07500482K" }
{ "dni" : "07517306D" }
{ "dni" : "08838930F" }
Type "it" for more
>

Exercicis:

*Buscar los estudiantes de género masculino
<pre>
db.students.find( { "gender": "H" } ).pretty()

//Buscar los estudiantes de género femenino
db.students.find( { "gender": "F" } ).pretty()

//Buscar los estudiantes nacidos en el año 1993
db.students.find( { "birth_year": 1993 }).pretty()

// Buscar los libros cuyo precio esté expresado en $
db.books.find( { "price.currency": "USD" }).pretty()

//Buscar los estudiantes de génermo masculino y nacidos en el año 1993
db.students.find( { "gender": "H" , "birth_year": 1993 }).pretty()
db.students.find( { "gender": "H" , "birth_year": 1993 }).count()

Una altra manera amb l'atribut $and: -> posem un array amb les condicions a complir
db.students.find( { "$and": [{"gender": "H"}, {"birth_year": 1993}] }).pretty()

//Buscar los estudiantes nacidos en la década de los 90 
operadors  a la pàg 141 de la transparència. $gt (greater than), $gte (greater than or equal), $lt, $lte
db.students.find( {"birth_year": {$gte:1990}, "birth_year": {$lt:2000}} ).pretty()
3243
db.students.find( { "$and": [{"birth_year": {$gte:1990}}, {"birth_year": {$lt:2000}}] }).pretty()
387

Veiem que dóna resultats diferents. La versió separada per comes no es pot utiilitzar quan el mateix atribut interve en vàries de les condicions.
Per tant, la segona és la correcta.

//Buscar los estudiantes de género masculino nacidos en la década de los 90
db.students.find( { "gender": "H", "birth_year": {$gte:1990}, "birth_year": {$lt:2000}} ).pretty()
Fent count()
2895

db.students.find( { "$and": [ {"gender": "H"}, {"birth_year": {$gte:1990}}, {"birth_year": {$lt:2000}}] }).pretty()
339
o bé el que seria equivalent:
db.students.find( { "$and": [ {"gender": "H"}, {"birth_year": {$gte:1990, $lt:2000}}] }).pretty()
339

Veiem que dóna resultats diferents. La versió separada per comes no es pot utiilitzar quan el mateix atribut interve en vàries de les condicions.
Per tant, la segona és la correcta.


//Buscar los estudiantes de género femenino nacidos en la década de los 90
db.students.find( { "$and": [ {"gender": "M"}, {"birth_year": {$gte:1990}}, {"birth_year": {$lt:2000}}] }).pretty()

amb count(): 48

//Buscar los estudiantes nacidos en la década de los 80
db.students.find( { "$and": [{"birth_year": {$gte:1980}}, {"birth_year": {$lt:1990}}] }).pretty()
936

//Buscar los estudiantes de género masculino nacidos en la década de los 80
db.students.find( { "$and": [{"gender": "H"}, {"birth_year": {$gte:1980}}, {"birth_year": {$lt:1990}}] }).pretty()
851

//Buscar los estudiantes que no han nacido en el año 1985
db.students.find( { "birth_year": {$ne:1990} }).pretty()
3145

//Buscar aquellos estudiantes que hayan nacido en el año 1970, 1980 o 1990
db.students.find( { "$or": [{"birth_year": 1970}, {"birth_year": 1980}, {"birth_year": 1990}] }).pretty()
293

//Buscar aquellos estudiantes que no hayan nacido en el año 1970, 1980 o 1990
db.students.find( { "$and": [{"birth_year": {$ne:1970}}, {"birth_year": {$ne:1980}}, {"birth_year": {$ne:1990}}] }).pretty()
2950

//Buscar los estudiantes nacidos en año par
db.students.find({"birth_year": {$mod: [2,0]} })
1684
//Buscar los estudiantes nacidos en año impar
db.students.find({"birth_year": {$mod: [2,1]} })
1559

//Buscar els estudiants en any parell de la dècada dels 70 de gènere masculí:
db.students.find( {"$and": [{"birth_year": {$mod: [2,0]}} , {"birth_year": {$gte:1970}}, {"birth_year": {$lt:1980}}, {"gender": "H"} ] })
db.students.find( {"$and": [{"birth_year": {$mod: [2,0], $gte:1970, $lt:1980}}, {"gender": "H"} ] })
403

//Buscar los estudiantes que tengan teléfono auxiliar (phone_aux)
operador $exists
db.students.find({"phone_aux": {$exists:1}}).pretty()
679

//Buscar los estudiantes que no tengan segundo apellido
db.students.find({"lastname2": {$exists:0}}).pretty()
421

//Buscar los estudiantes que tengan teléfono auxiliar y solo un apellido
db.students.find({"phone_aux": {$exists:1}, "lastname2": {$exists:0}}).pretty()
71

//Cercar tots els llibres que dins els tags conté html5.
Hem de tenir present que tags és un array d'elements
db.books.find({"tags": "html5"}).pretty()

//Cercar tots els llibres que dins els tags conté html5, html, css o css3.
podríem utilitzar $or, però utilitzarem $in (després de tags obrim un subdocument)
db.books.find({"tags": {$in:["html5","html","css","css3"]}}).pretty()
db.books.find( {$or:[{"tags":"html5"},{"tags":"html"},{"tags":"css"},{"tags":"css3"}]}).pretty()
14

//Cercar tots els llibres que dins els tags no continguin html5, html, css o css3.
db.books.find({"tags": {$nin:["html5","html","css","css3"]}}).pretty()
db.books.find( {$and:[{"tags":{$ne:"html5"}},{"tags":{$ne:"html"}},{"tags":{$ne:"css"}},{"tags":{$ne:"css3"}}]}).count()
319

//cercar els llibres que continguin els tags 'programming' i 'ageile'
db.books.find({"tags": {$all:["programming","agile"]}}).pretty()
db.books.find( {$and:[{"tags":"programming"},{"tags":"agile"}]}).pretty()
16

//cercar els estudiants que no han nascut el 1985

3147

//cercar els estudiants que han nascut el 70, 80, 90
db.students.find({"birth_year": {$in:[1970, 1980, 1990]}}).count() 
293

//cercar els estudiants que no han nascut el 70, 80, 90

//cercar els llibres escrits per dos autors
db.books.find({"author": {$size: 2}}).count()
79

//Buscar los estudiantes cuyo dni empiece por letra
utilitzem expressions regulars (Javascript). Important
db.students.find({"dni": {$regex: /^[A-Z]{1}/, $options: "i"}}).count()
db.students.find({"dni": {$regex: /^[A-Z]{1}/i}}).count()
db.students.find({"dni": /^[A-Z]{1}/i}).count()
248

L'opció i és que no importi majúscules i minúscules

//Buscar los estudiantes cuyo dni empiece y termine por letra
db.students.find({"dni": /^[A-Z]{1}.*[A-Z]{1}$/i}).count()
244
els que comencen i acaben amb lletra són els NULL, entre d'altres

//Mirem els que tenen NULL com a dni:
db.students.find({"dni": {$eq:"NULL"}}).count()
8

Anem a fer un update d'aquests registres.
db.collection.update (query, update, options)
Per defecte, el update només actualitza el primer element que troba. Si volem que actualitzi tots els documents, hem de passar l'opció multi.

Per eliminar un camp s'ha de fer un unset.
//eliminem el camp dels estudiants amb DNI NULL
db.students.update ({"dni":"NULL"}, {$unset: {"dni": ""}}, {"multi": true} )

db.students.update ({"dni":"NULL"}, 
                    {$unset: {"dni": ""}}, 
					{"multi": true}
					)
					
//cercar els estudiants que no tenen dni assignat:
db.students.find({"dni": {$exists:false}}).count()
són els 8 que abans eren NULL

//Buscar los estudiantes cuyo teléfono o tel aux empiece por 622.
db.students.find({$or: [{"phone": /^622/}, {"phone_aux": /^622/}]}).count()
db.students.find({$or: [{"phone": /^6{1}2{1}2{1}/}, {"phone_aux": /^6{1}2{1}2{1}/}]}).count()
db.students.find({$or: [{"phone": /^6{1}2{2}/}, {"phone_aux": /^6{1}2{2}/}]}).count()
201

//Buscar los estudiantes cuyo email termine en .net
db.students.find({"email": /\.net$/i}).count()
47

//Buscar los estudiantes cuyo email termine en .org
db.students.find({"email": /\.org$/i}).count()
16

//Buscar los estudiantes cuyo nombre empiece por vocal
db.students.find({"firstname": /^[aeiouàáèéíòóúù]{1}/i}).count()
760
MongoDB no suporta collation

//Buscar los estudiantes con nombre más largo de 13 caracteres
db.students.find({"firstname": /^.{13,}$/}).count()
138

//Buscar los estudiantes con 3 o más 4en su nombre
db.students.find({"firstname": /^.*[aeiouàáèéíòóúù]{1}.*[aeiouàáèéíòóúù]{1}.*[aeiouàáèéíòóúù]{1}.*$/i}).count()

########################################################################
//Buscar aquellos desarrolladores que sigan vivos


//Buscar aquellos desarrolladores que hayan obtenido un premio en el año 2002


//Buscar aquellos desarrolladores que hayan obtenido algun premio entre el año 1995 y 2000


//Buscar aquellos desarrolladores que hayan obtenido exactamente 3 premios


//Buscar aquellos desarrolladores que hayan realizado contribuciones en OOP


//Buscar aquellos desarrolladores que hayan realizado contribuciones en OOP o Java


//Buscar aquellos desarrolladores que hayan realizado contribuciones en OOP y Simula


//Mostrar todos los tweets publicados usuarios con más de 100 followers


//Mostrar todos los tweets publicados por el usuario 'behcolin'


//Mostrar aquellos paises cuya moneda es el euro


//Mostrar aquellos paises donde el español es el idioma nativo


//Mostrar aquellos paises donde el español es lengua oficial


//Mostrar los tweets publicados por usuarios con el mismo numero de seguidores y amigos



//Mostrar el número de tweets publicados por cada usuario

Sessió 4. 28-oct-2014

Recordatori per copiar de la shell de windows: selecciono, i Maj + botó mig del ratolí.

Avui veurem el Aggregation Framework, que és molt més potent, i és bastant recent. Era una demanda que hi havia, un framework de fer consultes més fàcil i potent del que fins ara es feia, que era mapReduce(), que estava basat en funcions de Javascript i era bastant complicat.

La idea és definir un Aggregation pipeline o workflow. Partim d'una col.lecció inicial, i la fem passar per diferents fases o estats, per obtenir finalment un resulat. A cada fase li apliquem un operador d'agregació.

Farem servir dues noves col.leccions, i per importar-les no farem servir export i import, sinó que farem servir mongodump i mongorestore. mongodump es pot fer servir per fer còpies de seguretat (totes les bases de dades, base de dades específica, una sola col.lecció,...). El resultat que obtenim és en format BSON (format binari).

base de dades imdb (Internet Movie Database). Descarregarem aquesta base de dades del servidor remot que tenim configurat. Hi ha 3 col.leccions.

mongodump --host ds039550.mongolab.com --port 39550 --username demo --password demo --db imdb --out backup

mongodump --host ds039550.mongolab.com 
          --port 39550 --username demo 
		  --password demo 
		  --db imdb 
		  --out backup

S'ha creat la carpeta backup/, a dins hi ha la carpeta imdb/, i a dints hi ha un arxiu per cada col.lecció, en format bson. També s'ha creat arxius per als index.

      93 movies.metadata.json
  76.937 oscars.bson
      93 oscars.metadata.json
 246.062 people.bson
      93 people.metadata.json
     198 system.indexes.bson

Per fer una còpia completa de la bd:

mongodump --host localhost --port 27017 --out dump

Per fer la restauració de tot el servidor o d'una base de dades, es fa de forma similar. He d'indicar la ruta on estan els arxius binaris

mongorestore --host localhost --port 27017 --db imdb --drop ".\backup\imdb"

C:\Windows\system32>mongo
MongoDB shell version: 2.6.4
connecting to: test
> show databases
admin       (empty)
geography   0.078GB
imdb        0.078GB
local       0.078GB
twitter     0.453GB
university  0.078GB
>

I ara ja podem anar a la consola i assegurar-nos de què tenim la base de dades imdb

També restaurem la base de dades digg que ens proporcionen:

mongorestore --host localhost --port 27017 --db digg --drop "C:\Users\mongodb-m\digg"

2014-10-28T09:48:22.757+0100 C:\Users\mongodb-m\digg\stories.bson
2014-10-28T09:48:22.758+0100    going into namespace [digg.stories]
2014-10-28T09:48:22.759+0100     dropping
10000 objects found
2014-10-28T09:48:24.715+0100    Creating index: { key: { _id: 1 }, ns: "digg.sto
ries", name: "_id_" }
2014-10-28T09:48:24.717+0100    Creating index: { key: { diggs: 1 }, ns: "digg.s
tories", name: "diggs_1" }

> show databases
admin       (empty)
digg        0.078GB
geography   0.078GB
imdb        0.078GB
local       0.078GB
twitter     0.453GB
university  0.078GB

> use digg
> db.stories.find().pretty()

Abans d'utilitzar el aggregate()', abans es feia servir les funcions de Javascript mapReduce, però això és complicat i està obsolet. (és Javascript pur).

Amb el mètode aggregate() li passem un array, i cada element de l'array és informació sobre el pas/estat que s'ha de processar. Cada estat és un subdocument:

aggregate( [ {}, {}, {} ] ) 

Utilitzem com operadors en cadascun dels passos/estats (els operadors comencen per $):

En la carpeta Alumnos trobem la carpeta aggregation framework, que són els exercicis que anirem treballant. Recomanable utilitzar robomongo en comptes de la shell.

Primer de tot ens familiaritzem amb l'estructura de la col.lecció stories (de la base de dades digg).

db.stories.findOne()

/* 0 */
{
    "_id" : ObjectId("4ba267dc238d3ba3ca000001"),
    "href" : "http://digg.com/people/Jedi_believer_who_refused_to_remove_hood_gets_an_apology",
    "title" : "'Jedi' believer who refused to remove hood gets an apology!",
    "comments" : 153,
    "container" : {
        "name" : "Offbeat",
        "short_name" : "offbeat"
    },
    "submit_date" : 1268771801,
    "topic" : {
        "name" : "People",
        "short_name" : "people"
    },
    "promote_date" : 1268915964,
    "id" : "19970068",
    "media" : "news",
    "diggs" : 404,
    "description" : "Chris Jarvis is a member of the International Church of Jediism - based on the famous Star Wars films - whose doctrine states followers can wear hoods.",
    "link" : "http://www.dailymail.co.uk/news/article-1258365/Jedi-believer-wins-apology-Jobcentre-kicked-wearing-hood.html",
    "user" : {
        "name" : "babychen",
        "registered" : 1141570067,
        "fullname" : "Babychen Mathew",
        "icon" : "http://digg.com/users/babychen/l.png",
        "profileviews" : 24749
    },
    "status" : "popular",
    "shorturl" : [ 
        {
            "short_url" : "http://digg.com/d31Ln7s",
            "view_count" : 3682
        }
    ]
}
//Mostrar el número de recursos (podem dir-ne posts) publicados por cada usuario junto al número de comentarios y diggs obtenidos
La idea és que per cada usuari hem d'obtenir tots els posts/recursos publicats, i cada recurs tindrà un número de comentaris i diggs que s'han de sumar. Seria l'equivalent al GROUP BY de SQL. Per tant, utilitzaré $group. 

db.stories.aggregate ([{}])
El primer que faré és una agrupació, $group:
db.stories.aggregate ([{"$group": {_id: "$user.name"} }])
Utilitzarem un id per agrupar. De forma obligatòria hem de dir quin és l'atribut que utilitzarem com a id. I després passaré la informació que vull obtenir (pe ex, nom, cognoms,...). Utilitzaré com a _id el user.name. Ho ficaré amb $ ($user.name) doncs vull avaluar el valor d'aquest atribut.

{
    "result" : [ 
        {
            "_id" : "wabes2k"
        }, 
        {
            "_id" : "diggdangdong"
        }, 
        {
            "_id" : "trinityjr"
        }, 
        {
            "_id" : "Landthatilove"
        }, 
        {
            "_id" : "gmp24"
        }, 
...
		
Ara en aquesta agrupació vull posar més informació. Bàsicament sumar el número de posts.

db.stories.aggregate ([{"$group": {	_id: "$user.name", 
									"posts": {$sum: 1} } }])

{
    "result" : [ 
        {
            "_id" : "wabes2k",
            "posts" : 1
        }, 
        {
            "_id" : "diggdangdong",
            "posts" : 1
        }, 
        {
            "_id" : "trinityjr",
            "posts" : 2
        }, 
        {
            "_id" : "Landthatilove",
            "posts" : 2
        }, 
...
		
Per tant, ara ja sé el número de posts que ha publicat cada usuari. I ara vull sumar el total de comentaris i diggs:

db.stories.aggregate ([{"$group": {	_id: "$user.name", 
									"posts": {$sum: 1},
									"comments": {$sum: "$comments"} ,
									"diggs": {$sum: "$diggs"} } 
						}
					])

{
    "result" : [ 
        {
            "_id" : "wabes2k",
            "posts" : 1,
            "comments" : 366,
            "diggs" : 498
        }, 
        {
            "_id" : "diggdangdong",
            "posts" : 1,
            "comments" : 400,
            "diggs" : 1039
        }, 
...

Ara volem saber el valor màxim de puntuació en algun post, mínim valor de puntuació, i mitjana:

db.stories.aggregate ([{"$group": {	_id: "$user.name", 
									"posts": {$sum: 1},
									"comments": {$sum: "$comments"} ,
									"diggs": {$sum: "$diggs"},
									"max_diggs": {$max: "$diggs"}, 
									"min_diggs": {$min: "$diggs"},  
									"avg_diggs": {$avg: "$diggs"} } 
						}
					])
					
...
        {
            "_id" : "atomicpoet",
            "posts" : 5,
            "comments" : 1069,
            "diggs" : 4006,
            "max_diggs" : 2188,
            "min_diggs" : 320,
            "avg_diggs" : 801.2000000000001
        }, 
...

Ara anem a aplicar un altre estat a l'agregat. Anem a ordenar-ho per número de posts amb l'operador $sort.

db.stories.aggregate ([{"$group": {	_id: "$user.name", 
									"posts": {$sum: 1},
									"comments": {$sum: "$comments"} ,
									"diggs": {$sum: "$diggs"},
									"max_diggs": {$max: "$diggs"}, 
									"min_diggs": {$min: "$diggs"},  
									"avg_diggs": {$avg: "$diggs"} } 
						},
						{"$sort": {
							"posts": -1,
							"_id": 1}
						}
					])
					
{
    "result" : [ 
        {
            "_id" : "MrBabyMan",
            "posts" : 187,
            "comments" : 27538,
            "diggs" : 266837,
            "max_diggs" : 7958,
            "min_diggs" : 320,
            "avg_diggs" : 1426.935828877005
        }, 
        {
            "_id" : "LtGenPanda",
            "posts" : 169,
            "comments" : 21407,
            "diggs" : 208699,
            "max_diggs" : 6019,
            "min_diggs" : 335,
            "avg_diggs" : 1234.905325443787
        }, 
...

Interessa sovint que l'últim pas del workflow és dir-li que tot el resultat me'l guardi en una col.lecció. Es fa utiitzant $out:

db.stories.aggregate ([{"$group": {	_id: "$user.name", 
									"posts": {$sum: 1},
									"comments": {$sum: "$comments"} ,
									"diggs": {$sum: "$diggs"},
									"max_diggs": {$max: "$diggs"}, 
									"min_diggs": {$min: "$diggs"},  
									"avg_diggs": {$avg: "$diggs"} } 
						},
						{"$sort": {
							"posts": -1,
							"_id": 1}
						},
						{"$out": "userstats" }
					])

Resultat:					
{
    "result" : [],
    "ok" : 1
}					

I he obtingut la col.lecció userstats. És una mala pràctica en aquest cas utilitzar un guió baix, per ex user_stats.
//Mostrar para cada temática el número de recursos publicados junto a la lista de titulos
La temàtica principal és container.name (després farem servir la temàtica secundària que és topic). Fixem-nos bé que si fem un agrupament, amb SQL no podríem obtenir la llista de títol. En canvi, aquí sí que ho podem fer gràcies a l'operador $push, que fica els valors dins d'un array.

db.stories.aggregate 	([
							{
								"$group" : {
									"_id": "$container.name",
									"posts": {$sum: 1},
									"titles": {$push: "$title"}
								}
							}
						])

resultat:						
{
    "result" : [ 
        {
            "_id" : "Sports",
            "posts" : 697,
            "titles" : [ 
                "March Madness: Mastering the Art of Upsetology ", 
                "March Madness: Mastering the Art of Upsetology ", 
                "Former Jazz Center Arrested For Pot Between His Buttocks", 
                "Anyone Know The Name Of This Wrestling Move? (GIF)", 
                "10 Strange Athlete Superstitions", 
                "5 People In Your NCAA Pool", 
                "Olympic History: Who Has the Most Gold? (Graphic)",
...

I ara ordenem:

db.stories.aggregate 	([
							{
								"$group" : {
									"_id": "$container.name",
									"posts": {$sum: 1},
									"titles": {$push: "$title"}
								}
							},
							{
								"$sort": {
									"posts": -1,
									"_id": 1
								}
							}
						])
		
Resultats:
{
    "result" : [ 
        {
            "_id" : "World & Business",
            "posts" : 2002,
            "titles" : [ 
                "Health Care Nightmares", 
                "Loud sex enough for cops to search your home, court rules", 
                "National Debt Up $2 Trillion on Obama's Watch ", 
                "Son of Hamas - GQ.com",
...				
//Mostrar para cada categoría el número de recursos publicados junto a la lista de titulos

db.stories.aggregate 	([
							{
								"$group" : {
									"_id": "$topic.name",
									"posts": {$sum: 1},
									"titles": {$push: "$title"}
								}
							},
							{
								"$sort": {
									"posts": -1,
									"_id": 1
								}
							}
						])
//Mostrar per cada tema i categoria el número de recursos publicats.

$project fa referència a projecció, el que vull veure a la sortida.

db.stories.aggregate 	([
							{},
							{},
							{}
						])

db.stories.aggregate 	([
							{"$group": {} },
							{"$project": {} },
							{"$sort": {} }
						])

Fem servir un _id compost per fer l'agrupació. El _id està format per dos camps.

db.stories.aggregate 	([
							{"$group": {
								"_id": {"theme": "$container.name", "topic": "$topic.name"},
								"posts": {"$sum": 1},
								"avg_diggs": {"$avg": "$diggs"},
								"articles": {"$addToSet": 
												{
													"title": "$title",
													"link": "$href"
												}
											}
								
								} 
							},
							{"$project": {} },
							{"$sort": {} }
						])
						

De moment provem:

db.stories.aggregate 	([
							{"$group": {
								"_id": {"theme": "$container.name", "topic": "$topic.name"},
								"posts": {"$sum": 1},
								"avg_diggs": {"$avg": "$diggs"},
								"articles": {"$addToSet": 
												{
													"title": "$title",
													"link": "$href"
												}
											}
								
								} 
							}
						])
						
				
No volem el _id en la soritda (projecció):

db.stories.aggregate 	([
							{"$group": {
								"_id": {"theme": "$container.name", "topic": "$topic.name"},
								"posts": {"$sum": 1},
								"avg_diggs": {"$avg": "$diggs"},
								"articles": {"$addToSet": 
												{
													"title": "$title",
													"link": "$href"
												}
											}
								
								} 
							},
							{"$project": {
								"_id": false,
								"theme": "$_id.theme",
								"topic": "$_id.topic",
								"lposts": "$posts",
								"larticles": "$articles"
							} },
							{"$sort": {"posts": -1} }
						])
		
Resultat:
{
    "result" : [ 
        {
            "theme" : "Sports",
            "topic" : "Golf",
            "lposts" : 14,
            "larticles" : [ 
                {
                    "title" : "Natalie Gulbis Must Think Lincoln Was REALLY Skinny (Pic)",
                    "link" : "http://digg.com/golf/Natalie_Gulbis_Must_Think_Lincoln_Was_REALLY_Skinny_Pic"
                }, 
                {
                    "title" : "John Daly Tells the Golf Channel He Is 'Done' With Golf",
                   

Coses curioses que passen. He hagut de posar:

"lposts": "$posts",
"larticles": "$articles"

en comptes de

"posts": true,
"articles": true

per tal de què el theme i el topic surtin priner abans que els articles.

db.stories.aggregate 	([
							{"$group": {
								"_id": {"theme": "$container.name", "topic": "$topic.name"},
								"posts": {"$sum": 1},
								"avg_diggs": {"$avg": "$diggs"},
								"articles": {"$addToSet": 
												{
													"title": "$title",
													"link": "$href"
												}
											}
								
								} 
							},
							{"$project": {
								"_id": false,
								"theme": "$_id.theme",
								"topic": "$_id.topic",
								"num_posts": {"$size": "$articles"},
								"larticles": "$articles"
							} },
							{"$sort": {"posts": -1} }
						])
	
Resultat:
{
    "result" : [ 
        {
            "theme" : "Sports",
            "topic" : "Golf",
            "num_posts" : 10,
            "larticles" : [ 
                {
                    "title" : "Natalie Gulbis Must Think Lincoln Was REALLY Skinny (Pic)",
                    "link" : "http://digg.com/golf/Natalie_Gulbis_Must_Think_Lincoln_Was_REALLY_Skinny_Pic"
                }, 
...

Fem servir ara la base de dades imdb.

db.oscars.findOne()

{
    "_id" : ObjectId("54497c1fe57946c4b14be7fb"),
    "year" : 2013,
    "type" : "BEST-PICTURE",
    "movie" : {
        "id" : "1024648",
        "name" : "Argo"
    }
}

db.movies.findOne()

{
    "_id" : "0018379",
    "name" : "Seventh Heaven",
    "year" : 1927,
    "runtime" : 110,
    "genre" : "DR",
    "actors" : [ 
        {
            "id" : "0310980",
            "name" : "Janet Gaynor"
        }, 
        {
            "id" : "0268190",
            "name" : "Charles Farrell"
        }, 
        {
            "id" : "0054135",
            "name" : "Ben Bard"
        }, 
        {
            "id" : "0334581",
            "name" : "Albert Gran"
        }, 
        {
            "id" : "0124877",
            "name" : "David Butler"
        }
    ],
    "directors" : [ 
        {
            "id" : "0097648",
            "name" : "Frank Borzage"
        }
    ]
}
//Buscar aquellas películas realizadas por Tom Cruise
//Buscar aquellas películas dirigidas por Steven Spielberg
//Buscar aquellas películas que no están clasificadas
//Mostrar aquellas personas que han sido tanto actores como directores
//Mostrar aquellas personas que ni han actuado ni han dirigido
//Obtener el número de películas que hay para cada género ordenadas alfabéticamente
//Obtener el número de películas que hay para cada clasificación ordenadas alfabéticamente
//Mostrar para cada actor el número de películas que ha realizado
L'agrupament ha de ser per actor. Però els actors estan dins d'arrays associatius, doncs una pel.lícula té varis actors. Utilitzarem $unwind, que el que fa és que per cada document crea tants documents com elements hi ha en l'array. (unwind: desovillar, devanar)

db.movies.aggregate	([
						{ "$unwind": "$actors" }
					])

Amb això aconseguim tenir els actors a nivell de document.

{
    "result" : [ 
        {
            "_id" : "0018379",
            "name" : "Seventh Heaven",
            "year" : 1927,
            "runtime" : 110,
            "genre" : "DR",
            "actors" : {
                "id" : "0310980",
                "name" : "Janet Gaynor"
            },
            "directors" : [ 
                {
                    "id" : "0097648",
                    "name" : "Frank Borzage"
                }
            ]
        }, 
        {
            "_id" : "0018379",
            "name" : "Seventh Heaven",
            "year" : 1927,
            "runtime" : 110,
            "genre" : "DR",
            "actors" : {
                "id" : "0268190",
                "name" : "Charles Farrell"
            },
            "directors" : [ 
                {
                    "id" : "0097648",
                    "name" : "Frank Borzage"
                }
            ]
        }, 
...

I ara ja puc continuar.

db.movies.aggregate	([
						{ "$unwind": "$actors" },
						{
							"$group": {
								"_id": "$actors.name",
								"movies": {$sum: 1}
							}
						},
						{"$sort": {"movies": -1}}
					])
				
resultat:
{
    "result" : [ 
        {
            "_id" : "Tom Cruise",
            "movies" : 13
        }, 
        {
            "_id" : "Tom Hanks",
            "movies" : 11
        }, 
        {
            "_id" : "Will Smith",
            "movies" : 10
        }, 
		
//Mostrar para cada director el número de películas que ha dirigido
Director també és un array, doncs en les pel.lícules també poden haver-hi varis directors. També hem de fer unwind.


db.movies.aggregate	([
						{ "$unwind": "$directors" },
						{
							"$group": {
								"_id": "$directors.name",
								"num_movies": {$sum: 1}
							}
						},
						{"$sort": {"movies": -1}}
					])

Resultat:
{
    "result" : [ 
        {
            "_id" : "Steven Spielberg",
            "num_movies" : 14
        }, 
        {
            "_id" : "William Wyler",
            "num_movies" : 9
        }, 
...		

//Mostrar para cada director el número de películas realizadas, duración media, mayor y menor duración

db.movies.aggregate ([ { "$unwind": "$directors" }, { "$group": { "_id": "$directors.name", "num_movies": {$sum: 1}, "avg_runtime": {$avg: "$runtime"}, "max_runtime": {$max: "$runtime"}, "min_runtime": {$min: "$runtime"} } }, {"$sort": {"movies": -1}} ]) </pre>

				
//Mostrar para cada actor el número de películas en las que ha participado, duración media, mayor y menor duración

db.movies.aggregate	([
						{ "$unwind": "$actors" },
						{
							"$group": {
								"_id": "$actors.name",
								"movies": {$sum: 1},
								"avg_runtime": {$avg: "$runtime"},
								"max_runtime": {$max: "$runtime"},
								"min_runtime": {$min: "$runtime"}
							}
						},
						{"$sort": {"movies": -1}}
					])

Resultat:
{
    "result" : [ 
        {
            "_id" : "Tom Cruise",
            "movies" : 13,
            "avg_runtime" : 130,
            "max_runtime" : 154,
            "min_runtime" : 110
        }, 
        {
            "_id" : "Tom Hanks",
            "movies" : 11,
            "avg_runtime" : 130,
            "max_runtime" : 188,
            "min_runtime" : 81
        }, 					
//Para cada actor mostrar el listado de películas en las que han participado


db.movies.aggregate	([
						{ "$unwind": "$actors" },
						{
							"$group": {
								"_id": "$actors.name",
								"num_pelis": {$sum: 1},
								"name": {$push: "$name"}
							}
						},
						{"$sort": {"num_pelis": -1}}
					])

Resultat:
{
    "result" : [ 
        {
            "_id" : "Tom Cruise",
            "num_pelis" : 13,
            "name" : [ 
                "Color of Money, The", 
                "Top Gun", 
                "Rain Man", 
                "Born on the Fourth of July", 
                "Few Good Men, A", 
                "Firm, The", 
                "Jerry Maguire", 
                "Mission: Impossible", 
                "Mission: Impossible II", 
                "Minority Report", 
                "Mission: Impossible III", 
                "War of the Worlds", 
                "Mission: Impossible - Ghost Protocol"
            ]
        }, 
        {
            "_id" : "Tom Hanks",
            "num_pelis" : 11,
            "name" : [ 
                "Philadelphia", 
                "Forrest Gump", 
                "Apollo 13", 
                "Toy Story", 
                "Toy Story 2", 
                "Green Mile, The", 
                "Saving Private Ryan", 
                "Catch Me If You Can", 
                "The Polar Express", 
                "The Da Vinci Code", 
                "Toy Story 3"
            ]
        }, 
...

Ara bé, és millor fer primer un sort per tal de què per cada actor les pel.lícules surtin ordenades alfabèticament.

db.movies.aggregate	([
						{"$sort": {"name": 1}},
						{ "$unwind": "$actors" },
						{
							"$group": {
								"_id": "$actors.name",
								"num_pelis": {$sum: 1},
								"name": {$push: "$name"}
							}
						},
						{"$sort": {"num_pelis": -1}}
					])
					
{
    "result" : [ 
        {
            "_id" : "Tom Cruise",
            "num_pelis" : 13,
            "name" : [ 
                "Born on the Fourth of July", 
                "Color of Money, The", 
                "Few Good Men, A", 
                "Firm, The", 
                "Jerry Maguire", 
                "Minority Report", 
                "Mission: Impossible", 
                "Mission: Impossible - Ghost Protocol", 
                "Mission: Impossible II", 
                "Mission: Impossible III", 
                "Rain Man", 
                "Top Gun", 
                "War of the Worlds"
            ]
        }, 
					
//Para cada director mostrar el listado de películas que han dirigido


db.movies.aggregate	([
						{ "$unwind": "$directors" },
						{
							"$group": {
								"_id": "$directors.name",
								"num_pelis": {$sum: 1},
								"name": {$push: "$name"}
							}
						},
						{"$sort": {"num_pelis": -1}}
					])
					

i si vull que les pel.lícules estiguin ordenades:
db.movies.aggregate	([
						{"$sort": {"name": 1}},
						{ "$unwind": "$directors" },
						{
							"$group": {
								"_id": "$directors.name",
								"num_pelis": {$sum: 1},
								"name": {$push: "$name"}
							}
						},
						{"$sort": {"num_pelis": -1}}
					])
					
Resultat:					
{
    "result" : [ 
        {
            "_id" : "Steven Spielberg",
            "num_pelis" : 14,
            "name" : [ 
                "Catch Me If You Can", 
                "E.T. the Extra-Terrestrial", 
                "Indiana Jones and the Kingdom of the Crystal Skull", 
                "Indiana Jones and the Last Crusade", 
                "Indiana Jones and the Temple of Doom", 
                "Jaws", 
                "Jurassic Park", 
                "Lincoln", 
                "Lost World: Jurassic Park, The", 
                "Minority Report", 
                "Raiders of the Lost Ark", 
                "Saving Private Ryan", 
                "Schindler's List", 
                "War of the Worlds"
            ]
        }, 
		
//Mostrar las películas en las que ha participado Penelope Cruz
Abans de fer l'agregat de tots els objectes, primer de tot faré un match per quedar-me amb les pelis en què ha participat Pe.

db.movies.aggregate	([
						{"$match": {
							"actors.name": "Penelope Cruz"}
							}
					])

però millor fer-ho amb elemMatch:

db.movies.aggregate	([
						{"$match": {
							"actors": {
								"$elemMatch": {"name": "Penelope Cruz"}
							}
						}
						}
					])
					
Continuem:

db.movies.aggregate	([
						{"$match": {
							"actors": {
								"$elemMatch": {"name": "Penelope Cruz"}
							}
						}
						},
						{
							"$sort": {"title": 1}
						}
					])

db.movies.aggregate	([
						{"$match": {
							"actors": {
								"$elemMatch": {"name": "Penelope Cruz"}
							}
						}
						},
						{
							"$sort": {"title": 1}
						},
						{
							"$unwind": "$actors"
						}
					])
					
hem fet un unwind d'actors, i estem tenint tots els actors que també han participat juntament amb la Pe. Per tant, hem de filtrar de nou:

db.movies.aggregate	([
						{"$match": {
							"actors": {
								"$elemMatch": {"name": "Penelope Cruz"}
							}
						}
						},
						{
							"$sort": {"title": 1}
						},
						{
							"$unwind": "$actors"
						},
						{
							"$match": {"actors.name": "Penelope Cruz"}
						}
					])
					
i ara puc fer un group (tot i que això no està en l'enunciat):
					
db.movies.aggregate	([
						{"$match": {
							"actors": {
								"$elemMatch": {"name": "Penelope Cruz"}
							}
						}
						},
						{
							"$sort": {"title": 1}
						},
						{
							"$unwind": "$actors"
						},
						{
							"$match": {"actors.name": "Penelope Cruz"}
						},
						{
							"$group": {
								"_id": "$actors.name",
								"num_pelis": {$sum: 1},
								"name": {$push: "$name"}
							}
						}
					])

Resultat:
{
    "result" : [ 
        {
            "_id" : "Penelope Cruz",
            "num_pelis" : 2,
            "name" : [ 
                "Vicky Cristina Barcelona", 
                "Pirates of the Caribbean: On Stranger Tides"
            ]
        }
    ],
    "ok" : 1
}
//Mostrar las películas que ha dirigido Peter Jackson
//Obtener la película más reciente
//Obtener la película más antigua
//Obtener la película más larga de duración
//Obtener la película más corta de duración
//Mostrar los actores ordenados a partir de su año de nacimiento de forma descendente
//Mostrar aquellos actores que han actuado junto a Penelope Cruz
//Mostrar aquellos actores que han actuado junto a Tom Hanks
//Mostrar las películas que han logrado oscars junto al número de oscars conseguido
//Mostrar los actores que han logrado oscars junto al número de oscars conseguidos
//Mostrar los actores que han logrado oscars junto a las películas y oscars conseguidos
//Mostrar para cada año las películas y actores que han obtenido premios
//Mostrar para cada año los distintos premios que se han entregado en los oscars

Sessió 5. 4-nov-2014

llibre: Seven databases in Seven Weeks

editorial: IT eBooks

Grid FS (Grid File Store). Podem utilitzar MongoDb per serialitzar arxius, guardar arxius a la base de dades. Cada objecte d'una col.lecció està limitat a 16MB, però això no és problema perquè els fitxers es divideixen en chunks de 255Kb. Grid FS treballa amb dues col.leccions:

Un bucket és un parell arxiu/metadades. Per ex, el bucket movies seria movies.files i movies.chunks.

Per interactuar amb Grid FS tenim dues opcions. Una opció és l'eina nativa mongofiles. L'altra opció és, des del nostre llenguatge de programació (JAVA, Python, .NET,...), utilitzar el driver/llibreria per a MongoDB.

Grid FS no és un sistema d'arxius complet. Bàsicament podem pujar un arxiu (put), get, delete, list, search.

$ mongofiles --help

En el servidor Mongolab (servidor remot del profe) hi ha la base de dades gridfs (fs.files, fs.chunks). El que farem és un get per descarregar arxiu que estan emmagatzemats. Dades de connexió:

Host: ds047720.mongolab.com
Port: 47720
DB: gridfs 
Username: demo
Password: demo
$ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo list
connected to: ds047720.mongolab.com:47720
Frogtek.mpg     47718404
Java-Specification-8.pdf        3938266
Agile Methods in Action.mp4     37215501
Scrum at Exact Research.mp4     14901085
Scrum Methodology an Agile Movie.mp4    37864324

$ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo list Scrum (que comencin per Scrum)
$ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo search Scrum (que continguin Scrum)
$ mongofiles --host ds047720.mongolab.com --port 47720 --db gridfs --username demo --password demo get "Scrum at Exact Research.mp4"
connected to: ds047720.mongolab.com:47720
done write to: Scrum at Exact Research.mp4

Descarrega l'arxiu a la ruta on ens trobem, a no ser que utilitzi l'opció --local, que puc canviar-li la ruta i nom del nou arxiu que em descarrego.

Anem a pujar els arxius a la nostra bd. És important l'opció --type, el tipus MIME: (mirar a Google MIME types)

$ mongofiles --db gridfs --local "Java-Specification-8.pdf" --type "application/pdf" put "Java-Specification-8.pdf"
connected to: 127.0.0.1
added file: { _id: ObjectId('545898b7597375c9d763e61e'), filename: "Java-Specifi
cation-8.pdf", chunkSize: 261120, uploadDate: new Date(1415092407559), md5: "22c
706198c4e653d876be871b09b3a3c", length: 3938266, contentType: "video/mpeg" }
done!

$ mongofiles --db gridfs --local "Scrum at Exact Research.mp4" --type "video/mpeg" put "Scrum at Exact Research.mp4"
<pre>
Per llistar els arxius:
<pre>
$ mongofiles --db gridfs list
Scrum at Exact Research.mp4     14901085
Java-Specification-8.pdf        3938266

Anem al nostre servidor Mongo:

C:\Users\mongodb-m>mongo
MongoDB shell version: 2.6.4
connecting to: test
> use gridfs
switched to db gridfs
> show collections
fs.chunks
fs.files
system.indexes

col.lecció files: (> db.fs.files.find())

{
    "_id" : ObjectId("54589971bed87f39cd19cb0b"),
    "filename" : "Scrum at Exact Research.mp4",
    "chunkSize" : 261120,
    "uploadDate" : ISODate("2014-11-04T09:16:33.916Z"),
    "md5" : "ba194b3a2fb1db7e1c0016df02b80914",
    "length" : 14901085,
    "contentType" : "video/mpeg"
}

/* 1 */
{
    "_id" : ObjectId("54589a18dc1d9c49c385a659"),
    "filename" : "Java-Specification-8.pdf",
    "chunkSize" : 261120,
    "uploadDate" : ISODate("2014-11-04T09:19:20.958Z"),
    "md5" : "22c706198c4e653d876be871b09b3a3c",
    "length" : 3938266,
    "contentType" : "application/pdf"
}

Col.lecció chunks:

{
    "_id" : ObjectId("5458997109fd9c478cd959e6"),
    "files_id" : ObjectId("54589971bed87f39cd19cb0b"),
    "n" : 0,
    "data" : { "$binary" : "AAAAHGZ0eXBtcDQyAAAAAU00ViBtcDQyaXNvb...}

/* 1 */
{
    "_id" : ObjectId("5458997109fd9c478cd959e7"),
    "files_id" : ObjectId("54589971bed87f39cd19cb0b"),
    "n" : 1,
    "data" : { "$binary" : "CA6deEehDcLxrftGFtXI/8r0diMkQk4uD...}
...

I ara em descarrego l'arxiu des de la meva bd:

$ mongofiles --db gridfs --local "Scrum.mp4" get "Scrum at Exact Research.mp4"

Esborro un fitxer:

$ mongofiles --db gridfs --local "Scrum.mp4" delete "Scrum at Exact Research.mp4"
done!

Mongo no treballa amb fusos horaris. Treballa amb format GMT. Per treballar amb dates el millor és treballar amb Aggregation Framework. Anem a fer proves amb dates:

Hi ha operadors d'agregat per treballar amb dates i extreure la informació de les dates. Per ex, cercar $year en la documentació.

db.fs.files.aggregate(
	[
		{
			"$group": {
				"_id": {
					"month": {"$month": "$uploadDate"},
					"year": {"$year": "$uploadDate"}
				}
			}
		}
	]
)

resultat:

{
    "result" : [ 
        {
            "_id" : {
                "month" : 11,
                "year" : 2014
            }
        }
    ],
    "ok" : 1
}
db.fs.files.aggregate(
	[
		{
			"$group": {
				"_id": {
					"month": {"$month": "$uploadDate"},
					"year": {"$year": "$uploadDate"}
				},
				"num_files": {"$sum": 1},
				"total_length": {"$sum": "$length"},
				"files": {
					"$push": {
						"filename": "$filename",
						"mime": "$contentType"
					}
				}
			}
		}
	]
)

resultat:

{
    "result" : [ 
        {
            "_id" : {
                "month" : 11,
                "year" : 2014
            },
            "num_files" : 2,
            "total_length" : 18839351,
            "files" : [ 
                {
                    "filename" : "Java-Specification-8.pdf",
                    "mime" : "application/pdf"
                }, 
                {
                    "filename" : "Scrum at Exact Research.mp4",
                    "mime" : "video/mpeg"
                }
            ]
        }
    ],
    "ok" : 1
}

Consultes de geolocalització. Està molt integrat a MongoDB. Primer de tot necessitarem una consulta ben dissenyada.

Pdem cercar a Google per world.geo.json. Per exemple, podem trobar el límit dels països en format geo.json.

Fixar-se que en Mongo sempre es demana primer la longitud i després la latitud:

loc: [<longitude>, <latitude>]

o bé:

loc: {lng: <longitude>, lat: <latitude>}

Descarreguem aquesta base de dades, que es troba per Internet, on hi ha més de 7000 ciutat geolocalitzades.

Host: ds047800.mongolab.com
Port: 47800
DB: geography
Username: demo
Password: demo

La inserim dins de geography.

$ mongoexport --host ds047800.mongolab.com --port 47800 --db geography --username demo --password demo --collection cities --out cities.json
o bé
$ mongodump --host ds047800.mongolab.com --port 47800 --db geography --username demo --password demo --collection cities --out backup

Són 98838 registres. (recordem que --out backup vol dir que ho fica dins la carpeta backup/).

...
{ "_id" : { "$oid" : "54579597e9cd9f37d7fcacd8" }, "name" : "Canillo", "country" : "AD", "timezone" : "Europe/Andorra", "population" : 3292, "loc" : { "longitude" : 1.6, "latitude" : 42.56667 } }
...

Ara hem de fer el mongoimport:

C:\> mongoimport --db geography --collection cities --type json --file "C:\Users\mongodb-m\cities.json" --drop --stopOnError

connected to: 127.0.0.1
2014-11-04T12:24:03.042+0100 dropping: geography.cities
2014-11-04T12:24:06.022+0100            Progress: 7385498/20433856      36%
2014-11-04T12:24:06.038+0100                    36400   12133/second
2014-11-04T12:24:09.003+0100            Progress: 14880627/20433856     72%
2014-11-04T12:24:09.003+0100                    73400   12233/second
2014-11-04T12:24:11.077+0100 check 9 99838
2014-11-04T12:24:11.093+0100 imported 99838 objects

C:\Users\mongodb-m>mongo
MongoDB shell version: 2.6.4
connecting to: test
> use geography
switched to db geography
> show collections
cities
countries
system.indexes
zips
<pre>
> db.cities.find().pretty()
...
{
        "_id" : ObjectId("54579597e9cd9f37d7fcacdc"),
        "name" : "Ra's al Khaymah",
        "country" : "AE",
        "timezone" : "Asia/Dubai",
        "population" : 115949,
        "loc" : {
                "longitude" : 55.94278,
                "latitude" : 25.79111
        }
}
...

Ara ja podem començar a treballar.

> db.system.indexex.find()

Veiem que només hi ha index associats a _id, per les diferents col.leccions. Seria una bona idea crear un index per a longitud i latitud, per tal de facilitar les cerques.

Anem a fer un exemple d'index:

> use university
db.students.findOne({"dni": "Y2912569R"})
{
        "_id" : ObjectId("54461df50713cb58f0ab4c0a"),
        "firstname" : "Andrés",
        "lastname1" : "Abarca",
        "dni" : "Y2912569R",
        "gender" : "H",
        "email" : "adamsith@07168.cn",
        "phone" : "669.559.922",
        "birth_year" : 1977
}

recorre els 3000 i picos objectes fins que finalment el troba db.students.find({"dni": "Y2912569R"}).explain()

> db.students.find({"dni": "Y2912569R"}).explain()
{
        "cursor" : "BasicCursor",
        "isMultiKey" : false,
        "n" : 1,
        "nscannedObjects" : 3243,
        "nscanned" : 3243,
        "nscannedObjectsAllPlans" : 3243,
        "nscannedAllPlans" : 3243,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 25,
        "nChunkSkips" : 0,
        "millis" : 3,
        "server" : "PUEPUE-1AVCOLIM:27017",
        "filterSet" : false
}

Veiem que ha escanejat 3243 objectes, i ha trigat 3 ms. Anem a millorar aquesta cerca, creant un index: (createindex està deprecated)

db.students.ensureIndex({"dni": 1}, {"name": "dni_idx", "unique": true}) -> dóna error perquè hi ha camps repetits de DNI. Ho executem sense el unique
db.students.ensureIndex({"dni": 1}, {"name": "dni_idx"})
db.students.getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "university.students"
        },
        {
                "v" : 1,
                "key" : {
                        "dni" : 1
                },
                "name" : "dni_idx",
                "ns" : "university.students"
        }
]

Es crea una estructura d'arbre per indexar els objectes (b-tree). Consumeix espai en els arxius. Cada vegada que es creï un registre es fa una reindexació.

Ara tornem a fer el explain, i veiem com la consulta triga 0 ms i que el número d'objectes escanejats només és 1:

> db.students.find({"dni": "Y2912569R"}).explain()
{
        "cursor" : "BtreeCursor dni_idx",
        "isMultiKey" : false,
        "n" : 1,
        "nscannedObjects" : 1,
        "nscanned" : 1,
        "nscannedObjectsAllPlans" : 1,
        "nscannedAllPlans" : 1,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 0,
        "indexBounds" : {
                "dni" : [
                        [
                                "Y2912569R",
                                "Y2912569R"
                        ]
                ]
        },
        "server" : "PUEPUE-1AVCOLIM:27017",
        "filterSet" : false
}

Millorem l'index, dient-li que només es creï l'index per aquells registres que tenen un valor per al dni:

db.students.dropIndex("dni_index")
db.students.ensureIndex({"dni": 1}, {"name": "dni_idx", "unique": true,"sparse":true})

El problema dels index és que no es poden abusar d'ells, doncs si fas index per tot, es pot veure que el tamany dels arxius que ocupen els index pot ser molt més gran que el que ocupen els fitxers.

Tornant a l'exemple de geografia, hem de crear un index en el camp que conté les coordenades de geolocalització (longitud, latitud), o bé en format GeoJSON.

Per començar és més fàcil treballar amb u index 2d (després treballarem amb el index 2dsphere):

> use geography
db.cities.ensureIndex({"loc": "2d"}, {"name": "geo_idx"})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}

db.cities.getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "geography.cities"
        },
        {
                "v" : 1,
                "key" : {
                        "loc" : "2d"
                },
                "name" : "geo_idx",
                "ns" : "geography.cities"
        }
]

Consultes de proximitat: 100 elements més propers a un punt.

near es pot utilitzar de forma fàcil, o com a comanda (amb totes les seves opcions).

Escollim una ciutat, i agafem les coordenades de Bagà: 42.2528016,1.8628564

db.cities.find({"loc": {"$near": [1.8628564, 42.2528016] }})
...
{
        "_id" : ObjectId("54579599e9cd9f37d7fd147e"),
        "name" : "Ripoll",
        "country" : "ES",
        "timezone" : "Europe/Madrid",
        "population" : 11057,
        "loc" : {
                "longitude" : 2.19033,
                "latitude" : 42.20064
        }
}
...

Per defecte dóna 100 valors. Si vull limitar la cerca:

db.cities.find({"loc": {"$near": [1.8628564, 42.2528016], "$maxDistance": 1 }})
db.cities.find({"loc": {"$near": [1.8628564, 42.2528016], "$maxDistance": 1 }}).limit(5)
{
        "_id" : ObjectId("54579599e9cd9f37d7fd17cb"),
        "name" : "Bagà",
        "country" : "ES",
        "timezone" : "Europe/Madrid",
        "population" : 2178,
        "loc" : {
                "longitude" : 1.86098,
                "latitude" : 42.25289
        }
}
...

"$maxDistance": 1 -> significa 1 radian

Puc limitar el número de resultats amb limit(). Com a màxim donarà 100 resultats. El que és important, obligatori haver definit un index, si no no funciona.

db.runCommand({"geoNear": "cities", "near": [1.8628564, 42.2528016], "maxDistance": 1})

Obtenim la informació de la distància dels punts, i una estadística. Està ordenat per proximitat. No es pot utilitzar el pretty() perquè només retorna un objecte. Millor mirar-ho amb el Robomongo.

Ara farem servir un index de geolocalització avançat, i podrem fer servir metres en comptes de radians per calcular la proximitat.

db.cities.dropIndex("geo_idx")
db.cities.ensureIndex({"loc": "2dspehere"}, {"name": "geo_idx"})
db.cities.getIndexes()

El format del find() tal com estava abans no funciona. L'hem de canviar de la següent manera:

db.cities.find({"loc":
					{"$near": {
						$geometry: {
							type: "Point",
							coordinates: [1.8628564, 42.2528016]
						},
						"$maxDistance": 20000,
						"$minDistance": 0
					}}
})

{
        "_id" : ObjectId("54579599e9cd9f37d7fd17cb"),
        "name" : "Bagà",
        "country" : "ES",
        "timezone" : "Europe/Madrid",
        "population" : 2178,
        "loc" : {
                "longitude" : 1.86098,
                "latitude" : 42.25289
        }
}
{
        "_id" : ObjectId("54579599e9cd9f37d7fd1443"),
        "name" : "Sant Julià de Cerdanyola",
        "country" : "ES",
        "timezone" : "Europe/Madrid",
        "population" : 1212,
        "loc" : {
                "longitude" : 1.89308,
                "latitude" : 42.2235
        }
}
...

La informació del punt no ha d'estar en format logn,lat, sinó en format GeoJSON. Ara puc demanar una proximitat de 2Km (20000m)

db.runCommand({
				"geoNear": "cities",
				"near": {type: "Point", coordinates: [1.8628564, 42.2528016]},
				"spherical": true,
				"maxDistance": 20000,
				"minDistance": 0
})

Podem veure com Castellar del Riu està a 16Km en línia recta.

Cerca basada en regions. Cercar objectes que estiguin delimitats en una regió. $geoWithin. Es tracta de que dins de $box li passem les dues coordenades que ens defineixen un rectangle. En aquest cas, cerco punts en la caixa definida entre Bagà i Perpinyà.

db.cities.find({
			"loc": {
				"$geoWithin": {
					"$box" : [[1.8628564, 42.2528016],[2.9018408, 42.6980102]]
				}
			}
})

...
{
        "_id" : ObjectId("54579599e9cd9f37d7fd26d6"),
        "name" : "Osséja",
        "country" : "FR",
        "timezone" : "Europe/Paris",
        "population" : 1673,
        "loc" : {
                "longitude" : 1.98192,
                "latitude" : 42.41383
        }
}
{
        "_id" : ObjectId("54579599e9cd9f37d7fd28d5"),
        "name" : "Montesquiu d'Albera",
        "country" : "FR",
        "timezone" : "Europe/Paris",
        "population" : 1095,
        "loc" : {
                "longitude" : 2.88243,
                "latitude" : 42.51798
        }
}

I encara més complicat, podem definir un polígon de cerca. De fet, es troben bases de dades amb els polígons dels països, etc. És el mateix, però en comptes de $box posem $polygon.

Punts que es troben dins de Cuba.

db.cities.find({"loc": {"$geoWithin": {"$polygon": [[-82.268151,23.188611],[-81.404457,23.117271],[-80.618769,23.10598],[-79.679524,22.765303],[-79.281486,22.399202],[-78.347434,22.512166],[-77.993296,22.277194],[-77.146422,21.657851],[-76.523825,21.20682],[-76.19462,21.220565],[-75.598222,21.016624],[-75.67106,20.735091],[-74.933896,20.693905],[-74.178025,20.284628],[-74.296648,20.050379],[-74.961595,19.923435],[-75.63468,19.873774],[-76.323656,19.952891],[-77.755481,19.855481],[-77.085108,20.413354],[-77.492655,20.673105],[-78.137292,20.739949],[-78.482827,21.028613],[-78.719867,21.598114],[-79.285,21.559175],[-80.217475,21.827324],[-80.517535,22.037079],[-81.820943,22.192057],[-82.169992,22.387109],[-81.795002,22.636965],[-82.775898,22.68815],[-83.494459,22.168518],[-83.9088,22.154565],[-84.052151,21.910575],[-84.54703,21.801228],[-84.974911,21.896028],[-84.447062,22.20495],[-84.230357,22.565755],[-83.77824,22.788118],[-83.267548,22.983042],[-82.510436,23.078747],[-82.268151,23.188611]]}}}) 

...{
    "_id" : ObjectId("54579598e9cd9f37d7fce883"),
    "name" : "Yaguajay",
    "country" : "CU",
    "timezone" : "America/Havana",
    "population" : 42218,
    "loc" : {
        "longitude" : -79.23778,
        "latitude" : 22.32722
    }
}
...

Sessió 6. 11-nov-2014

Replicació a MongoDB

(no ens dóna temps de fer sharding).

Per fer la pràctica, com a mínim hauríem de ser 3. Es tracta de replicar dades entre varis servidors (nodes), que treballaran com una unitat de replicació.

Replica Sets (Cluster) és un grup d'instàncies de mongodb que mantenen la mateixa col.lecció de dades. Les dades es repliquen entre els nodes. Això millora la disponibilitat de les dades.

Les nostres aplicacions/driver interactuen amb un Replica Set. Les lectures es podran distribuir entre els diferents nodes (per ex, amb el criteri de proximitat geogràfica). Si cau un node, els altres nodes estan actius.

Hi ha un node que és el primari, que centralitza totes les dades d'escriptura (equivalent a insert, delete, update). Per defecte també rep totes les lectures.

Les operacions de replicació són asíncrones. Si llegim directament dels secundaris ens podem trobar dades inconsistents, que encara no estan replicades.

En tot servidor mongodb hi ha una bd que es diu local, que emmagatzema tota la informació dels replica set. Té una col.lecció (capped collection, que té el tamany màxim limitat) que es diu oplog. Els documents que hi ha a OPLOG són totes les instruccions (històric) que es fan en el primari, i que s'han de replicar en el secundari. És com una llista de les tasques que s'han de realitzar en els nodes secundaris. El tamany de les capped collection és per defecte el 5% del tamany lliure del disc, i això pot ser molt gran.

Els secundaris entre sí s'estan enviant continuament missatgets (heartbeats). Si el primari cau, els secundaris s'ho comenten, i un dels secundaris esdevindrà primari (als secundaris se'ls dóna una prioritat; també té en compte en la votació l'estat actual dels secundaris).

Pràctica 1 persona sola: hem de fer tres instàncies de mongo. Però és millor fer la pràctica en grup. Si ho faig jo sol, hauré de llençar tres instàncies amb tres ports diferents.

Pràctica. En grups de 3 o 4, arrencarem tres servidors de mongo (mongod) pel port 27018. Cadascun amb el seu DATAPATH. Arrencarem mongod amb el paràmetre --replSet, amb un nom que els identifica. En un Replica Set hi poden haver fins a 12 nodes.

La ruta on vam instal.lar mongo és: C:\Program Files\MongoDB 2.6 Standard. A dins hi ha data/db/ on es guarden les dades.

Escollim un nom del Replica Set per al nostre grup: rsfila2. Creem aquesta carpeta a data/. La mateixa carpeta l'han de crear els companys. Anem a arrencar els nodes (cada company arrenca el seu):

> mongod --port 27018 --dbpath "C:\Program Files\MongoDB 2.6 Standard\data\rsfila2" --replSet rsfila2 --oplogSize 200

És important que tots els servidors diguin que volen treballar amb el mateix Replica Set. El nom del Replica Set no té perquè coincidir amb el nom de la carpeta. El oplog el volem amb un tamany per ex de 200MB, perquè per defecte seria el 5% del tamany lliure del disc.

Encara no està. Si llenço aquesta comanda, la meva instància encara no s'entera de què hi ha altres instàncies a la xarxa amb les quals em vull replicar. Falta que li presentem els altres membres del Replica Set. De moment el deixem engegat i va vomitant missatges de què no sap trobar els altres membres del Replica Set. Llença heartbeats, però no troba a d'altres nodes en la seva mateixa estructura.

Següent pas: ytiltizant la shell de mongo, ens connectarem al nostre servidor:

> mongo --port 27018
> use local
> 
show databases  admin  (empty)    local  0.078GB 

Veig que la bd local comença a tenir informació.

La meva IP és al 192.168.20.218 la dels companyts 20.129, 20.132, 20.181

rs és un àlies del Replica Set. Per exemple, podem agregar membres.

> rs.help()
> rs.status()
{
        "startupStatus" : 3,
        "info" : "run rs.initiate(...) if not yet done for the set",
        "ok" : 0,
        "errmsg" : "can't get local.system.replset config from self or any seed
(EMPTYCONFIG)"
}

Només un dels nodes llençarà la sentència de configuració, i aquest tindrà tots els números de ser el primari.

Anem a crear un document JSON que serà la configuració del meu Replica Set, on constarà les IPs dels nodes.

config = {
	"_id": "rsfila2",
	"members": [
		{"_id": 1, "host": "192.168.20.218:27018"},
		{"_id": 2, "host": "192.168.20.129:27018"},
		{"_id": 3, "host": "192.168.20.181:27018"},
		{"_id": 4, "host": "192.168.20.132:27018", "priority": 0}
	]
}

Podem passar més tipus d'informació, com per exemple hi ha diferents tipus de secundari. Per ex, nodes de prioritat 0 (priority), que no volem que mai siguin primaris; nodes ocults (mai es faran lectures d'ell, només existeixen com a backup); node delayed (mai serà primari, ni es farà lectura d'ell, i és delayed: la replicació es fa després d'una hora o el que vulguem). El node delayed va molt bé per recuperació de desastres (necessitem un optlog molt llarg).

Ho executem a la shell (de moment només és una variable que es diu config). rs.initiate(config)

i ara els logs canvien, es troben els membres els uns als altres. Comencen a parlar i s'estan votant, de manera que s'escull un primari.

...
014-11-11T10:25:37.338+0100 [initandlisten] connection accepted from 192.168.2
132:49309 #22 (9 connections now open)
014-11-11T10:25:37.338+0100 [initandlisten] connection accepted from 192.168.2
132:49310 #23 (10 connections now open)
014-11-11T10:25:38.773+0100 [rsHealthPoll] replSet member 192.168.20.181:27018
s now in state SECONDARY
014-11-11T10:25:38.773+0100 [rsHealthPoll] replSet member 192.168.20.132:27018
s now in state SECONDARY
014-11-11T10:25:47.200+0100 [conn8] end connection 192.168.20.129:50189 (9 con
ctions now open)
014-11-11T10:25:47.200+0100 [initandlisten] connection accepted from 192.168.2
129:50196 #24 (11 connections now open)
...
> rs.status()
rsfila2:PRIMARY> rs.status()
{
        "set" : "rsfila2",
        "date" : ISODate("2014-11-11T09:30:12Z"),
        "myState" : 1,
        "members" : [
                {
                        "_id" : 1,
                        "name" : "192.168.20.218:27018",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 2046,
                        "optime" : Timestamp(1415697913, 1),
                        "optimeDate" : ISODate("2014-11-11T09:25:13Z"),
                        "electionTime" : Timestamp(1415697924, 1),
                        "electionDate" : ISODate("2014-11-11T09:25:24Z"),
                        "self" : true
                },
                {
                        "_id" : 2,
                        "name" : "192.168.20.129:27018",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 298,
                        "optime" : Timestamp(1415697913, 1),
                        "optimeDate" : ISODate("2014-11-11T09:25:13Z"),
                        "lastHeartbeat" : ISODate("2014-11-11T09:30:10Z"),
                        "lastHeartbeatRecv" : ISODate("2014-11-11T09:26:15Z"),
                        "pingMs" : 0,
                        "syncingTo" : "192.168.20.218:27018"
                },
                {
                        "_id" : 3,
                        "name" : "192.168.20.181:27018",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 296,
                        "optime" : Timestamp(1415697913, 1),
                        "optimeDate" : ISODate("2014-11-11T09:25:13Z"),
                        "lastHeartbeat" : ISODate("2014-11-11T09:30:10Z"),
                        "lastHeartbeatRecv" : ISODate("2014-11-11T09:26:25Z"),
                        "pingMs" : 0,
                        "syncingTo" : "192.168.20.218:27018"
                },
                {
                        "_id" : 4,
                        "name" : "192.168.20.132:27018",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 296,
                        "optime" : Timestamp(1415697913, 1),
                        "optimeDate" : ISODate("2014-11-11T09:25:13Z"),
                        "lastHeartbeat" : ISODate("2014-11-11T09:30:10Z"),
                        "lastHeartbeatRecv" : ISODate("2014-11-11T09:26:25Z"),
                        "pingMs" : 0,
                        "syncingTo" : "192.168.20.218:27018"
                }
        ],
        "ok" : 1
}
> db.oplog.rs.find().pretty()
de moment no hi ha res.

Ara jo, com a primari, importaré la base de dades tweets. En el oplog s'escriurant totes les tasques que s'hauran de fer, i això es replicarà en els altres nodes.

C:\> mongoimport --host localhost:27018 --db twitter --collection tweets --type json --file "D:\tweets.json" --drop --stopOnError
> rsfila2:PRIMARY> db.tweets.count()
51428

> db.oplog.rs.find().pretty()
rsfila2:PRIMARY> show databases
admin      (empty)
geography  0.078GB
local      0.328GB
twitter    0.203GB

> db.oplog.rs.find().pretty()

Ara està ple d'informació que són les dades que s'han de replicar. En el servidor dels companys ja s'ha creat la base de dades i tenen les dades replicades.

Per tal de què els secundaris també puguin llegir, en els secundaris s'ha de fer:

> rs.slaveOk()

Els secundaris no poden escriure, només poden llegir. Només el primari pot escriure:

> db.tweets.insert({"name": "Joan"})

rsfila2:PRIMARY> db.oplog.rs.count() 52013 </pre>

rsfila2:PRIMARY> db.oplog.rs.find().pretty()
...
{
        "ts" : Timestamp(1415699916, 19),
        "h" : NumberLong("3970298272155406135"),
        "v" : 2,
        "op" : "i",
        "ns" : "twitter.tweets",
        "o" : {
                "_id" : ObjectId("5461ddcc90c824c08d77197a"),
                "text" : "RT @ayatquran: Sesungguhnya shalat itu adalah kewajiban yang ditentukan waktunya atas orang-orang yang b
eriman (4:103)",
                "in_reply_to_status_id" : null,
                "retweet_count" : null,
                "contributors" : null,
                "created_at" : "Thu Sep 02 18:11:25 +0000 2010",
                "geo" : null,
                "source" : "<a href=\"http://www.ubertwitter.com/bb/download.php\" rel=\"nofollow\">ÜberTwitter</a>",
                "coordinates" : null,
                "in_reply_to_screen_name" : null,
                "truncated" : false,
                "entities" : {
                        "user_mentions" : [
                                {
                                        "indices" : [
                                                3,
                                                13
                                        ],
                                        "screen_name" : "ayatquran",
                                        "name" : "Ayat Quran",
                                        "id" : 56593724
                                }
                        ],
                        "urls" : [ ],
                        "hashtags" : [ ]
                },
                "retweeted" : false,
                "place" : null,
                "user" : {
                        "friends_count" : 544,
                        "profile_sidebar_fill_color" : "2c1e7d",
                        "location" : "ÜT: 0.504168,101.450489",
                        "verified" : false,
                        "follow_request_sent" : null,
                        "favourites_count" : 2,
                        "profile_sidebar_border_color" : "3d0d3d",
                        "profile_image_url" : "http://a0.twimg.com/profile_images/1110579656/for_20twit_normal.jpg",
                        "geo_enabled" : true,
                        "created_at" : "Sat Dec 26 20:13:30 +0000 2009",
                        "description" : "hidup yg menyenangkn bersama",
                        "time_zone" : "Jakarta",
                        "url" : "http://null",
                        "screen_name" : "indahrahenni",
                        "notifications" : null,
                        "profile_background_color" : "101e52",
                        "listed_count" : 1,
                        "lang" : "en",
                        "profile_background_image_url" : "http://a1.twimg.com/profile_background_images/116008894/the_karate_kid_9
.jpg",
                        "statuses_count" : 1785,
                        "following" : null,
                        "profile_text_color" : "8c7142",
                        "protected" : false,
                        "show_all_inline_media" : false,
                        "profile_background_tile" : true,
                        "name" : "indah anugrah",
                        "contributors_enabled" : false,
                        "profile_link_color" : "0a040a",
                        "followers_count" : 100,
                        "id" : 99567001,
                        "profile_use_background_image" : true,
                        "utc_offset" : 25200
                },
                "favorited" : false,
                "in_reply_to_user_id" : null,
                "id" : NumberLong("22819399000")
        }
}
...

Ens dóna informació de totes les tasques que s'han fet, i de totes les tasques pendents.

creem la col.lecció logs en el PRIMARY

> use logs

> for (var i=0; i<=10000; i++) {
	db.logs.insert({"_id": i, "time": new ISODate()});
}

i ara mirem el oplog per veure com està l'estat de totes aquestes insercions

> use local
rsfila2:PRIMARY> db.oplog.rs.find().skip(60000).pretty()

{
        "ts" : Timestamp(1415703957, 997),
        "h" : NumberLong("6946955576797341749"),
        "v" : 2,
        "op" : "i",
        "ns" : "logs.logs",
        "o" : {
                "_id" : 8005,
                "time" : ISODate("2014-11-11T11:05:57.508Z")
        }
}
{
        "ts" : Timestamp(1415703957, 998),
        "h" : NumberLong("5184908651075962710"),
        "v" : 2,
        "op" : "i",
        "ns" : "logs.logs",
        "o" : {
                "_id" : 8006,
                "time" : ISODate("2014-11-11T11:05:57.508Z")
        }
}

Operacions que no siguin de tipus i (inserció):

db.oplog.rs.find({"op": {"$ne": "i"}})

{
        "ts" : Timestamp(1415697913, 1),
        "h" : NumberLong(0),
        "v" : 2,
        "op" : "n",
        "ns" : "",
        "o" : {
                "msg" : "initiating set"
        }

"op" : "n" és una operació de tipus new, d'inicialització.

rsfila2:PRIMARY> use logs
switched to db logs
rsfila2:PRIMARY> db.logs.drop()
true

> db.dropDatabase()
{ "dropped" : "logs", "ok" : 1 }

Hem eliminat la bd. Si hi hagués algun secundari amb replicació retardada, encara conservaria les dades.

rsfila2:PRIMARY> db.oplog.rs.find({"op": {"$ne": "i"}}).pretty()
{
        "ts" : Timestamp(1415697913, 1),
        "h" : NumberLong(0),
        "v" : 2,
        "op" : "n",
        "ns" : "",
        "o" : {
                "msg" : "initiating set"
        }
}
{
        "ts" : Timestamp(1415704284, 1),
        "h" : NumberLong("-2663097432869229176"),
        "v" : 2,
        "op" : "c",
        "ns" : "logs.$cmd",
        "o" : {
                "drop" : "logs"
        }
}
{
        "ts" : Timestamp(1415704413, 1),
        "h" : NumberLong("1820014921288848938"),
        "v" : 2,
        "op" : "c",
        "ns" : "logs.$cmd",
        "o" : {
                "dropDatabase" : 1
        }
}

Veig que hi ha les operacions de tipus c, que són les comandes, que es corresponen al db.logs.drop() i al db.dropDatabase().

Faig Ctrl-C (mato el primari), i automàticament els tres secundaris entren en un procés de votació per tal de saber quin serà el nou primari. En fer un status se sabrà quin és el nou primari. El primari passa a ser la IP 20.181.

MongoDB Seguretat

Mirar els apunts. Per defecte MongoDB treballa en mode obert i no té autenticació, tal com hem fet al llarg del curs. Si volem treballar amb autenticació hem d'anar al fitxer de configuració

security:
   authorization: enabled

(per defecte és disabled). Quan habilitem l'autenticació, hem de crear el primer usuari. Només et deixa connectar-te des de la màquina local (localhost exception) per tal de poder crear el primer usuari. Un cop creat el primer usuari, aquesta excepció deixa de funcionar. Per tant, s'ha de crear els usuaris amb el PERMISOS SUFICIENTS. En cas de desastre no passa res: es torna a modificar el fitxer de configuració amb seguretat deshabilitada.

> use admin
> sb.createUser(...)




creat per Joan Quintana Compte, octubre 2014

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
IES Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines