¿Cómo realizo el equivalente de unión SQL en MongoDB?

Resuelto The Unknown asked hace 14 años • 0 respuestas

¿Cómo realizo el equivalente de unión SQL 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 de usuario de cada una.

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 campo determinado (por ejemplo, ...find({pid:444}) ) y la información del usuario asociada con cada comentario de una sola vez?

Por el momento, primero obtengo 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.

The Unknown avatar Feb 28 '10 15:02 The Unknown
Aceptado

A partir de Mongo 3.2, la mayoría de las respuestas a esta pregunta ya no son correctas. El nuevo operador $lookup agregado al canal de agregación es esencialmente idéntico a una unión externa izquierda:

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

De los documentos:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

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

Clayton Gulick avatar Nov 03 '2015 23:11 Clayton Gulick

Podemos fusionar/unir todos los datos dentro de una sola colección con una función sencilla en pocas líneas usando la consola del cliente mongodb, y ahora podremos realizar la consulta deseada. A continuación 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);
    }
);

.- Obtener los nuevos datos de recogida:

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 puedan ayudarte.

Orlando Becerra avatar Mar 30 '2014 03:03 Orlando Becerra

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

https://mongodb-documentation.readthedocs.io/en/latest/ecosystem/tutorial/model-data-for-ruby-on-rails.html

Cuando mostremos nuestra lista de historias, necesitaremos mostrar el nombre del usuario que publicó la historia. Si estuviéramos usando una base de datos relacional, podríamos realizar una unión de usuarios y tiendas, y obtener todos nuestros objetos en una sola consulta. Pero MongoDB no admite uniones y, por lo tanto, a veces requiere un poco de desnormalización. Aquí, esto significa almacenar en caché el atributo 'nombre de usuario'.

Es posible que los puristas relacionales ya se sientan incómodos, como si estuviéramos violando alguna ley universal. Pero tengamos en cuenta que las colecciones de MongoDB no equivalen a tablas relacionales; cada uno tiene un objetivo de diseño único. Una tabla normalizada proporciona una porción atómica y aislada de datos. Un documento, sin embargo, representa más fielmente un objeto en su conjunto. 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.

William Stein avatar Feb 08 '2011 20:02 William Stein

Con la combinación correcta de $lookup , $project y $match , puede unir varias tablas con 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: vincular todas las tablas

puedes buscar tantas tablas como quieras.

$lookup : uno para cada tabla en consulta

$unwind : desnormaliza correctamente los datos; de lo contrario, estarían envueltos en matrices

Código Python..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"}
                   
                        ])

Paso 2: definir todos los condicionales

$project : define aquí todas las declaraciones condicionales, además de todas las variables que deseas seleccionar.

Código 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: Une todos los condicionales

$match : une todas las condiciones usando OR o AND, etc. Puede haber varios de estos.

$proyecto : desdefinir todos los condicionales

Código Python completo..

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
                        }}

                        ])

De esta manera se puede realizar prácticamente cualquier combinación de tablas, condicionales y uniones.

sbharti avatar Apr 19 '2017 03:04 sbharti