MongoDB: Combina datos de múltiples colecciones en una ... ¿cómo?


229

¿Cómo puedo (en MongoDB) combinar datos de múltiples colecciones en una sola colección?

¿Puedo usar map-reduce y si es así, cómo?

Agradecería mucho algún ejemplo, ya que soy un novato.


18
¿Solo desea copiar documentos de diferentes colecciones en una sola colección o cuál es su plan? ¿Se puede especificar "combinar"? Si solo quieres copiar a través de mongo shell, a db.collection1.find().forEach(function(doc){db.collection2.save(doc)});es suficiente. Especifique su controlador usado (java, php, ...) si no usa mongo shell.
proximus

así que tengo una colección (por ejemplo, usuarios) que otras colecciones, por ejemplo, colección de libreta de direcciones, lista de colecciones de libros, etc. ¿Cómo puedo, en función de la clave say user_id, combinar estas colecciones en una sola colección? ?
user697697

Respuestas:


147

Aunque no puede hacer esto en tiempo real, puede ejecutar map-reduce varias veces para fusionar datos mediante la opción "reduce" en MongoDB 1.8+ map / reduce (consulte http://www.mongodb.org/ display / DOCS / MapReduce # MapReduce-Outputoptions ). Debe tener alguna clave en ambas colecciones que pueda usar como _id.

Por ejemplo, supongamos que tiene una userscolección y una commentscolección y desea tener una nueva colección que tenga información demográfica del usuario para cada comentario.

Digamos que la userscolección tiene los siguientes campos:

  • _carné de identidad
  • nombre de pila
  • apellido
  • país
  • género
  • años

Y luego la commentscolección tiene los siguientes campos:

  • _carné de identidad
  • ID de usuario
  • comentario
  • creado

Harías este mapa / reducir:

var mapUsers, mapComments, reduce;
db.users_comments.remove();

// setup sample data - wouldn't actually use this in production
db.users.remove();
db.comments.remove();
db.users.save({firstName:"Rich",lastName:"S",gender:"M",country:"CA",age:"18"});
db.users.save({firstName:"Rob",lastName:"M",gender:"M",country:"US",age:"25"});
db.users.save({firstName:"Sarah",lastName:"T",gender:"F",country:"US",age:"13"});
var users = db.users.find();
db.comments.save({userId: users[0]._id, "comment": "Hey, what's up?", created: new ISODate()});
db.comments.save({userId: users[1]._id, "comment": "Not much", created: new ISODate()});
db.comments.save({userId: users[0]._id, "comment": "Cool", created: new ISODate()});
// end sample data setup

mapUsers = function() {
    var values = {
        country: this.country,
        gender: this.gender,
        age: this.age
    };
    emit(this._id, values);
};
mapComments = function() {
    var values = {
        commentId: this._id,
        comment: this.comment,
        created: this.created
    };
    emit(this.userId, values);
};
reduce = function(k, values) {
    var result = {}, commentFields = {
        "commentId": '', 
        "comment": '',
        "created": ''
    };
    values.forEach(function(value) {
        var field;
        if ("comment" in value) {
            if (!("comments" in result)) {
                result.comments = [];
            }
            result.comments.push(value);
        } else if ("comments" in value) {
            if (!("comments" in result)) {
                result.comments = [];
            }
            result.comments.push.apply(result.comments, value.comments);
        }
        for (field in value) {
            if (value.hasOwnProperty(field) && !(field in commentFields)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.users.mapReduce(mapUsers, reduce, {"out": {"reduce": "users_comments"}});
db.comments.mapReduce(mapComments, reduce, {"out": {"reduce": "users_comments"}});
db.users_comments.find().pretty(); // see the resulting collection

En este punto, tendrá una nueva colección llamada users_commentsque contiene los datos combinados y ahora puede usarla. Todas estas colecciones reducidas tienen _idla clave que emitía en sus funciones de mapa y luego todos los valores son un subobjeto dentro de la valueclave: los valores no están en el nivel superior de estos documentos reducidos.

Este es un ejemplo algo simple. Puede repetir esto con más colecciones tanto como quiera seguir acumulando la colección reducida. También puede hacer resúmenes y agregaciones de datos en el proceso. Probablemente definiría más de una función de reducción, ya que la lógica para agregar y preservar los campos existentes se vuelve más compleja.

También notará que ahora hay un documento para cada usuario con todos los comentarios de ese usuario en una matriz. Si fusionáramos datos que tienen una relación uno a uno en lugar de uno a muchos, sería plano y simplemente podría usar una función de reducción como esta:

reduce = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};

Si desea aplanar la users_commentscolección para que sea un documento por comentario, ejecute esto adicionalmente:

var map, reduce;
map = function() {
    var debug = function(value) {
        var field;
        for (field in value) {
            print(field + ": " + value[field]);
        }
    };
    debug(this);
    var that = this;
    if ("comments" in this.value) {
        this.value.comments.forEach(function(value) {
            emit(value.commentId, {
                userId: that._id,
                country: that.value.country,
                age: that.value.age,
                comment: value.comment,
                created: value.created,
            });
        });
    }
};
reduce = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.users_comments.mapReduce(map, reduce, {"out": "comments_with_demographics"});

Esta técnica definitivamente no debe realizarse sobre la marcha. Es adecuado para un trabajo cron o algo así que actualiza los datos combinados periódicamente. Probablemente querrá ejecutar ensureIndexla nueva colección para asegurarse de que las consultas que realice contra ella se ejecuten rápidamente (tenga en cuenta que sus datos aún están dentro de una valueclave, por lo que si tuviera que indexar comments_with_demographicsel createdtiempo de comentario , seríadb.comments_with_demographics.ensureIndex({"value.created": 1});


1
Probablemente nunca haría eso en software de producción, pero sigue siendo una técnica genial.
Dave Griffith el

3
Gracias Dave. Utilicé esta técnica para generar tablas de exportación e informes para un sitio de alto tráfico en producción durante los últimos 3 meses sin problemas. Aquí hay otro artículo que describe un uso similar de la técnica: tebros.com/2011/07/…
rmarscher

1
Gracias @rmarscher, tus detalles adicionales realmente me ayudaron a comprender mejor todo.
benstr

55
Debería actualizar esta respuesta con un ejemplo utilizando la canalización de agregación y la nueva operación de búsqueda $. Mencionándolo aquí hasta que pueda armar una redacción adecuada. docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher

1
Para su información, para aquellos que desean asimilar rápidamente lo que hace, esto es lo que hay en la users_commentscolección después del primer bloque de código gist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Nolan Amy el

127

MongoDB 3.2 ahora permite combinar datos de múltiples colecciones en una a través de la etapa de agregación $ lookup . Como ejemplo práctico, supongamos que tiene datos sobre libros divididos en dos colecciones diferentes.

Primera colección, llamada books, que tiene los siguientes datos:

{
    "isbn": "978-3-16-148410-0",
    "title": "Some cool book",
    "author": "John Doe"
}
{
    "isbn": "978-3-16-148999-9",
    "title": "Another awesome book",
    "author": "Jane Roe"
}

Y la segunda colección, llamada books_selling_data, tiene los siguientes datos:

{
    "_id": ObjectId("56e31bcf76cdf52e541d9d26"),
    "isbn": "978-3-16-148410-0",
    "copies_sold": 12500
}
{
    "_id": ObjectId("56e31ce076cdf52e541d9d28"),
    "isbn": "978-3-16-148999-9",
    "copies_sold": 720050
}
{
    "_id": ObjectId("56e31ce076cdf52e541d9d29"),
    "isbn": "978-3-16-148999-9",
    "copies_sold": 1000
}

Para fusionar ambas colecciones es solo una cuestión de usar $ lookup de la siguiente manera:

db.books.aggregate([{
    $lookup: {
            from: "books_selling_data",
            localField: "isbn",
            foreignField: "isbn",
            as: "copies_sold"
        }
}])

Después de esta agregación, la bookscolección tendrá el siguiente aspecto:

{
    "isbn": "978-3-16-148410-0",
    "title": "Some cool book",
    "author": "John Doe",
    "copies_sold": [
        {
            "_id": ObjectId("56e31bcf76cdf52e541d9d26"),
            "isbn": "978-3-16-148410-0",
            "copies_sold": 12500
        }
    ]
}
{
    "isbn": "978-3-16-148999-9",
    "title": "Another awesome book",
    "author": "Jane Roe",
    "copies_sold": [
        {
            "_id": ObjectId("56e31ce076cdf52e541d9d28"),
            "isbn": "978-3-16-148999-9",
            "copies_sold": 720050
        },
        {
            "_id": ObjectId("56e31ce076cdf52e541d9d28"),
            "isbn": "978-3-16-148999-9",
            "copies_sold": 1000
        }
    ]
}

Es importante tener en cuenta algunas cosas:

  1. La colección "de", en este caso books_selling_data, no se puede fragmentar.
  2. El campo "como" será una matriz, como en el ejemplo anterior.
  3. Las opciones "localField" y "foreignField" en la etapa de búsqueda $ serán tratadas como nulas para propósitos de correspondencia si no existen en sus colecciones respectivas (los documentos de búsqueda $ tienen un ejemplo perfecto sobre eso).

Entonces, como conclusión, si desea consolidar ambas colecciones, teniendo, en este caso, un campo plano copias_vendidas con el total de copias vendidas, tendrá que trabajar un poco más, probablemente utilizando una colección intermedia que, entonces, ser $ a cabo a la colección definitiva.


Hola, por favor, ¿pueden decir cuál será la forma optimizada de administrar datos como este: User, file.files y file.chunks son tres colecciones, quiero un usuario específico con todos sus archivos relacionados en una respuesta, ¿es posible? {"name": "batMan", "email ':" bt@gmail.com "," files ": [{file1}, {file2}, {file3}, .... etc.]
mfaisalhyder

Los ejemplos de documentación oficial para la solución anterior se pueden encontrar aquí: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Jakub Czaplicki

44
Bueno, en realidad mi respuesta ya tenía tres enlaces a la documentación oficial. Pero gracias por tu contribución de todos modos. @JakubCzaplicki
Bruno Krebs

2
Podría estar teniendo un mal funcionamiento total del cerebro (lo más probable), pero $lookup¿no deberían estar todos "localField" y "foreignField" iguales "isbn"? no "_id" e "isbn"?
Dev01

13

Si no hay una inserción masiva en mongodb, colocamos todos los objetos en bucle small_collectiony los insertamos uno por uno en big_collection:

db.small_collection.find().forEach(function(obj){ 
   db.big_collection.insert(obj)
});

db.colleciton.insert ([{}, {}, {}]) Insert acepta matrices.
augurone

2
esto funciona bien para pequeñas colecciones, pero no se olvide de migrar índices :)
Sebastien Lorber

12

Ejemplo muy básico con $ lookup.

db.getCollection('users').aggregate([
    {
        $lookup: {
            from: "userinfo",
            localField: "userId",
            foreignField: "userId",
            as: "userInfoData"
        }
    },
    {
        $lookup: {
            from: "userrole",
            localField: "userId",
            foreignField: "userId",
            as: "userRoleData"
        }
    },
    { $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }},
    { $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}
])

Aquí se usa

 { $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }}, 
 { $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}

En vez de

{ $unwind:"$userRoleData"} 
{ $unwind:"$userRoleData"}

Porque {$ unwind: "$ userRoleData"} esto devolverá un resultado vacío o 0 si no se encuentra un registro coincidente con $ lookup.


11

Es posible hacer uniones en MongoDB de una manera 'UNIÓN SQL' usando agregaciones junto con búsquedas, en una sola consulta. Aquí hay un ejemplo que he probado que funciona con MongoDB 4.0:

// Create employees data for testing the union.
db.getCollection('employees').insert({ name: "John", type: "employee", department: "sales" });
db.getCollection('employees').insert({ name: "Martha", type: "employee", department: "accounting" });
db.getCollection('employees').insert({ name: "Amy", type: "employee", department: "warehouse" });
db.getCollection('employees').insert({ name: "Mike", type: "employee", department: "warehouse"  });

// Create freelancers data for testing the union.
db.getCollection('freelancers').insert({ name: "Stephany", type: "freelancer", department: "accounting" });
db.getCollection('freelancers').insert({ name: "Martin", type: "freelancer", department: "sales" });
db.getCollection('freelancers').insert({ name: "Doug", type: "freelancer", department: "warehouse"  });
db.getCollection('freelancers').insert({ name: "Brenda", type: "freelancer", department: "sales"  });

// Here we do a union of the employees and freelancers using a single aggregation query.
db.getCollection('freelancers').aggregate( // 1. Use any collection containing at least one document.
  [
    { $limit: 1 }, // 2. Keep only one document of the collection.
    { $project: { _id: '$$REMOVE' } }, // 3. Remove everything from the document.

    // 4. Lookup collections to union together.
    { $lookup: { from: 'employees', pipeline: [{ $match: { department: 'sales' } }], as: 'employees' } },
    { $lookup: { from: 'freelancers', pipeline: [{ $match: { department: 'sales' } }], as: 'freelancers' } },

    // 5. Union the collections together with a projection.
    { $project: { union: { $concatArrays: ["$employees", "$freelancers"] } } },

    // 6. Unwind and replace root so you end up with a result set.
    { $unwind: '$union' },
    { $replaceRoot: { newRoot: '$union' } }
  ]);

Aquí está la explicación de cómo funciona:

  1. Cree una instancia aggregatede cualquier colección de su base de datos que tenga al menos un documento. Si no puede garantizar que una colección de su base de datos no estará vacía, puede solucionar este problema creando en su base de datos algún tipo de colección 'ficticia' que contenga un único documento vacío que estará allí específicamente para realizar consultas sindicales.

  2. Haga que la primera etapa de su tubería sea { $limit: 1 }. Esto eliminará todos los documentos de la colección, excepto el primero.

  3. Elimine todos los campos del documento restante utilizando una $projectetapa:

    { $project: { _id: '$$REMOVE' } }
  4. Su agregado ahora contiene un único documento vacío. Es hora de agregar búsquedas para cada colección que desee unir. Usted puede utilizar el pipelinecampo para hacer un poco de filtrado específico, o salir localFieldy foreignFieldcomo nulo para que coincida con la colección completa.

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. Ahora tiene un agregado que contiene un único documento que contiene 3 matrices como esta:

    {
        Collection1: [...],
        Collection2: [...],
        Collection3: [...]
    }

    Luego puede combinarlos en una sola matriz usando una $projectetapa junto con el $concatArraysoperador de agregación:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. Ahora tiene un agregado que contiene un único documento, en el que se encuentra una matriz que contiene su unión de colecciones. Lo que queda por hacer es agregar una $unwindy una $replaceRootetapa para dividir su matriz en documentos separados:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. Voilà. Ahora tiene un conjunto de resultados que contiene las colecciones que desea unir. Luego puede agregar más etapas para filtrarlo aún más, ordenarlo, aplicar skip () y limit (). Casi todo lo que quieras.


La consulta falla con el mensaje "$ proyección requiere al menos un campo de salida".
abhishek_ganta

@abhishek Si obtienes eso es porque intentaste quitar todos los campos del documento en una sola etapa de proyección. MongoDB no te dejará hacer esto. Para solucionar esto, debe hacer 2 proyecciones sucesivas donde la primera elimina todo menos el _id, y la segunda elimina el _id restante.
sboisse

@abhishek He simplificado aún más la consulta al reemplazar las etapas $ project en una sola que usa la variable '$$ REMOVE'. También agregué un ejemplo concreto de que puede copiar y pegar directamente en su probador de consultas para ver si funciona.
sboisse

@sboisse, esta solución funciona para colecciones más pequeñas, sin embargo, si quiero hacer esto en grandes colecciones (más de 100,000 documentos), me encuentro con un "Tamaño total de documentos en collectionToUnion1 que excede el tamaño máximo de documento". En los documentos, sugiere poner un $ desenrollar directamente después de la búsqueda $ para evitar crear documentos intermedios grandes. No he tenido éxito en modificar esta solución usando ese método. ¿Te has encontrado con este problema y has tenido que usar ese método? Enlace a los documentos a los que me refiero : [link] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/… )
lucky7samson

@ lucky7samson desafortunadamente, la cantidad de datos que tuve que tratar no fue tan grande. Así que no tuve que enfrentar el problema al que te refieres. En mi caso, podría aplicar el filtrado en la colección para buscar antes de fusionar los registros con el resto, por lo que la cantidad de datos para la unión fue bastante pequeña.
sboisse

9

use múltiples búsquedas $ para múltiples colecciones en agregación

consulta:

db.getCollection('servicelocations').aggregate([
  {
    $match: {
      serviceLocationId: {
        $in: ["36728"]
      }
    }
  },
  {
    $lookup: {
      from: "orders",
      localField: "serviceLocationId",
      foreignField: "serviceLocationId",
      as: "orders"
    }
  },
  {
    $lookup: {
      from: "timewindowtypes",
      localField: "timeWindow.timeWindowTypeId",
      foreignField: "timeWindowTypeId",
      as: "timeWindow"
    }
  },
  {
    $lookup: {
      from: "servicetimetypes",
      localField: "serviceTimeTypeId",
      foreignField: "serviceTimeTypeId",
      as: "serviceTime"
    }
  },
  {
    $unwind: "$orders"
  },
  {
    $unwind: "$serviceTime"
  },
  {
    $limit: 14
  }
])

resultado:

{
    "_id" : ObjectId("59c3ac4bb7799c90ebb3279b"),
    "serviceLocationId" : "36728",
    "regionId" : 1.0,
    "zoneId" : "DXBZONE1",
    "description" : "AL HALLAB REST EMIRATES MALL",
    "locationPriority" : 1.0,
    "accountTypeId" : 1.0,
    "locationType" : "SERVICELOCATION",
    "location" : {
        "makani" : "",
        "lat" : 25.119035,
        "lng" : 55.198694
    },
    "deliveryDays" : "MTWRFSU",
    "timeWindow" : [ 
        {
            "_id" : ObjectId("59c3b0a3b7799c90ebb32cde"),
            "timeWindowTypeId" : "1",
            "Description" : "MORNING",
            "timeWindow" : {
                "openTime" : "06:00",
                "closeTime" : "08:00"
            },
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b0a3b7799c90ebb32cdf"),
            "timeWindowTypeId" : "1",
            "Description" : "MORNING",
            "timeWindow" : {
                "openTime" : "09:00",
                "closeTime" : "10:00"
            },
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b0a3b7799c90ebb32ce0"),
            "timeWindowTypeId" : "1",
            "Description" : "MORNING",
            "timeWindow" : {
                "openTime" : "10:30",
                "closeTime" : "11:30"
            },
            "accountId" : 1.0
        }
    ],
    "address1" : "",
    "address2" : "",
    "phone" : "",
    "city" : "",
    "county" : "",
    "state" : "",
    "country" : "",
    "zipcode" : "",
    "imageUrl" : "",
    "contact" : {
        "name" : "",
        "email" : ""
    },
    "status" : "ACTIVE",
    "createdBy" : "",
    "updatedBy" : "",
    "updateDate" : "",
    "accountId" : 1.0,
    "serviceTimeTypeId" : "1",
    "orders" : [ 
        {
            "_id" : ObjectId("59c3b291f251c77f15790f92"),
            "orderId" : "AQ18O1704264",
            "serviceLocationId" : "36728",
            "orderNo" : "AQ18O1704264",
            "orderDate" : "18-Sep-17",
            "description" : "AQ18O1704264",
            "serviceType" : "Delivery",
            "orderSource" : "Import",
            "takenBy" : "KARIM",
            "plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
            "plannedDeliveryTime" : "",
            "actualDeliveryDate" : "",
            "actualDeliveryTime" : "",
            "deliveredBy" : "",
            "size1" : 296.0,
            "size2" : 3573.355,
            "size3" : 240.811,
            "jobPriority" : 1.0,
            "cancelReason" : "",
            "cancelDate" : "",
            "cancelBy" : "",
            "reasonCode" : "",
            "reasonText" : "",
            "status" : "",
            "lineItems" : [ 
                {
                    "ItemId" : "BNWB020",
                    "size1" : 15.0,
                    "size2" : 78.6,
                    "size3" : 6.0
                }, 
                {
                    "ItemId" : "BNWB021",
                    "size1" : 20.0,
                    "size2" : 252.0,
                    "size3" : 11.538
                }, 
                {
                    "ItemId" : "BNWB023",
                    "size1" : 15.0,
                    "size2" : 285.0,
                    "size3" : 16.071
                }, 
                {
                    "ItemId" : "CPMW112",
                    "size1" : 3.0,
                    "size2" : 25.38,
                    "size3" : 1.731
                }, 
                {
                    "ItemId" : "MMGW001",
                    "size1" : 25.0,
                    "size2" : 464.375,
                    "size3" : 46.875
                }, 
                {
                    "ItemId" : "MMNB218",
                    "size1" : 50.0,
                    "size2" : 920.0,
                    "size3" : 60.0
                }, 
                {
                    "ItemId" : "MMNB219",
                    "size1" : 50.0,
                    "size2" : 630.0,
                    "size3" : 40.0
                }, 
                {
                    "ItemId" : "MMNB220",
                    "size1" : 50.0,
                    "size2" : 416.0,
                    "size3" : 28.846
                }, 
                {
                    "ItemId" : "MMNB270",
                    "size1" : 50.0,
                    "size2" : 262.0,
                    "size3" : 20.0
                }, 
                {
                    "ItemId" : "MMNB302",
                    "size1" : 15.0,
                    "size2" : 195.0,
                    "size3" : 6.0
                }, 
                {
                    "ItemId" : "MMNB373",
                    "size1" : 3.0,
                    "size2" : 45.0,
                    "size3" : 3.75
                }
            ],
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b291f251c77f15790f9d"),
            "orderId" : "AQ137O1701240",
            "serviceLocationId" : "36728",
            "orderNo" : "AQ137O1701240",
            "orderDate" : "18-Sep-17",
            "description" : "AQ137O1701240",
            "serviceType" : "Delivery",
            "orderSource" : "Import",
            "takenBy" : "KARIM",
            "plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
            "plannedDeliveryTime" : "",
            "actualDeliveryDate" : "",
            "actualDeliveryTime" : "",
            "deliveredBy" : "",
            "size1" : 28.0,
            "size2" : 520.11,
            "size3" : 52.5,
            "jobPriority" : 1.0,
            "cancelReason" : "",
            "cancelDate" : "",
            "cancelBy" : "",
            "reasonCode" : "",
            "reasonText" : "",
            "status" : "",
            "lineItems" : [ 
                {
                    "ItemId" : "MMGW001",
                    "size1" : 25.0,
                    "size2" : 464.38,
                    "size3" : 46.875
                }, 
                {
                    "ItemId" : "MMGW001-F1",
                    "size1" : 3.0,
                    "size2" : 55.73,
                    "size3" : 5.625
                }
            ],
            "accountId" : 1.0
        }, 
        {
            "_id" : ObjectId("59c3b291f251c77f15790fd8"),
            "orderId" : "AQ110O1705036",
            "serviceLocationId" : "36728",
            "orderNo" : "AQ110O1705036",
            "orderDate" : "18-Sep-17",
            "description" : "AQ110O1705036",
            "serviceType" : "Delivery",
            "orderSource" : "Import",
            "takenBy" : "KARIM",
            "plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
            "plannedDeliveryTime" : "",
            "actualDeliveryDate" : "",
            "actualDeliveryTime" : "",
            "deliveredBy" : "",
            "size1" : 60.0,
            "size2" : 1046.0,
            "size3" : 68.0,
            "jobPriority" : 1.0,
            "cancelReason" : "",
            "cancelDate" : "",
            "cancelBy" : "",
            "reasonCode" : "",
            "reasonText" : "",
            "status" : "",
            "lineItems" : [ 
                {
                    "ItemId" : "MMNB218",
                    "size1" : 50.0,
                    "size2" : 920.0,
                    "size3" : 60.0
                }, 
                {
                    "ItemId" : "MMNB219",
                    "size1" : 10.0,
                    "size2" : 126.0,
                    "size3" : 8.0
                }
            ],
            "accountId" : 1.0
        }
    ],
    "serviceTime" : {
        "_id" : ObjectId("59c3b07cb7799c90ebb32cdc"),
        "serviceTimeTypeId" : "1",
        "serviceTimeType" : "nohelper",
        "description" : "",
        "fixedTime" : 30.0,
        "variableTime" : 0.0,
        "accountId" : 1.0
    }
}

1

Mongorestore tiene esta característica de agregar encima de lo que ya está en la base de datos, por lo que este comportamiento podría usarse para combinar dos colecciones:

  1. mongodump collection1
  2. collection2.rename (colección1)
  3. mongorestore

Todavía no lo intenté, pero podría funcionar más rápido que el enfoque de mapa / reducción.


1

Comenzando Mongo 4.4, podemos lograr esta unión dentro de una tubería de agregación al acoplar la nueva $unionWithetapa de agregación con $groupel nuevo $accumulatoroperador:

// > db.users.find()
//   [{ user: 1, name: "x" }, { user: 2, name: "y" }]
// > db.books.find()
//   [{ user: 1, book: "a" }, { user: 1, book: "b" }, { user: 2, book: "c" }]
// > db.movies.find()
//   [{ user: 1, movie: "g" }, { user: 2, movie: "h" }, { user: 2, movie: "i" }]
db.users.aggregate([
  { $unionWith: "books"  },
  { $unionWith: "movies" },
  { $group: {
    _id: "$user",
    user: {
      $accumulator: {
        accumulateArgs: ["$name", "$book", "$movie"],
        init: function() { return { books: [], movies: [] } },
        accumulate: function(user, name, book, movie) {
          if (name) user.name = name;
          if (book) user.books.push(book);
          if (movie) user.movies.push(movie);
          return user;
        },
        merge: function(userV1, userV2) {
          if (userV2.name) userV1.name = userV2.name;
          userV1.books.concat(userV2.books);
          userV1.movies.concat(userV2.movies);
          return userV1;
        },
        lang: "js"
      }
    }
  }}
])
// { _id: 1, user: { books: ["a", "b"], movies: ["g"], name: "x" } }
// { _id: 2, user: { books: ["c"], movies: ["h", "i"], name: "y" } }
  • $unionWithcombina registros de la colección dada dentro de documentos que ya están en la tubería de agregación. Después de las 2 etapas de unión, tenemos todos los registros de usuarios, libros y películas dentro de la tubería.

  • Luego $groupregistramos $usery acumulamos elementos utilizando el $accumulatoroperador que permite acumulaciones personalizadas de documentos a medida que se agrupan:

    • Los campos que estamos interesados ​​en acumular se definen con accumulateArgs.
    • init define el estado que se acumulará a medida que agrupamos elementos.
    • la accumulatefunción permite realizar una acción personalizada con un registro que se agrupa para generar el estado acumulado. Por ejemplo, si el elemento que se está agrupando tiene el bookcampo definido, entonces actualizamos la booksparte del estado.
    • mergese usa para fusionar dos estados internos. Solo se usa para agregaciones que se ejecutan en grupos fragmentados o cuando la operación excede los límites de memoria.

¿Es posible recuperar resultados similares para: versión 4.2.6
Nixit Patel

0

Sí, puedes: Toma esta función de utilidad que he escrito hoy:

function shangMergeCol() {
  tcol= db.getCollection(arguments[0]);
  for (var i=1; i<arguments.length; i++){
    scol= db.getCollection(arguments[i]);
    scol.find().forEach(
        function (d) {
            tcol.insert(d);
        }
    )
  }
}

Puede pasar a esta función cualquier cantidad de colecciones, la primera será la meta. Todas las colecciones restantes son fuentes para ser transferidas a la meta.


-1

Fragmento de código. Cortesía: varias publicaciones en el desbordamiento de pila, incluido este.

 db.cust.drop();
 db.zip.drop();
 db.cust.insert({cust_id:1, zip_id: 101});
 db.cust.insert({cust_id:2, zip_id: 101});
 db.cust.insert({cust_id:3, zip_id: 101});
 db.cust.insert({cust_id:4, zip_id: 102});
 db.cust.insert({cust_id:5, zip_id: 102});

 db.zip.insert({zip_id:101, zip_cd:'AAA'});
 db.zip.insert({zip_id:102, zip_cd:'BBB'});
 db.zip.insert({zip_id:103, zip_cd:'CCC'});

mapCust = function() {
    var values = {
        cust_id: this.cust_id
    };
    emit(this.zip_id, values);
};

mapZip = function() {
    var values = {
    zip_cd: this.zip_cd
    };
    emit(this.zip_id, values);
};

reduceCustZip =  function(k, values) {
    var result = {};
    values.forEach(function(value) {
    var field;
        if ("cust_id" in value) {
            if (!("cust_ids" in result)) {
                result.cust_ids = [];
            }
            result.cust_ids.push(value);
        } else {
    for (field in value) {
        if (value.hasOwnProperty(field) ) {
                result[field] = value[field];
        }
         };  
       }
      });
       return result;
};


db.cust_zip.drop();
db.cust.mapReduce(mapCust, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.zip.mapReduce(mapZip, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.cust_zip.find();


mapCZ = function() {
    var that = this;
    if ("cust_ids" in this.value) {
        this.value.cust_ids.forEach(function(value) {
            emit(value.cust_id, {
                zip_id: that._id,
                zip_cd: that.value.zip_cd
            });
        });
    }
};

reduceCZ = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.cust_zip_joined.drop();
db.cust_zip.mapReduce(mapCZ, reduceCZ, {"out": "cust_zip_joined"}); 
db.cust_zip_joined.find().pretty();


var flattenMRCollection=function(dbName,collectionName) {
    var collection=db.getSiblingDB(dbName)[collectionName];

    var i=0;
    var bulk=collection.initializeUnorderedBulkOp();
    collection.find({ value: { $exists: true } }).addOption(16).forEach(function(result) {
        print((++i));
        //collection.update({_id: result._id},result.value);

        bulk.find({_id: result._id}).replaceOne(result.value);

        if(i%1000==0)
        {
            print("Executing bulk...");
            bulk.execute();
            bulk=collection.initializeUnorderedBulkOp();
        }
    });
    bulk.execute();
};


flattenMRCollection("mydb","cust_zip_joined");
db.cust_zip_joined.find().pretty();

-2

Tienes que hacer eso en tu capa de aplicación. Si está utilizando un ORM, podría usar anotaciones (o algo similar) para extraer referencias que existen en otras colecciones. Solo he trabajado con Morphia , y la @Referenceanotación recupera la entidad referenciada cuando se la consulta, por lo que puedo evitar hacerlo yo mismo en el código.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.