MongoDB: Kombinieren Sie Daten aus mehreren Sammlungen zu einer. Wie?


229

Wie kann ich (in MongoDB) Daten aus mehreren Sammlungen in einer Sammlung kombinieren?

Kann ich Map-Reduce verwenden und wenn ja, wie?

Ich würde mich sehr über ein Beispiel freuen, da ich ein Neuling bin.


18
Möchten Sie nur Dokumente aus verschiedenen Sammlungen in eine einzige Sammlung kopieren oder was ist Ihr Plan? Können Sie "kombinieren" angeben? Wenn Sie nur über die Mongo-Shell kopieren möchten, db.collection1.find().forEach(function(doc){db.collection2.save(doc)});reicht a aus. Bitte geben Sie Ihren verwendeten Treiber (Java, PHP, ...) an, wenn Sie keine Mongo-Shell verwenden.
Proximus

Ich habe also eine Sammlung (sagen wir Benutzer) als andere Sammlungen, sagt Adressbuchsammlung, Liste der Büchersammlungen usw. Wie kann ich diese Sammlungen basierend auf dem Schlüssel say user_id zu nur einer einzigen Sammlung kombinieren? ?
user697697

Antworten:


147

Obwohl Sie dies nicht in Echtzeit tun können, können Sie Map-Reduce mehrmals ausführen, um Daten zusammenzuführen, indem Sie die Option "Reduzieren" in MongoDB 1.8+ Map / Reduce verwenden (siehe http://www.mongodb.org/). display / DOCS / MapReduce # MapReduce-Outputoptions ). Sie benötigen einen Schlüssel in beiden Sammlungen, den Sie als _id verwenden können.

Angenommen, Sie haben eine usersSammlung und eine commentsSammlung und möchten eine neue Sammlung mit einigen demografischen Benutzerinformationen für jeden Kommentar.

Angenommen, die usersSammlung enthält die folgenden Felder:

  • _Ich würde
  • Vorname
  • Nachname
  • Land
  • Geschlecht
  • Alter

Und dann hat die commentsSammlung die folgenden Felder:

  • _Ich würde
  • Benutzeridentifikation
  • Kommentar
  • erstellt

Sie würden diese Karte machen / reduzieren:

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

Zu diesem Zeitpunkt haben Sie eine neue Sammlung mit dem Namen users_comments, die die zusammengeführten Daten enthält, und Sie können diese jetzt verwenden. Diese reduzierten Sammlungen haben alle _idden Schlüssel, den Sie in Ihren Kartenfunktionen ausgegeben haben, und dann sind alle Werte ein Unterobjekt innerhalb des valueSchlüssels - die Werte befinden sich nicht auf der obersten Ebene dieser reduzierten Dokumente.

Dies ist ein etwas einfaches Beispiel. Sie können dies mit mehr Sammlungen wiederholen, so oft Sie die reduzierte Sammlung weiter aufbauen möchten. Sie können dabei auch Zusammenfassungen und Aggregationen von Daten erstellen. Wahrscheinlich würden Sie mehr als eine Reduzierungsfunktion definieren, da die Logik zum Aggregieren und Beibehalten vorhandener Felder komplexer wird.

Sie werden auch feststellen, dass es jetzt für jeden Benutzer ein Dokument mit allen Kommentaren dieses Benutzers in einem Array gibt. Wenn wir Daten zusammenführen würden, die eher eine Eins-zu-Eins-Beziehung als eine Eins-zu-Viele-Beziehung haben, wäre dies flach und Sie könnten einfach eine Reduzierungsfunktion wie die folgende verwenden:

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

Wenn Sie die users_commentsSammlung reduzieren möchten, sodass es sich um ein Dokument pro Kommentar handelt, führen Sie zusätzlich Folgendes aus:

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

Diese Technik sollte definitiv nicht im laufenden Betrieb durchgeführt werden. Es eignet sich für einen Cron-Job oder ähnliches, bei dem die zusammengeführten Daten regelmäßig aktualisiert werden. Möglicherweise möchten Sie ensureIndexdie neue Sammlung ausführen, um sicherzustellen, dass Abfragen, die Sie für sie ausführen, schnell ausgeführt werden (denken Sie daran, dass sich Ihre Daten noch in einem valueSchlüssel befinden. Wenn Sie also comments_with_demographicsdie Kommentarzeit indizieren created, ist dies der Falldb.comments_with_demographics.ensureIndex({"value.created": 1});


1
Ich würde das wahrscheinlich nie in Produktionssoftware machen, aber es ist immer noch eine böse coole Technik.
Dave Griffith

3
Danke, Dave. Ich habe diese Technik verwendet, um Export- und Berichtstabellen für eine Site mit hohem Datenverkehr in der Produktion in den letzten 3 Monaten ohne Probleme zu generieren. Hier ist ein weiterer Artikel, der eine ähnliche Verwendung der Technik beschreibt: tebros.com/2011/07/…
rmarscher

1
Danke @rmarscher, deine zusätzlichen Details haben mir wirklich geholfen, alles besser zu verstehen.
Benstr

5
Ich sollte diese Antwort mit einem Beispiel aktualisieren, das die Aggregationspipeline und die neue $ lookup-Operation verwendet. Erwähne es hier, bis ich eine richtige Beschreibung zusammenstellen kann. docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher

1
Zu Ihrer Information für diejenigen, die schnell wissen möchten, was dies bewirkt, hier ist, was in der users_commentsSammlung nach dem ersten Codeblock gist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Nolan Amy

127

Mit MongoDB 3.2 können jetzt Daten aus mehreren Sammlungen über die Aggregationsphase $ lookup zu einer zusammengefasst werden . Nehmen wir als praktisches Beispiel an, Sie haben Daten zu Büchern, die in zwei verschiedene Sammlungen aufgeteilt sind.

Erste Sammlung, genannt books, mit folgenden Daten:

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

Und die zweite Sammlung books_selling_datamit den folgenden Daten:

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

Um beide Sammlungen zusammenzuführen, müssen Sie $ lookup nur folgendermaßen verwenden:

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

Nach dieser Aggregation bookssieht die Sammlung folgendermaßen aus:

{
    "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 ist wichtig, einige Dinge zu beachten:

  1. Die "from" -Sammlung kann in diesem Fall books_selling_datanicht gesplittert werden.
  2. Das Feld "as" ist ein Array, wie im obigen Beispiel.
  3. Sowohl die Optionen "localField" als auch "ForeignField" in der $ lookup-Phase werden für Übereinstimmungszwecke als null behandelt, wenn sie in ihren jeweiligen Sammlungen nicht vorhanden sind (die $ lookup-Dokumente enthalten ein perfektes Beispiel dafür).

Wenn Sie also beide Sammlungen konsolidieren möchten und in diesem Fall ein flaches Feld copy_sold mit den insgesamt verkauften Kopien haben, müssen Sie ein wenig mehr arbeiten, wahrscheinlich mit einer Zwischensammlung, die dann sein $ aus der endgültigen Sammlung.


Hallo zusammen, können Sie uns bitte mitteilen, wie Daten wie diese optimiert verwaltet werden sollen: Benutzer, file.files und file.chunks sind drei Sammlungen. Ich möchte, dass ein bestimmter Benutzer mit allen zugehörigen Dateien in einer Antwort angezeigt wird. {"name": "batMan", "email ':" bt@gmail.com "," files ": [{file1}, {file2}, {file3}, .... so weiter]}
mfaisalhyder

Offizielle Dokumentationsbeispiele für die obige Lösung finden Sie hier: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Jakub Czaplicki

4
Nun, eigentlich hatte meine Antwort bereits drei Links zur offiziellen Dokumentation. Aber trotzdem danke für deinen Beitrag. @ JakubCzaplicki
Bruno Krebs

2
Ich habe möglicherweise eine totale Fehlfunktion des Gehirns (höchstwahrscheinlich), aber $lookupsollten nicht alle "localField" und "ForeignField" gleich "isbn" sein? nicht "_id" und "isbn"?
Dev01

13

Wenn es keine Masseneinfügung in mongodb gibt, schleifen wir alle Objekte in die small_collectionund fügen sie einzeln in die ein big_collection:

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

db.colleciton.insert ([{}, {}, {}]) Insert akzeptiert Arrays.
Augurone

2
Dies funktioniert gut für kleine Sammlungen, aber vergessen Sie nicht, Indizes zu migrieren :)
Sebastien Lorber

12

Sehr einfaches Beispiel mit $ 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 }}
])

Hier wird verwendet

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

Anstatt

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

Aufgrund von {$ unwind: "$ userRoleData"} wird ein leeres oder 0-Ergebnis zurückgegeben, wenn mit $ lookup kein übereinstimmender Datensatz gefunden wurde.


11

Das Ausführen von Gewerkschaften in MongoDB in einer 'SQL UNION'-Weise ist möglich, indem Aggregationen zusammen mit Suchvorgängen in einer einzigen Abfrage verwendet werden. Hier ist ein Beispiel, das ich getestet habe und das mit MongoDB 4.0 funktioniert:

// 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' } }
  ]);

Hier ist die Erklärung, wie es funktioniert:

  1. Instanziieren Sie ein aggregateOut aus einer Sammlung Ihrer Datenbank, die mindestens ein Dokument enthält. Wenn Sie nicht garantieren können, dass eine Sammlung Ihrer Datenbank nicht leer ist, können Sie dieses Problem umgehen, indem Sie in Ihrer Datenbank eine Art "Dummy" -Sammlung erstellen, die ein einzelnes leeres Dokument enthält, das speziell für die Durchführung von Gewerkschaftsabfragen vorgesehen ist.

  2. Machen Sie die erste Stufe Ihrer Pipeline { $limit: 1 }. Dadurch werden alle Dokumente der Sammlung mit Ausnahme des ersten entfernt.

  3. Entfernen Sie alle Felder des verbleibenden Dokuments mithilfe einer $projectStufe:

    { $project: { _id: '$$REMOVE' } }
  4. Ihr Aggregat enthält jetzt ein einzelnes, leeres Dokument. Es ist Zeit, Lookups für jede Sammlung hinzuzufügen, die Sie zusammenführen möchten. Sie können die verwendenpipeline Feld verwenden, um eine bestimmte Filterung durchzuführen, oder lassen localFieldund foreignFieldals Null, um mit der gesamten Sammlung übereinzustimmen.

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. Sie haben jetzt ein Aggregat, das ein einzelnes Dokument enthält, das drei Arrays wie folgt enthält:

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

    Sie können sie dann mit a zu einem einzigen Array zusammenführen $project Stufe zusammen mit dem $concatArraysAggregationsoperator zusammenführen :

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. Sie haben jetzt ein Aggregat mit einem einzelnen Dokument, in dem sich ein Array befindet, das Ihre Vereinigung von Sammlungen enthält. Was noch zu tun bleibt, ist das Hinzufügen eines$unwind und einer $replaceRootStufe, um Ihr Array in separate Dokumente aufzuteilen:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. Voilà. Sie haben jetzt eine Ergebnismenge mit den Sammlungen, die Sie zusammenführen möchten. Sie können dann weitere Stufen hinzufügen, um es weiter zu filtern, zu sortieren, skip () und limit () anzuwenden. So ziemlich alles was du willst.


Die Abfrage schlägt mit der Meldung "$ projection erfordert mindestens ein Ausgabefeld" fehl.
abhishek_ganta

@abhishek Wenn Sie das verstehen, liegt es daran, dass Sie versucht haben, alle Felder in einer einzigen Projektionsphase aus dem einzelnen Dokument zu entfernen. MongoDB lässt Sie das nicht zu. Um dies zu umgehen, müssen Sie zwei aufeinanderfolgende Projektionen durchführen, bei denen die erste alles außer der _id entfernt und die zweite die verbleibende _id entfernt.
Sboisse

@abhishek Ich habe die Abfrage noch weiter vereinfacht, indem ich die $ -Projektphasen durch eine einzige ersetzt habe, die die Variable '$$ REMOVE' verwendet. Ich habe auch ein konkretes Beispiel hinzugefügt, das Sie einfach kopieren und direkt in Ihren Abfragetester einfügen können, um zu sehen, ob es funktioniert.
Sboisse

@sboisse, diese Lösung funktioniert für kleinere Sammlungen. Wenn ich dies jedoch für große Sammlungen (über 100.000 Dokumente) tun möchte, wird die Meldung angezeigt, dass die Gesamtgröße der Dokumente in collectionToUnion1 die maximale Dokumentgröße überschreitet. In den Dokumenten wird empfohlen, direkt nach der $ -Suche ein $ unwind einzufügen, um zu vermeiden, dass große Zwischendokumente erstellt werden. Es ist mir nicht gelungen, diese Lösung mit dieser Methode zu modifizieren. Sind Sie auf dieses Problem gestoßen und mussten diese Methode anwenden? Link zu den Dokumenten, auf die ich mich beziehe : [Link] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/… )
happy7samson

@ucky7samson Leider war die Datenmenge, mit der ich umgehen musste, nicht so groß. Ich musste mich also nicht mit dem Problem auseinandersetzen, auf das Sie sich beziehen. In meinem Fall konnte ich die Sammlung filtern, um nachzuschlagen, bevor die Datensätze mit dem Rest zusammengeführt wurden, sodass die Datenmenge für die Vereinigung recht gering war.
Sboisse

9

Verwenden Sie mehrere $ lookup für mehrere Sammlungen in Aggregation

Abfrage:

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

Ergebnis:

{
    "_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 verfügt über diese Funktion zum Anhängen an das, was bereits in der Datenbank vorhanden ist. Daher kann dieses Verhalten zum Kombinieren von zwei Sammlungen verwendet werden:

  1. mongodump collection1
  2. collection2.rename (collection1)
  3. Mongorestore

Ich habe es noch nicht ausprobiert, aber es funktioniert möglicherweise schneller als der Map / Reduce-Ansatz.


1

Ab Mongo 4.4sofort können wir diesen Join innerhalb einer Aggregationspipeline erreichen, indem wir die neue $unionWithAggregationsstufe mit $groupdem neuen $accumulatorOperator koppeln:

// > 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" } }
  • $unionWithkombiniert Datensätze aus der angegebenen Sammlung in Dokumenten, die sich bereits in der Aggregationspipeline befinden. Nach den beiden Gewerkschaftsphasen haben wir somit alle Benutzer-, Buch- und Filmaufzeichnungen in der Pipeline.

  • Wir $groupzeichnen dann $userElemente auf und akkumulieren sie mithilfe des $accumulatorOperators, sodass benutzerdefinierte Akkumulationen von Dokumenten möglich sind, sobald sie gruppiert werden:

    • Die Felder, an denen wir interessiert sind, werden mit definiert accumulateArgs .
    • init definiert den Zustand, der beim Gruppieren von Elementen akkumuliert wird.
    • Mit dieser accumulateFunktion können Sie eine benutzerdefinierte Aktion ausführen, wobei ein Datensatz gruppiert wird, um den akkumulierten Status zu erstellen. Wenn für das zu gruppierende Element beispielsweise das bookFeld definiert ist, aktualisieren wir den booksTeil des Status.
    • mergewird verwendet, um zwei interne Zustände zusammenzuführen. Es wird nur für Aggregationen verwendet, die auf Sharded-Clustern ausgeführt werden oder wenn der Vorgang die Speichergrenzen überschreitet.

ist es möglich, ähnliche Ausgaben abzurufen für: 4.2.6 Version
Nixit Patel

0

Ja, Sie können: Nehmen Sie diese Dienstprogrammfunktion, die ich heute geschrieben habe:

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

Sie können an diese Funktion eine beliebige Anzahl von Sammlungen übergeben, wobei die erste die Zielsammlung sein wird. Alle übrigen Sammlungen sind Quellen, die an die Zielsammlung übertragen werden sollen.


-1

Code-Auszug. Courtesy-Mehrere Beiträge zum Stapelüberlauf, einschließlich dieses.

 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

Sie müssen dies in Ihrer Anwendungsschicht tun. Wenn Sie ein ORM verwenden, können Anmerkungen (oder ähnliches) verwendet werden, um Referenzen abzurufen, die in anderen Sammlungen vorhanden sind. Ich habe nur mit Morphia gearbeitet , und die @ReferenceAnmerkung ruft die referenzierte Entität ab, wenn sie abgefragt wird, sodass ich es vermeiden kann, dies selbst im Code zu tun.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.