¿Cómo realizo el SQL Join equivalent en MongoDB?

¿Cómo realizo el SQL Join equivalent en MongoDB?

Por ejemplo, supongamos que tiene dos colecciones (usuarios y comentarios) y quiero extraer todos los comentarios con pid = 444 junto con la información del usuario para cada uno.

comments { uid:12345, pid:444, comment="blah" } { uid:12345, pid:888, comment="asdf" } { uid:99999, pid:444, comment="qwer" } users { uid:12345, name:"john" } { uid:99999, name:"mia" } 

¿Hay alguna manera de extraer todos los comentarios con un determinado campo (por ejemplo … encontrar ({pid: 444})) y la información del usuario asociada a cada comentario de una vez?

Por el momento, primero recibo los comentarios que coinciden con mis criterios, luego descubro todos los uid en ese conjunto de resultados, obtengo los objetos del usuario y los fusiono con los resultados del comentario. Parece que lo estoy haciendo mal.

A partir de Mongo 3.2, las respuestas a esta pregunta ya no son correctas. El nuevo operador de búsqueda $ agregado a la canalización de agregación es esencialmente idéntico a una combinación externa izquierda:

https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup

De los documentos:

 { $lookup: { from: , localField: , foreignField: , as:  } } 

Por supuesto, Mongo no es una base de datos relacional, y los desarrolladores están siendo cuidadosos en recomendar casos de uso específicos para $ lookup, pero al menos a partir de 3.2 unirse ahora es posible con MongoDB.

Esta página en el sitio oficial de mongodb aborda exactamente esta pregunta:

http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/

Cuando mostramos nuestra lista de historias, necesitaremos mostrar el nombre del usuario que publicó la historia. Si utilizáramos una base de datos relacional, podríamos unirnos a usuarios y tiendas, y obtener todos nuestros objetos en una sola consulta. Pero MongoDB no es compatible con las uniones y por lo tanto, a veces, requiere un poco de desnormalización. Aquí, esto significa el almacenamiento en caché del atributo ‘nombre de usuario’.

Los puristas relacionales pueden sentirse incómodos, como si estuviéramos violando alguna ley universal. Pero tengamos en cuenta que las colecciones de MongoDB no son equivalentes a las tablas relacionales; cada uno sirve un objective de diseño único. Una tabla normalizada proporciona un fragmento de datos aislado y atómico. Un documento, sin embargo, representa más de cerca un objeto como un todo. En el caso de un sitio de noticias sociales, se puede argumentar que un nombre de usuario es intrínseco a la historia que se publica.

Podemos fusionar / unir todos los datos dentro de una sola colección con una función fácil en pocas líneas usando la consola del cliente mongodb, y ahora podríamos realizar la consulta deseada. Debajo de un ejemplo completo,

.- Autores:

 db.authors.insert([ { _id: 'a1', name: { first: 'orlando', last: 'becerra' }, age: 27 }, { _id: 'a2', name: { first: 'mayra', last: 'sanchez' }, age: 21 } ]); 

.- Categorías:

 db.categories.insert([ { _id: 'c1', name: 'sci-fi' }, { _id: 'c2', name: 'romance' } ]); 

.- Libros

 db.books.insert([ { _id: 'b1', name: 'Groovy Book', category: 'c1', authors: ['a1'] }, { _id: 'b2', name: 'Java Book', category: 'c2', authors: ['a1','a2'] }, ]); 

.- Préstamo de libros

 db.lendings.insert([ { _id: 'l1', book: 'b1', date: new Date('01/01/11'), lendingBy: 'jose' }, { _id: 'l2', book: 'b1', date: new Date('02/02/12'), lendingBy: 'maria' } ]); 

.- La magia:

 db.books.find().forEach( function (newBook) { newBook.category = db.categories.findOne( { "_id": newBook.category } ); newBook.lendings = db.lendings.find( { "book": newBook._id } ).toArray(); newBook.authors = db.authors.find( { "_id": { $in: newBook.authors } } ).toArray(); db.booksReloaded.insert(newBook); } ); 

.- Obtenga la nueva información de la colección:

 db.booksReloaded.find().pretty() 

.- Respuesta 🙂

 { "_id" : "b1", "name" : "Groovy Book", "category" : { "_id" : "c1", "name" : "sci-fi" }, "authors" : [ { "_id" : "a1", "name" : { "first" : "orlando", "last" : "becerra" }, "age" : 27 } ], "lendings" : [ { "_id" : "l1", "book" : "b1", "date" : ISODate("2011-01-01T00:00:00Z"), "lendingBy" : "jose" }, { "_id" : "l2", "book" : "b1", "date" : ISODate("2012-02-02T00:00:00Z"), "lendingBy" : "maria" } ] } { "_id" : "b2", "name" : "Java Book", "category" : { "_id" : "c2", "name" : "romance" }, "authors" : [ { "_id" : "a1", "name" : { "first" : "orlando", "last" : "becerra" }, "age" : 27 }, { "_id" : "a2", "name" : { "first" : "mayra", "last" : "sanchez" }, "age" : 21 } ], "lendings" : [ ] } 

Espero que estas líneas te puedan ayudar.

Tienes que hacerlo de la manera que describes. MongoDB es una base de datos no relacional y no admite combinaciones.

Aquí hay un ejemplo de una colección de Actores * Actores y películas :

https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt

Hace uso del método .mapReduce()

* join : una alternativa para unirse a las bases de datos orientadas a documentos

Como otros han señalado que está tratando de crear una base de datos relacional desde ninguna base de datos relacional que realmente no desea hacer, pero de todos modos, si tiene un caso en el que tiene que hacer esto, aquí hay una solución que puede usar. Primero hacemos un descubrimiento foreach en la colección A (o en los usuarios de su caso) y luego obtenemos cada artículo como un objeto, luego usamos la propiedad del objeto (en su caso uid) para buscar en nuestra segunda colección (en su caso, comentarios) si puede encontrarlo, entonces tenemos una coincidencia y podemos imprimir o hacer algo con él. Espero que esto te ayude y buena suerte 🙂

 db.users.find().forEach( function (object) { var commonInBoth=db.comments.findOne({ "uid": object.uid} ); if (commonInBoth != null) { printjson(commonInBoth) ; printjson(object) ; }else { // did not match so we don't care in this case } }); 

Depende de lo que trates de hacer.

Actualmente lo tiene configurado como una base de datos normalizada, lo cual está bien, y la forma en que lo está haciendo es apropiado.

Sin embargo, hay otras formas de hacerlo.

Puede tener una colección de publicaciones que tenga incrustados comentarios para cada publicación con referencias a los usuarios que puede consultar de forma iterativa para obtener. Puede almacenar el nombre del usuario con los comentarios, puede almacenarlos todos en un solo documento.

Lo que ocurre con NoSQL es que está diseñado para esquemas flexibles y lectura y escritura muy rápidas. En una granja de Big Data típica, la base de datos es el cuello de botella más grande, tiene menos motores de base de datos que servidores de aplicaciones y servidores frontales … son más caros pero más potentes, también el espacio en el disco duro es muy barato comparativamente. La normalización proviene del concepto de tratar de ahorrar espacio, pero tiene un costo al hacer que sus bases de datos realicen uniones complicadas y verificar la integridad de las relaciones, al realizar operaciones en cascada. Todo lo cual ahorra a los desarrolladores algunos dolores de cabeza si diseñaron la base de datos correctamente.

Con NoSQL, si acepta que la redundancia y el espacio de almacenamiento no son problemas debido a su costo (tanto en el tiempo de procesador requerido para realizar actualizaciones como en el disco duro para almacenar datos adicionales), la desnormalización no es un problema (para arreglos integrados que se convierten cientos de miles de elementos puede ser un problema de rendimiento, pero la mayoría de las veces eso no es un problema). Además, tendrá varios servidores de aplicación y de interfaz para cada clúster de base de datos. Haga que hagan el trabajo pesado de las uniones y deje que los servidores de bases de datos se adhieran a la lectura y la escritura.

TL; DR: Lo que estás haciendo está bien, y hay otras formas de hacerlo. Consulte los patrones del modelo de datos de la documentación de mongodb para obtener algunos ejemplos excelentes. http://docs.mongodb.org/manual/data-modeling/

Hay una especificación que admiten muchos controladores que se llama DBRef.

DBRef es una especificación más formal para crear referencias entre documentos. DBRefs (generalmente) incluyen un nombre de colección así como una identificación de objeto. La mayoría de los desarrolladores solo usan DBRefs si la colección puede cambiar de un documento a otro. Si su colección referenciada siempre será la misma, las referencias manuales descritas anteriormente son más eficientes.

Tomado de MongoDB Documentation: Modelos de datos> Referencia del modelo de datos> Referencias de la base de datos

Puede unirse a dos colecciones en Mongo utilizando la búsqueda que se ofrece en la versión 3.2. En tu caso, la consulta sería

 db.comments.aggregate({ $lookup:{ from:"users", localField:"uid", foreignField:"uid", as:"users_comments" } }) 

o también puede unirse con respecto a los usuarios, entonces habrá un pequeño cambio como se indica a continuación.

 db.users.aggregate({ $lookup:{ from:"comments", localField:"uid", foreignField:"uid", as:"users_comments" } }) 

Funcionará igual que la combinación izquierda y derecha en SQL.

Con la combinación correcta de $ lookup , $ project y $ match , puede unir varias tablas en múltiples parámetros. Esto se debe a que se pueden encadenar varias veces.

Supongamos que queremos hacer lo siguiente ( referencia )

 SELECT S.* FROM LeftTable S LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND S.MOB IS NOT NULL 

Paso 1: enlace todas las tablas

puede $ buscar tantas tablas como desee.

$ búsqueda – uno para cada tabla en consulta

$ unwind – porque los datos están desnormalizados correctamente, de lo contrario envueltos en matrices

Código de Python …

 db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "R"} ]) 

Paso 2: define todos los condicionales

$ project : defina aquí todas las declaraciones condicionales, más todas las variables que quiera seleccionar.

Código de Python …

 db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "R"}, # define conditionals + variables {"$project": { "midEq": {"$eq": ["$MID", "$R.MID"]}, "ID": 1, "MOB": 1, "MID": 1 }} ]) 

Paso 3: únete a todos los condicionales

$ match – únete a todas las condiciones usando OR o AND etc. Puede haber múltiplos de estos.

$ project : undefine todos los condicionales

Código de Python …

 db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "$R"}, # define conditionals + variables {"$project": { "midEq": {"$eq": ["$MID", "$R.MID"]}, "ID": 1, "MOB": 1, "MID": 1 }}, # join all conditionals {"$match": { "$and": [ {"R.TIM": {"$gt": 0}}, {"MOB": {"$exists": True}}, {"midEq": {"$eq": True}} ]}}, # undefine conditionals {"$project": { "midEq": 0 }} ]) 

Casi cualquier combinación de tablas, condicionales y combinaciones se puede hacer de esta manera.

Antes de 3.2.6 , Mongodb no admite la consulta de join como mysql. a continuación solución que funciona para usted.

  db.getCollection('comments').aggregate([ {$match : {pid : 444}}, {$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}}, ]) 

Puede ejecutar consultas SQL, incluyendo join en MongoDB con mongo_fdw de Postgres.

MongoDB no permite uniones, pero puedes usar complementos para manejar eso. Compruebe el plugin mongo-join. Es lo mejor y ya lo he usado. Puede instalarlo usando npm directamente como este npm install mongo-join . Puede consultar la documentación completa con ejemplos .

(++) herramienta realmente útil cuando necesitamos unirnos a colecciones (N)

(-) podemos aplicar condiciones solo en el nivel superior de la consulta

Ejemplo

 var Join = require('mongo-join').Join, mongodb = require('mongodb'), Db = mongodb.Db, Server = mongodb.Server; db.open(function (err, Database) { Database.collection('Appoint', function (err, Appoints) { /* we can put conditions just on the top level */ Appoints.find({_id_Doctor: id_doctor ,full_date :{ $gte: start_date }, full_date :{ $lte: end_date }}, function (err, cursor) { var join = new Join(Database).on({ field: '_id_Doctor', // <- field in Appoints document to: '_id', // <- field in User doc. treated as ObjectID automatically. from: 'User' // <- collection name for User doc }).on({ field: '_id_Patient', // <- field in Appoints doc to: '_id', // <- field in User doc. treated as ObjectID automatically. from: 'User' // <- collection name for User doc }) join.toArray(cursor, function (err, joinedDocs) { /* do what ever you want here */ /* you can fetch the table and apply your own conditions */ ..... ..... ..... resp.status(200); resp.json({ "status": 200, "message": "success", "Appoints_Range": joinedDocs, }); return resp; }); }); 

$ búsqueda (agregación)

Realiza una combinación externa izquierda a una colección no marcada en la misma base de datos para filtrar documentos de la colección “unida” para su procesamiento. Para cada documento de entrada, la etapa $ lookup agrega un nuevo campo de matriz cuyos elementos son los documentos coincidentes de la colección “unida”. La etapa $ lookup pasa estos documentos reformados a la siguiente etapa. La etapa $ lookup tiene las siguientes syntax:

Equality Match

Para realizar una coincidencia de igualdad entre un campo de los documentos de entrada con un campo de los documentos de la colección “unida”, la etapa de búsqueda $ tiene la siguiente syntax:

 { $lookup: { from: , localField: , foreignField: , as:  } } 

La operación correspondería a la siguiente instrucción pseudo-SQL:

 SELECT *,  FROM collection WHERE  IN (SELECT  FROM  WHERE  ); 

Mongo URL

playORM puede hacerlo por usted usando S-SQL (SQL escalable) que simplemente agrega particiones para que pueda hacer uniones dentro de las particiones.

Puede hacerlo utilizando la canalización de agregación, pero es difícil escribirlo usted mismo.

Puede usar mongo-join-query para crear la canalización de agregación automáticamente desde su consulta.

Así es como se vería su consulta:

 const mongoose = require("mongoose"); const joinQuery = require("mongo-join-query"); joinQuery( mongoose.models.Comment, { find: { pid:444 }, populate: ["uid"] }, (err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results)) ); 

Su resultado tendría el objeto del usuario en el campo uid y puede vincular tantos niveles profundos como desee. Puede rellenar la referencia al usuario, que hace referencia a un Equipo, que hace referencia a otra cosa, etc.

Descargo de responsabilidad : escribí mongo-join-query para abordar este problema exacto.

Creo que si necesita tablas de datos normalizadas, debe probar otras soluciones de bases de datos.

Pero he fundado esa solución para MOngo en Git. Por cierto, en el código de inserciones: tiene el nombre de la película, pero no la de la película .

Problema

Tienes una colección de actores con una variedad de películas que han hecho.

Desea generar una colección de películas con una matriz de actores en cada una.

Algunos datos de muestra

  db.actors.insert( { actor: "Richard Gere", movies: ['Pretty Woman', 'Runaway Bride', 'Chicago'] }); db.actors.insert( { actor: "Julia Roberts", movies: ['Pretty Woman', 'Runaway Bride', 'Erin Brockovich'] }); 

Solución

Necesitamos recorrer cada película en el documento Actor y emitir cada Película individualmente.

La captura aquí está en la fase de reducción. No podemos emitir una matriz desde la fase de reducción, por lo que debemos construir una matriz Actores dentro del documento “valor” que se devuelve.

El código

 map = function() { for(var i in this.movies){ key = { movie: this.movies[i] }; value = { actors: [ this.actor ] }; emit(key, value); } } reduce = function(key, values) { actor_list = { actors: [] }; for(var i in values) { actor_list.actors = values[i].actors.concat(actor_list.actors); } return actor_list; } 

Observe cómo actor_list es en realidad un objeto javascript que contiene una matriz. También observe que el mapa emite la misma estructura.

Ejecute lo siguiente para ejecutar el mapa / reducir, enviarlo a la colección “pivote” e imprimir el resultado:

printjson (db.actors.mapReduce (map, reduce, “pivot”)); db.pivot.find (). forEach (printjson);

Aquí está el resultado de la muestra, tenga en cuenta que “Pretty Woman” y “Runaway Bride” tienen “Richard Gere” y “Julia Roberts”.

 { "_id" : { "movie" : "Chicago" }, "value" : { "actors" : [ "Richard Gere" ] } } { "_id" : { "movie" : "Erin Brockovich" }, "value" : { "actors" : [ "Julia Roberts" ] } } { "_id" : { "movie" : "Pretty Woman" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } } { "_id" : { "movie" : "Runaway Bride" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } } 

No, parece que no lo estás haciendo mal. Las uniones de MongoDB son “del lado del cliente”. Casi como dijiste:

Por el momento, primero recibo los comentarios que coinciden con mis criterios, luego descubro todos los uid en ese conjunto de resultados, obtengo los objetos del usuario y los fusiono con los resultados del comentario. Parece que lo estoy haciendo mal.

 1) Select from the collection you're interested in. 2) From that collection pull out ID's you need 3) Select from other collections 4) Decorate your original results. 

No es una unión “real”, pero en realidad es mucho más útil que una combinación de SQL porque no tiene que ocuparse de filas duplicadas para uniones de “muchos” lados, sino que decora el conjunto originalmente seleccionado.

Hay muchas tonterías y FUD en esta página. Resulta que 5 años después, MongoDB sigue siendo una cosa.

Podemos fusionar dos colecciones mediante el uso de sub consulta de mongoDB. Aquí hay un ejemplo, Commentss–

 `db.commentss.insert([ { uid:12345, pid:444, comment:"blah" }, { uid:12345, pid:888, comment:"asdf" }, { uid:99999, pid:444, comment:"qwer" }])` 

Usuarios–

 db.userss.insert([ { uid:12345, name:"john" }, { uid:99999, name:"mia" }]) 

Sub consulta de MongoDB para JOIN–

 `db.commentss.find().forEach( function (newComments) { newComments.userss = db.userss.find( { "uid": newComments.uid } ).toArray(); db.newCommentUsers.insert(newComments); } );` 

Obtenga el resultado de la nueva colección generada

 db.newCommentUsers.find().pretty() 

Resultado–

 `{ "_id" : ObjectId("5511236e29709afa03f226ef"), "uid" : 12345, "pid" : 444, "comment" : "blah", "userss" : [ { "_id" : ObjectId("5511238129709afa03f226f2"), "uid" : 12345, "name" : "john" } ] } { "_id" : ObjectId("5511236e29709afa03f226f0"), "uid" : 12345, "pid" : 888, "comment" : "asdf", "userss" : [ { "_id" : ObjectId("5511238129709afa03f226f2"), "uid" : 12345, "name" : "john" } ] } { "_id" : ObjectId("5511236e29709afa03f226f1"), "uid" : 99999, "pid" : 444, "comment" : "qwer", "userss" : [ { "_id" : ObjectId("5511238129709afa03f226f3"), "uid" : 99999, "name" : "mia" } ] }` 

Espero que esto ayude.