MongoDB es una base de datos no relacional o NoSQL que proporciona un alto rendimiento, disponibilidad y escalado.
En MongoDB todo gira en torno a documentos que serían los equivalentes a las filas de una tabla en un sistema relacional. El equivalente a las tablas serían las colecciones salvo que estas son flexibles y pueden almacenar diferentes estructuras de datos.
Contenidos
Operaciones
Crear base de datos
use pruebas1;
Añadir colección a la base de datos
db.usuarios.save({nombre 'DevSeo', web: 'www.devseo.xyz'});
Ver las bases de datos creadas
show dbs;
Ver las colecciones
show collections;
Buscar documentos en la base de datos
db.usuarios.find();
Exportar base de datos
C:\Program Files\MongoDB\Server\4.2\bin>mongodump.exe --db nombreBaseDatos
Importar datos
Desde la shell
Mongo admite scripts JavaScript por lo que podemos insertar datos mediante código
for (i = 0; i & lt; 10; ++i) {
var doc = {
name: "user-" + i;
}
db.posts.insert(doc);
}
Cargar script externo
load("ruta/al/scripts/myScript.js";)
Importar base de datos mongo
Ejecutar desde la terminal:
C:\Program Files\MongoDB\Server\4.2\bin>mongorestore.exe --drop --db nombreBaseDatos archivoAImportar\dump\off
Importar archivo que con datos JSON
Ejecutar desde la terminal:
mongoimport.exe -h host:port -d database -c collection -u username -p password --ssl --jsonArray --file "C:\Ruta\Al\Fichero\json.json"
Desde código
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/testdb, function(err, db) {
if (err) throw err;
var query = {
'grade': 100
};
function callback(err, doc) {
if (err) throw err;
console.dir(doc);
db.close();
}
});
Insertar documentos
Podemos omitir el atributo _id y mongo generará automáticamente uno único para el documento.
Además podemos variar la estructura de datos.
> db.users.insert({ _id: 1, username : "Pedro", edad: 50, casado: true })
> db.users.insert({ _id: 2, username : "Juan", edad: 80, casado: false })
> db.users.insert({ _id: 3, username : "María", edad: 15 })
Crear colección de forma explícita
Las colecciones se crean de forma automática al insertar datos pero podemos crearlas de forma explícita para configurar ciertos datos.
Por ejemplo, podemos crear colecciones en las que se eliminarán automáticamente los registros antiguos al insertar los nuevos cuando se alcanza un límite.
> db.createCollection("users", { capped: true, max: 1000 })
Listar todos los documentos
> db.users.find()
Listar un documento al azar
> db.users.findOne()
Listar elementos por el valor de sus atributos
> db.users.find({ edad: 50 })
> db.comments.find({date: ISODate("2020-09-24T00:00:00Z")});
Listar por rango de valores
> db.users.find({ edad: { $gt : 20, $lte: 50 }})
Listar por múltiples condiciones
> db.users.find({
edad: { $gt : 20 },
casado: true
})
Listar elementos que no incluyen un atributo
> db.users.find({ casado: { $exists : false } })
Listar filtrando por expresiones regulares
> db.users.find({ username : { $regex : "Mar" }})
Filtrar elementos por tipo de dato
Ver aquí todos los tipos de datos.
> db.users.find({ casado: { $type : 8 }})
Agrupar dos consultar con un OR
> db.users.find({ $or : [ { _id : 1 }, { _id : 2 } ] })
Consultar el número de documentos de una colección
> db.users.count()
> db.users.count({ casado: true })
Filtrar coincidencias en un array u objetos embebidos
> db.users.find({ hobbies : "futbol" }).pretty()
> db.users.find({ hobbies : { $all: ["futbol", "padel"] }}).pretty()
> db.users.find({ hobbies : { $in: ["futbol"] }}).pretty()
> db.users.find({ "address.city": "Asturias" }).pretty()
Paginar los resultados
> db.users.find().skip(2).limit(2)
Ordenar resultados
> db.users.find().sort({ _id : 1 }).limit(2)
Iterar con cursores
> var c = db.users.find().sort({ username : -1 }).limit(2)
> c.hasNext()
> c.next()
Operación de agregación
Ver etapas admitidas en las operaciones de agregación.
Limitaciones de las funciones de agregación
- Existe un límite de 100mb de tamaño de pipeline para cada etapa durante el procesado. Se puede saltar esta restricción con allowDiskUse pero ralentizará la consulta.
- Existe un límite de 16mb de tamaño para la salida de cada pipeline. Se puede evitar esto obteniendo un cursor y recorriendo el cursor elemento a elemento.
- Sharding: cuando se usa group o sort todos los datos se reúnen en un solo shard (el principal) antes de continuar procesando pipes. Esto se puede mejorar importando el mongo en Hadoop y usar mapreduce ya que tiene mejor escalabilidad.
Avg
El siguiente ejemplo realiza una función de agregación, un sumatorio, para todos los registros del documento orders
que cumplen la condición (tienen status
= A). La suma se realiza a partir del campo amount
dando como resultado un nuevo objeto con dos propiedades: id
y total
.
db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])

Actulizar datos
Se actualiza el elemento que coincide con la búsqueda. Si no existe, se añade.
> db.users.update({ _id : 1 }, { $set : { casado: false }})
Eliminar un atributo
> db.users.update({ _id : 1 }, { $unset : { cassado: 1 }})
Actualización masiva de datos. Hay que indicar multi si no solo actualizará la primera coincidencia.
> db.users.update({}, { $set : { casado: true }}, { multi : true })
Actualizaciones de atributos de tipo array
> db.users.update({ _id:4 }, { $set: { hobbies : "baloncesto" }})
> db.users.update({ _id:4 }, { $push: { hobbies : "caza" }})
> db.users.update({ _id : 4 }, { $addToSet : { hobbies: "cazar"; }}) // añade solo si no existe
> db.users.update({ _id : 4 }, { $pushAll : { hobbies: [ "baloncesto", "caza" ]}})
> db.users.update({ _id:4 }, { $pop : { hobbies: 1 }}) // elimina el primero
> db.users.update({ _id:4 }, { $pop : { hobbies: -1 }}) // elimina el último
> db.users.update({ _id : 4 }, { $pullAll: { hobbies: [ "baloncesto", "caza" ]}}) // elimina por valor
Eliminar
Eliminar documento
> db.users.remove({ _id : 4 })
Eliminar colección
> db.users.drop()
Índices
Índices simples y compuestos
> db.users.createIndex({ username: 1 });
> db.users.createIndex({ username: 1, edad: -1 })
Crear índices sobre un campo de tipo array. Mongo crea entradas separadas automáticamente para cada elemento del array en un tabla de índices haciendo muy eficiente la búsqueda por valores del array.
No se puede crear un índices compuesto por más de un campo de tipo array.
> db.users.createIndex({ hobbies: 1})
Índices únicos
> db.users.createIndex({ username: 1, edad: -1 }, { unique: 1 }
Operaciones cubiertas por el índice: son muy eficientes ya que no tiene que acceder al documento para obtener los datos si no que los saca del índice
> db.users.createIndex({ edad: 1 })
> db.users.find({ edad: { "$lt": 30 } }, { edad: 1, _id : 0 })
Listar los índices de una colección
> db.users.getIndexes()
Eliminar un índice existente
> db.users.dropIndex({ username: 1, edad: -1 })
Análisis y estadísticas
Ver resultados del plan de ejecución
> db.users.find({ _id : 1 }, { _id : 1 }).explain()
Método genérico para obtener el tiempo que tarda en ejecutarse una consulta
> d = new Date; db.consulta.... ; print(new Date - d + 'ms');
Información de una colección
> db.users.stats()
Códigos ejemplo (conexión desde NodeJS)
Procesar las fotografías de caratula de album, cuyo album no existe en la base de datos.
var client = require('mongodb')
.MongoClient;
client.connect('mongodb://localhost:27017/h7', function(err, db) {
if (err) throw err;
var albums = db.collection('albums');
db.collection('images', function(err, images) {
if (err) throw err;
images.find({}, {
'_id': true
}, function(err, cursor) {
if (err) throw err;
var count = cursor.count(function(err, count) {
console.dir('num images: ' + count);
// iterate over each image
cursor.each(function(err, item) {
if (item !== null) {
// attempt to find an album containing the photo, if not prune
albums.findOne({
images: item._id
}, function(err, doc) {
if (err) throw err;
if (doc == null) {
images.remove({
'_id': item._id
}, function(err, numRemoved) {
if (err) throw err;
count - ;
console.dir('count: ' + count);
if (count == 0) db.close();
});
} else {
count - ;
console.dir('count: ' + count);
if (count == 0) db.close();
}
});
}
});
});
})
});
});
Registro de usuario y validación usando node y mongo
var bcrypt = require('bcrypt-nodejs');
/\* The UsersDAO must be constructed with a connected database object \*/
function UsersDAO(db) {
"use strict";
/* If this constructor is called without the "new" operator, "this" points
\* to the global object. Log a warning and call it correctly. \*/
if (false === (this instanceof UsersDAO)) {
console.log('Warning: UsersDAO constructor called without "new" operator');
return new UsersDAO(db);
}
var users = db.collection("users");
this.addUser = function(username, password, email, callback) {
"use strict";
// Generate password hash
var salt = bcrypt.genSaltSync();
var password_hash = bcrypt.hashSync(password, salt);
// Create user document
var user = {
'_id': username,
'password': password_hash
};
// Add email if set
if (email != "") {
user['email'] = email;
}
users.insert(user, function(err, result) {
"use strict";
if (!err) {
console.log("Inserted new user");
return callback(null, result[0]);
}
return callback(err, null);
});
}
this.validateLogin = function(username, password, callback) {
"use strict";
// Callback to pass to MongoDB that validates a user document
function validateUserDoc(err, user) {
"use strict";
if (err) return callback(err, null);
if (user) {
if (bcrypt.compareSync(password, user.password)) {
callback(null, user);
} else {
var invalid_password_error = new Error("Invalid password");
// Set an extra field so we can distinguish this from a db error
invalid_password_error.invalid_password = true;
callback(invalid_password_error, null);
}
} else {
var no_such_user_error = new Error("User: " + user + " does not exist");
// Set an extra field so we can distinguish this from a db error
no_such_user_error.no_such_user = true;
callback(no_such_user_error, null);
}
}
users.findOne({
'_id': username
}, validateUserDoc);
}
}
module.exports.UsersDAO = UsersDAO;
Operaciones de un blog
* The PostsDAO must be constructed with a connected database object */
function PostsDAO(db) {
"use strict";
/* If this constructor is called without the "new" operator, "this" points
\* to the global object. Log a warning and call it correctly. \*/
if (false === (this instanceof PostsDAO)) {
console.log('Warning: PostsDAO constructor called without "new" operator');
return new PostsDAO(db);
}
var posts = db.collection("posts");
this.insertEntry = function(title, body, tags, author, callback) {
"use strict";
console.log("inserting blog entry" + title + body);
// fix up the permalink to not include whitespace
var permalink = title.replace(/\s/g, '_');
permalink = permalink.replace(/\W/g, ”);
// Build a new post
var post = {
"title": title,
"author": author,
"body": body,
"permalink": permalink,
"tags": tags,
"comments": [],
"date": new Date()
}
// now insert the post
posts.insert(post, function(err, result) {
"use strict";
if (err) return callback(err, null);
console.log("Inserted new post");
callback(err, permalink);
});
};
this.getPosts = function(num, callback) {
"use strict";
posts.find()
.sort('date', -1)
.limit(num)
.toArray(function(err, items) {
"use strict";
if (err) return callback(err, null);
console.log("Found " + items.length + " posts");
callback(err, items);
});
};
this.getPostsByTag = function(tag, num, callback) {
"use strict";
posts.find({
tags: tag
})
.sort('date', -1)
.limit(num)
.toArray(function(err, items) {
"use strict";
if (err) return callback(err, null);
console.log("Found " + items.length + " posts");
callback(err, items);
});
};
this.getPostByPermalink = function(permalink, callback) {
"use strict";
posts.findOne({
'permalink': permalink
}, function(err, post) {
"use strict";
if (err) return callback(err, null);
// XXX: Look here for final exam to see where we store "num_likes"
// fix up likes values. set to zero if data is not present
if (typeof post.comments === 'undefined') {
post.comments = [];
}
// Each comment document in a post should have a "num_likes" entry, so we have to
// iterate all the comments in the post to make sure that is the case
for (var i = 0; i < post.comments.length; i++) {
if (typeof post.comments[i].num_likes === 'undefined') {
post.comments[i].num_likes = 0;
}
post.comments[i].comment_ordinal = i;
}
callback(err, post);
});
};
this.addComment = function(permalink, name, email, body, callback) {
"use strict";
var comment = {
'author': name,
'body': body
}
if (email != "") {
comment['email'] = email
}
posts.update({
'permalink': permalink
}, {
'$push': {
'comments': comment
}
}, function(err, numModified) {
"use strict";
if (err) return callback(err, null);
callback(err, numModified);
});
};
this.incrementLikes = function(permalink, comment_ordinal, callback) {
"use strict";
posts.findOne({
'permalink': permalink
}, function(err, post) {
"use strict";
console.dir(post.comments[comment_ordinal]["num_likes"]);
if (err) return callback(err, null);
if (!post.comments[comment_ordinal]["num_likes"]) {
post.comments[comment_ordinal]["num_likes"] = 0;
}
post.comments[comment_ordinal]["num_likes"] += 1;
posts.update({
'permalink': permalink
}, post, function(err, numModified) {
console.log("numModified= " + numModified);
if (err) return callback(err, null);
callback(err, numModified);
});
});
};
}
module.exports.PostsDAO = PostsDAO;
Teoría
Al crear un documento, MongoDB automáticamente crea el campo _id para el mismo. Este es único para cada documento y sobre él se establece un índice para acelerar las búsquedas.
En las bases de datos tradicionales (relacionales), el almacenamiento de datos suele ser agnóstico a la aplicación. En cambio en MongoDB es más importante cómo se usan los datos, qué consultas se realizan y si son más usados en operaciones de lectura o escritura.
Este esquema se llama dirigido por la aplicación y tiene las siguientes características:
- Documentos enriquecidos: un documento contiene toda la información relevante necesaria. No se encuentra distribuida en otros documentos.
- Pre-join o datos embebidos: un documento puede contener otros documentos embebidos (el equivalente a los joins). Estos se almacenan con atributos de arrays u objetos.
- No hay joins: el objetivo de mongo es dar un alto rendimiento. Los JOINS de SQL escalan muy mal y son el motivo de bajos rendimientos. Por tanto en mongo se tiene todos los datos en un documento y no se utilizan JOINS. Se pueden implementar pero se deberían usar de forma excepcional.
- No hay constraints: esto se suple con los datos embebidos.
- Operaciones atómicas: las operaciones son atómicas a nivel de documento.
- No hay que definir esquemas: no necesitamos definir esquemas para empezar a guardar datos en un documento. Además un documento admite múltiples estructuras de datos, no queda limitada a la estructura de la primera insercción.
Fuentes recomendadas