MongoDB: Combinez les données de plusieurs collections en une seule ... comment?

229

Comment puis-je (dans MongoDB) combiner les données de plusieurs collections en une seule collection?

Puis-je utiliser map-Reduce et si oui, comment?

J'apprécierais grandement un exemple car je suis novice.

user697697
la source
18
Voulez-vous simplement copier des documents de différentes collections dans une seule collection ou quel est votre plan? Pouvez-vous spécifier "combiner"? Si vous voulez simplement copier via mongo shell, un db.collection1.find().forEach(function(doc){db.collection2.save(doc)});suffit. Veuillez spécifier votre pilote utilisé (java, php, ...) si vous n'utilisez pas le shell mongo.
proximus le
J'ai donc une collection (par exemple, les utilisateurs) que d'autres collections, dit la collection de carnets d'adresses, la liste des collections de livres, etc. Comment puis-je utiliser la clé say user_id pour combiner ces collections en une seule collection. ?
user697697
Connexe: stackoverflow.com/q/2350495/435605
AlikElzin-kilaka

Réponses:

147

Bien que vous ne puissiez pas le faire en temps réel, vous pouvez exécuter plusieurs fois map-réduire pour fusionner les données en utilisant l'option "réduire" dans MongoDB 1.8+ map / Reduce (voir http://www.mongodb.org/ display / DOCS / MapReduce # MapReduce-Outputoptions ). Vous devez avoir une clé dans les deux collections que vous pouvez utiliser comme _id.

Par exemple, supposons que vous ayez une userscollection et une commentscollection et que vous souhaitiez avoir une nouvelle collection contenant des informations démographiques sur l'utilisateur pour chaque commentaire.

Supposons que la userscollection comporte les champs suivants:

  • _id
  • Prénom
  • nom de famille
  • pays
  • le sexe
  • âge

Et puis la commentscollection a les champs suivants:

  • _id
  • identifiant d'utilisateur
  • commentaire
  • établi

Vous feriez cette carte / réduisez:

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

À ce stade, vous aurez une nouvelle collection appelée users_commentsqui contient les données fusionnées et vous pouvez maintenant l'utiliser. Ces collections réduites ont toutes _idla clé que vous émettiez dans vos fonctions de carte, puis toutes les valeurs sont un sous-objet à l'intérieur duvalue clé - les valeurs ne sont pas au niveau supérieur de ces documents réduits.

Ceci est un exemple quelque peu simple. Vous pouvez répéter cela avec plus de collections autant que vous le souhaitez pour continuer à constituer la collection réduite. Vous pouvez également faire des résumés et des agrégations de données au cours du processus. Vous définiriez probablement plus d'une fonction de réduction, car la logique d'agrégation et de conservation des champs existants devient plus complexe.

Vous remarquerez également qu'il existe désormais un document pour chaque utilisateur avec tous les commentaires de cet utilisateur dans un tableau. Si nous fusionnions des données qui ont une relation un-à-un plutôt qu'un-à-plusieurs, ce serait plat et vous pourriez simplement utiliser une fonction de réduction comme celle-ci:

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 vous souhaitez aplatir la users_commentscollection afin qu'elle soit un document par commentaire, exécutez également ceci:

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"});

Cette technique ne doit certainement pas être effectuée à la volée. Il convient à un travail cron ou quelque chose comme ça qui met à jour périodiquement les données fusionnées. Vous voudrez probablement exécuter ensureIndexla nouvelle collection pour vous assurer que les requêtes que vous effectuez contre elle s'exécutent rapidement (gardez à l'esprit que vos données sont toujours dans une valueclé, donc si vous deviez indexer comments_with_demographicsl' createdheure du commentaire , ce seraitdb.comments_with_demographics.ensureIndex({"value.created": 1});

rmarscher
la source
1
Je ne ferais probablement jamais cela dans un logiciel de production, mais c'est toujours une technique cool et méchante.
Dave Griffith
3
Merci, Dave. J'ai utilisé cette technique pour générer des tableaux d'export et de reporting pour un site à fort trafic en production depuis 3 mois sans problème. Voici un autre article qui décrit une utilisation similaire de la technique: tebros.com/2011/07/…
rmarscher
1
Merci @rmarscher vos détails supplémentaires m'ont vraiment aidé à mieux comprendre tout.
benstr
5
Je devrais mettre à jour cette réponse avec un exemple utilisant le pipeline d'agrégation et la nouvelle opération $ lookup. Mentionnez-le ici jusqu'à ce que je puisse rédiger un article approprié. docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher
1
FYI pour ceux qui veulent rapidement comprendre ce que cela fait, voici ce qui est dans la users_commentscollection après le premier bloc de code gist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Nolan Amy
127

MongoDB 3.2 permet désormais de combiner les données de plusieurs collections en une seule à travers l' étape d'agrégation $ lookup . À titre d'exemple pratique, disons que vous disposez de données sur les livres divisées en deux collections différentes.

Première collecte, appelée books, contenant les données suivantes:

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

Et la deuxième collection, appelée books_selling_data, contenant les données suivantes:

{
    "_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
}

Pour fusionner les deux collections, il suffit d'utiliser la recherche $ de la manière suivante:

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

Après cette agrégation, la bookscollection ressemblera à ceci:

{
    "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
        }
    ]
}

Il est important de noter quelques éléments:

  1. Dans ce cas books_selling_data, la collection "from" ne peut pas être fragmentée.
  2. Le champ "as" sera un tableau, comme dans l'exemple ci-dessus.
  3. Les options "localField" et "foreignField" à l' étape de recherche $ seront traitées comme nulles à des fins de correspondance si elles n'existent pas dans leurs collections respectives (les documents de recherche $ en ont un parfait exemple).

Donc, en conclusion, si vous voulez consolider les deux collections, ayant, dans ce cas, un champ plat copies_sold avec le total des copies vendues, vous devrez travailler un peu plus, probablement en utilisant une collection intermédiaire qui, alors, être $ à la collection finale.

Bruno Krebs
la source
salut là-bas, veuillez nous dire quelle sera la façon optimisée de gérer des données comme celle-ci: l'utilisateur, file.files et file.chunks sont trois collections, je veux un utilisateur spécifique avec tout son fichier associé dans une réponse est-il possible.? {"name": "batMan", "email": "[email protected]", "files": [{file1}, {file2}, {file3}, .... etc.]}
mfaisalhyder
Des exemples de documentation officielle pour la solution ci-dessus peuvent être trouvés ici: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Jakub Czaplicki
4
Eh bien, ma réponse avait déjà trois liens vers la documentation officielle. Mais merci pour votre contribution quand même. @JakubCzaplicki
Bruno Krebs
2
Je pourrais avoir un dysfonctionnement cérébral total (le plus probable), mais dans $lookuptous les deux "localField" et "foreignField" ne devrait-il pas être égal à "isbn"? pas "_id" et "isbn"?
Dev01
13

S'il n'y a pas d'insertion en masse dans mongodb, nous bouclons tous les objets dans le small_collectionet les insérons un par un dans le big_collection:

db.small_collection.find().forEach(function(obj){ 
   db.big_collection.insert(obj)
});
Hieu Le
la source
db.colleciton.insert ([{}, {}, {}]) L'insertion accepte les tableaux.
augurone
2
cela fonctionne bien pour les petites collections, mais n'oubliez pas de migrer les index :)
Sebastien Lorber
12

Exemple très basique avec $ 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 }}
])

Ici est utilisé

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

Au lieu de

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

Parce que {$ unwind: "$ userRoleData"} cela retournera un résultat vide ou 0 si aucun enregistrement correspondant trouvé avec $ lookup.

Anish Agarwal
la source
11

Il est possible d'effectuer des unions dans MongoDB à la manière d'un 'SQL UNION' en utilisant des agrégations avec des recherches, dans une seule requête. Voici un exemple que j'ai testé qui fonctionne avec 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' } }
  ]);

Voici l'explication de son fonctionnement:

  1. Instancier une aggregatede toute collection de votre base de données qui a au moins un document en elle. Si vous ne pouvez garantir qu'une collection de votre base de données ne sera pas vide, vous pouvez contourner ce problème en créant dans votre base de données une sorte de collection `` factice '' contenant un seul document vide qui sera là spécifiquement pour effectuer des requêtes d'union.

  2. Faites de la première étape de votre pipeline { $limit: 1 }. Cela supprimera tous les documents de la collection, sauf le premier.

  3. Supprimez tous les champs du document restant à l'aide d'une $projectétape:

    { $project: { _id: '$$REMOVE' } }
  4. Votre agrégat contient désormais un seul document vide. Il est temps d'ajouter des recherches pour chaque collection que vous souhaitez réunir. Vous pouvez utiliser le pipelinechamp pour effectuer un filtrage spécifique, ou laisser localFieldet foreignFieldcomme null pour correspondre à l'ensemble de la collection.

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. Vous avez maintenant un agrégat contenant un seul document qui contient 3 tableaux comme celui-ci:

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

    Vous pouvez ensuite les fusionner ensemble dans un seul tableau à l'aide d'une $projectétape avec l' $concatArraysopérateur d'agrégation:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. Vous avez maintenant un agrégat contenant un seul document, dans lequel se trouve un tableau qui contient votre union de collections. Ce qui reste à faire est d'ajouter une $unwindet une $replaceRootétape pour diviser votre tableau en documents séparés:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. Voilà. Vous disposez maintenant d'un jeu de résultats contenant les collections que vous souhaitez réunir. Vous pouvez ensuite ajouter plus d'étapes pour le filtrer davantage, le trier, appliquer skip () et limit (). À peu près tout ce que vous voulez.

sboisse
la source
La requête échoue avec le message "$ projection nécessite au moins un champ de sortie".
abhishek_ganta
@abhishek Si vous obtenez cela, c'est parce que vous avez essayé de supprimer tous les champs du document unique en une seule étape de projection. MongoDB ne vous laissera pas faire cela. Pour contourner ce problème, vous devez effectuer 2 projections successives où la première supprime tout sauf le _id et la seconde supprime le _id restant.
sboisse
@abhishek J'ai simplifié encore plus la requête en remplaçant les étapes du projet $ par une seule qui utilise la variable '$$ REMOVE'. J'ai également ajouté un exemple concret que vous pouvez simplement copier et coller directement dans votre testeur de requêtes pour voir que cela fonctionne.
sboisse
@sboisse, cette solution fonctionne pour les petites collections, cependant, si je veux le faire sur de grandes collections (100 000+ documents), je rencontre une "taille totale des documents dans collectionToUnion1 dépasse la taille maximale du document". Dans les documents, il suggère de mettre un $ dérouler directement après la recherche $ pour éviter de créer de gros documents intermédiaires. Je n'ai pas réussi à modifier cette solution à l'aide de cette méthode. Avez-vous rencontré ce problème et avez-vous dû utiliser cette méthode? Lien vers les documents auxquels je me réfère : [link] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/… )
lucky7samson
@ lucky7samson, malheureusement, la quantité de données que j'ai eu à traiter n'était pas si grande. Je n'ai donc pas eu à affronter le problème dont vous parlez. Dans mon cas, j'ai pu appliquer un filtrage sur la collection à la recherche avant de fusionner les enregistrements avec le reste, de sorte que la quantité de données à réunir était assez petite.
sboisse
9

utiliser plusieurs recherches $ pour plusieurs collections en agrégation

requete:

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

résultat:

{
    "_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
    }
}
KARTHIKEYAN.A
la source
1

Mongorestore a cette fonctionnalité d'ajouter au-dessus de tout ce qui est déjà dans la base de données, donc ce comportement pourrait être utilisé pour combiner deux collections:

  1. collection mongodump1
  2. collection2.rename (collection1)
  3. mongorestore

Je ne l'ai pas encore essayé, mais il pourrait fonctionner plus rapidement que l'approche carte / réduire.

shauli
la source
1

En commençant Mongo 4.4, nous pouvons réaliser cette jointure au sein d'un pipeline d'agrégation en couplant la nouvelle $unionWithétape d'agrégation avec $grouple nouvel $accumulatoropérateur de:

// > 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" } }
  • $unionWithcombine les enregistrements de la collection donnée dans des documents déjà dans le pipeline d'agrégation. Après les 2 étapes de l'union, nous avons donc tous les utilisateurs, livres et enregistrements de films dans le pipeline.

  • Nous $groupenregistrons ensuite par $useret accumulons des éléments à l'aide de l' $accumulatoropérateur permettant des accumulations personnalisées de documents au fur et à mesure qu'ils sont regroupés:

    • les champs que nous souhaitons accumuler sont définis avec accumulateArgs.
    • init définit l'état qui sera accumulé lorsque nous regrouperons les éléments.
    • la accumulatefonction permet d'effectuer une action personnalisée avec un enregistrement en cours de regroupement afin de construire l'état accumulé. Par exemple, si l'élément en cours de regroupement a le bookchamp défini, nous mettons à jour la bookspartie de l'état.
    • mergeest utilisé pour fusionner deux états internes. Il n'est utilisé que pour les agrégations s'exécutant sur des clusters fragmentés ou lorsque l'opération dépasse les limites de la mémoire.
Xavier Guihot
la source
est-il possible de récupérer une sortie similaire pour: la version 4.2.6
Nixit Patel
0

Oui, vous pouvez: Prenez cette fonction utilitaire que j'ai écrite aujourd'hui:

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

Vous pouvez passer à cette fonction n'importe quel nombre de collections, la première va être la cible. Toutes les autres collections sont des sources à transférer vers la cible.

Shangab
la source
-1

Extrait de code. Courtoisie - Plusieurs messages sur le débordement de la pile, y compris celui-ci.

 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();
Vipul Mehta
la source
-2

Vous devez le faire dans votre couche d'application. Si vous utilisez un ORM, il pourrait utiliser des annotations (ou quelque chose de similaire) pour extraire des références qui existent dans d'autres collections. Je n'ai travaillé qu'avec Morphia , et l' @Referenceannotation récupère l'entité référencée lorsqu'elle est interrogée, donc je peux éviter de le faire moi-même dans le code.

homard1234
la source