Gracias a Julian Motz por ayudar amablemente a revisar este artículo.
Una de las mayores diferencias entre las bases de datos SQL y NoSQL es UNIRSE. En las bases de datos relacionales, la cláusula SQL JOIN permite combinar filas de dos o más tablas utilizando un campo común entre ellas. Por ejemplo, si tiene tablas de books
y publishers
, puede escribir comandos SQL como:
SELECT book.title, publisher.nameFROM bookLEFT JOIN book.publisher_id ON publisher.id;
En otras palabras, la tabla book
tiene un campo publisher_id
que hace referencia al campo id
en la tabla publisher
.
Esto es práctico, ya que un solo editor podría ofrecer miles de libros. Si alguna vez necesitamos actualizar los detalles de un editor, podemos cambiar un solo registro. La redundancia de datos se minimiza, ya que no necesitamos repetir la información del editor para cada libro. La técnica se conoce como normalización.
Las bases de datos SQL ofrecen una gama de características de normalización y restricción para garantizar que se mantengan las relaciones.
NoSQL = = ¿Sin UNIÓN?
No siempre
Las bases de datos orientadas a documentos, como MongoDB, están diseñadas para almacenar datos desnormalizados. Idealmente, no debería haber ninguna relación entre las colecciones. Si se requieren los mismos datos en dos o más documentos, debe repetirse.
Esto puede ser frustrante, ya que hay pocas situaciones en las que nunca se necesitan datos relacionales. Afortunadamente, MongoDB 3.2 introduce un nuevo operador $lookup
que puede realizar una operación de UNIÓN EXTERNA IZQUIERDA en dos o más colecciones. Pero hay un problema
La agregación de MongoDB
$lookup
solo se permite en operaciones de agregación. Piense en estos como una canalización de operadores que consultan, filtran y agrupan un resultado. La salida de un operador se utiliza como entrada para el siguiente.La agregación
es más difícil de entender que las consultas find
más simples y generalmente se ejecutará más lentamente. Sin embargo, son potentes y una opción invaluable para operaciones de búsqueda complejas.La agregación
se explica mejor con un ejemplo. Supongamos que estamos creando una plataforma de redes sociales con una colección user
. Almacena los detalles de cada usuario en documentos separados. Por ejemplo:
{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email: "[email protected]", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z")}
Podemos agregar tantos campos como sea necesario, pero todos los documentos MongoDB requieren un campo _id
que tenga un valor único. _id
es similar a una clave principal SQL, y se insertará automáticamente si es necesario.
Nuestra red social ahora requiere una colección post
, que almacena numerosas actualizaciones detalladas de los usuarios. Los documentos almacenan el texto, la fecha, una calificación y una referencia al usuario que lo escribió en un campo user_id
:
{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date: ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important"}
Ahora queremos mostrar las últimas veinte publicaciones con una calificación «importante» de todos los usuarios en orden cronológico inverso. Cada documento devuelto debe contener el texto, la hora de la publicación y el nombre y país del usuario asociado.
A la consulta agregada de MongoDB se le pasa una matriz de operadores de canalización que definen cada operación en orden. Primero, necesitamos extraer todos los documentos de la colección post
que tengan la calificación correcta utilizando el filtro $match
:
{ "$match": { "rating": "important" } }
Ahora debemos ordenar los elementos coincidentes en orden de fecha inverso utilizando el operador $sort
:
{ "$sort": { "date": -1 } }
Dado que solo requerimos veinte publicaciones, podemos aplicar una etapa $limit
, por lo que MongoDB solo necesita procesar los datos que queremos:
{ "$limit": 20 }
Ahora podemos unir datos de la colección user
utilizando el nuevo operador $lookup
. Requiere un objeto con cuatro parámetros:
-
localField
: el campo de búsqueda en el documento de entrada -
from
: la colección a la que unirse -
foreignField
: el campo a buscar en la colecciónfrom
-
as
: el nombre del campo de salida.
Nuestro operador es por lo tanto:
{ "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo"} }
Esto creará un nuevo campo en nuestra salida llamado userinfo
. Contiene una matriz en la que cada valor coincide con el documento user
:
"userinfo":
Tenemos una relación uno a uno entre post.user_id
y user._id
, ya que un post solo puede tener un autor. Por lo tanto, nuestro array userinfo
solo contendrá un elemento. Podemos usar el operador $unwind
para deconstruirlo en un sub-documento:
{ "$unwind": "$userinfo" }
La salida ahora se convertirá a un formato más práctico que puede tener operadores adicionales aplicados:
"userinfo": { "name": "User One", "email: "[email protected]", …}
Finalmente, podemos devolver el texto, la hora de la publicación, el nombre del usuario y el país utilizando una etapa $project
en la canalización:
{ "$project": { "text": 1, "date": 1, "userinfo.name": 1, "userinfo.country": 1} }
Juntando Todo
Nuestra consulta agregada final coincide con los mensajes, ordena en orden, limita los últimos veinte elementos, une los datos de usuario, aplana la matriz de usuarios y devuelve solo los campos necesarios. El comando completo:
db.post.aggregate();
El resultado es una colección de hasta veinte documentos. Por ejemplo:
¡Genial! Finalmente puedo Cambiar a NoSQL!
MongoDB $lookup
es útil y potente, pero incluso este ejemplo básico requiere una consulta agregada compleja. No es un sustituto de la cláusula de UNIÓN más potente que se ofrece en SQL. MongoDB tampoco ofrece restricciones; si se elimina un documento user
, los documentos huérfanos post
permanecerían.
Lo ideal es que el operador $lookup
se requiera con poca frecuencia. Si lo necesita mucho, es posible que esté utilizando el almacén de datos incorrecto …
Si tiene datos relacionales, utilice una base de datos relacional (SQL).
Dicho esto, $lookup
es una adición bienvenida a MongoDB 3.2. Puede superar algunos de los problemas más frustrantes al usar pequeñas cantidades de datos relacionales en una base de datos NoSQL.