MongoDB: combina i dati di più raccolte in un unico ... come?


229

Come posso (in MongoDB) combinare i dati di più raccolte in una raccolta?

Posso usare map-ridurre e, in tal caso, come?

Gradirei molto qualche esempio dato che sono alle prime armi.


18
Vuoi solo copiare documenti di raccolte diverse in un'unica raccolta o qual è il tuo piano? Puoi specificare "combina"? Se vuoi solo copiare tramite la shell mongo a db.collection1.find().forEach(function(doc){db.collection2.save(doc)});è sufficiente. Specificare il driver utilizzato (java, php, ...) se non si utilizza la shell mongo.
prossimo

quindi ho una raccolta (ad esempio utenti) rispetto ad altre raccolte che indicano raccolta di rubriche, elenchi di raccolte di libri, ecc. Come posso basarmi sulla chiave dire user_id combinare queste raccolte in una sola raccolta. ?
user697697,

Risposte:


147

Sebbene non sia possibile farlo in tempo reale, è possibile eseguire più volte la riduzione della mappa per unire i dati utilizzando l'opzione "riduci" in MongoDB 1.8+ mappa / riduzione (vedere http://www.mongodb.org/ display / DOCS / MapReduce # MapReduce-Outputoptions ). È necessario disporre di alcune chiavi in ​​entrambe le raccolte che è possibile utilizzare come _id.

Ad esempio, supponiamo che tu abbia una userscollezione e uncomments raccolta e desideri disporre di una nuova raccolta con alcune informazioni demografiche dell'utente per ciascun commento.

Supponiamo che la usersraccolta abbia i seguenti campi:

  • _ID
  • nome di battesimo
  • cognome
  • nazione
  • Genere
  • età

E poi la commentsraccolta ha i seguenti campi:

  • _ID
  • ID utente
  • commento
  • creato

Dovresti fare questa mappa / ridurre:

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

A questo punto, avrai una nuova raccolta chiamata users_commentsche contiene i dati uniti e ora puoi usarli. Queste raccolte ridotte hanno tutte _idla chiave che stavi emettendo nelle funzioni della tua mappa e quindi tutti i valori sono un oggetto secondario all'interno della valuechiave - i valori non sono al livello più alto di questi documenti ridotti.

Questo è un esempio piuttosto semplice. Puoi ripeterlo con più raccolte tanto quanto vuoi continuare a costruire la raccolta ridotta. È inoltre possibile eseguire riepiloghi e aggregazioni di dati nel processo. Probabilmente definiresti più di una funzione di riduzione man mano che la logica di aggregazione e conservazione dei campi esistenti diventa più complessa.

Noterai anche che ora c'è un documento per ogni utente con tutti i commenti di quell'utente in un array. Se unessimo i dati che hanno una relazione uno a uno anziché uno a molti, sarebbero piatti e potresti semplicemente usare una funzione di riduzione come questa:

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

Se vuoi appiattire la users_commentsraccolta in modo che sia un documento per commento, esegui anche questo:

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

Questa tecnica non dovrebbe assolutamente essere eseguita al volo. È adatto per un lavoro cron o qualcosa del genere che aggiorna periodicamente i dati uniti. Probabilmente vorrai eseguire ensureIndexla nuova raccolta per assicurarti che le query che esegui contro di essa vengano eseguite rapidamente (tieni presente che i tuoi dati sono ancora all'interno di una valuechiave, quindi se dovessi indicizzare comments_with_demographicsil momento del commento created, sarebbedb.comments_with_demographics.ensureIndex({"value.created": 1});


1
Probabilmente non lo farei mai nel software di produzione, ma è ancora una tecnica malvagia.
Dave Griffith,

3
Grazie, Dave. Ho usato questa tecnica per generare senza problemi tabelle di esportazione e reportistica per un sito ad alto traffico in produzione negli ultimi 3 mesi. Ecco un altro articolo che descrive un uso simile della tecnica: tebros.com/2011/07/…
rmarscher,

1
Grazie @rmarscher i tuoi dettagli extra mi hanno davvero aiutato a capire meglio tutto.
benstr,

5
Dovrei aggiornare questa risposta con un esempio usando la pipeline di aggregazione e la nuova operazione $ lookup. Menzionandolo qui fino a quando non riesco a mettere insieme un corretto commento. docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher

1
Cordiali saluti per coloro che vogliono rapidamente capire cosa fa, ecco cosa c'è nella users_commentscollezione dopo il primo blocco di codice gist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Nolan Amy

127

MongoDB 3.2 ora consente di combinare i dati di più raccolte in una attraverso la fase di aggregazione $ lookup . A titolo di esempio pratico, supponiamo che i dati sui libri siano divisi in due diverse raccolte.

Prima raccolta, chiamata books, con i seguenti dati:

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

E la seconda raccolta, chiamata books_selling_data, con i seguenti dati:

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

Unire entrambe le raccolte è solo una questione di usare $ lookup nel modo seguente:

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

Dopo questa aggregazione, la booksraccolta sarà simile alla seguente:

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

È importante notare alcune cose:

  1. La raccolta "da", in questo caso books_selling_data, non può essere frammentata.
  2. Il campo "as" sarà un array, come nell'esempio sopra.
  3. Entrambe le opzioni "localField" e "foreignField" nella fase $ lookup verranno trattate come null ai fini della corrispondenza se non esistono nelle rispettive raccolte (il documenti $ lookup ne hanno un esempio perfetto).

Quindi, in conclusione, se vuoi consolidare entrambe le raccolte, avendo, in questo caso, un campo copie_venduto piatto con le copie totali vendute, dovrai lavorare un po 'di più, probabilmente usando una raccolta intermedia che, quindi, essere $ fuori alla raccolta finale.


ciao, gentilmente puoi dire quale sarà il modo ottimizzato per gestire i dati in questo modo: utente, file.files e file.chunks sono tre raccolte, voglio un utente specifico con tutti i suoi file correlati in una risposta è possibile? {"name": "batMan", "email": "bt@gmail.com", "files": [{file1}, {file2}, {file3}, .... so on]}
mfaisalhyder

Esempi di documentazione ufficiale per la soluzione di cui sopra sono disponibili qui: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Jakub Czaplicki,

4
Bene, in realtà la mia risposta aveva già tre collegamenti alla documentazione ufficiale. Ma grazie per il tuo contributo comunque. @JakubCzaplicki
Bruno Krebs,

2
Potrei avere una disfunzione cerebrale totale (molto probabilmente), ma $lookupnon dovrebbe essere tutto uguale "isbn" sia "localField" che "foreignField"? non "_id" e "isbn"?
Dev01

13

Se non è presente un inserimento bulk in mongodb, eseguiamo il loop di tutti gli oggetti in small_collectione li inseriamo uno per uno nel big_collection:

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

db.colleciton.insert ([{}, {}, {}]) Insert accetta array.
Augurone,

2
funziona bene per le piccole raccolte, ma non dimenticare di migrare gli indici :)
Sebastien Lorber,

12

Esempio molto semplice 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 }}
])

Qui è usato

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

Invece di

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

Perché {$ unwind: "$ userRoleData"} questo restituirà risultati vuoti o 0 se nessun record corrispondente trovato con $ lookup.


11

Fare unioni in MongoDB in modo "SQL UNION" è possibile usando aggregazioni e ricerche, in una singola query. Ecco un esempio che ho testato che funziona 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' } }
  ]);

Ecco la spiegazione di come funziona:

  1. Crea un'istanza aggregatedi qualsiasi raccolta del database che contiene almeno un documento. Se non puoi garantire che nessuna raccolta del tuo database non sarà vuota, puoi risolvere questo problema creando nel tuo database una sorta di raccolta "fittizia" contenente un singolo documento vuoto che sarà lì appositamente per fare query sindacali.

  2. Prepara la prima fase della tua pipeline { $limit: 1 }. Questo eliminerà tutti i documenti della raccolta tranne il primo.

  3. Rimuovere tutti i campi del documento rimanente utilizzando uno $projectstage:

    { $project: { _id: '$$REMOVE' } }
  4. Il tuo aggregato ora contiene un singolo documento vuoto. È tempo di aggiungere ricerche per ogni raccolta che desideri unire insieme. È possibile utilizzare il pipelinecampo per eseguire alcuni filtri specifici oppure lasciare localFielde foreignFieldcome null per abbinare l'intera raccolta.

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. Ora hai un aggregato contenente un singolo documento che contiene 3 array come questo:

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

    È quindi possibile unirli insieme in un singolo array utilizzando uno $projectstage insieme $concatArraysall'operatore di aggregazione:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. Ora hai un aggregato contenente un singolo documento, in cui si trova un array che contiene l'unione di raccolte. Ciò che resta da fare è aggiungere una $unwinde una $replaceRootfase per dividere l'array in documenti separati:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. Ecco. Ora hai un set di risultati contenente le raccolte che desideri unire insieme. È quindi possibile aggiungere più fasi per filtrarlo ulteriormente, ordinarlo, applicare skip () e limit (). Praticamente tutto quello che vuoi.


La query non riesce con il messaggio "$ proiezione richiede almeno un campo di output".
abhishek_ganta

@abhishek Se lo capisci è perché hai provato a rimuovere tutti i campi dal singolo documento in una singola fase di proiezione. MongoDB non ti permetterà di farlo. Per ovviare a questo problema, è necessario eseguire 2 proiezioni successive in cui la prima rimuove tutto tranne il _id e la seconda spoglia il rimanente _id.
sboisse

@abhishek Ho semplificato ancora di più la query sostituendo le fasi $ project in una singola che utilizza la variabile '$$ REMOVE'. Ho anche aggiunto un esempio concreto che puoi semplicemente copiare e incollare direttamente nel tuo tester di query per vedere che funziona.
sboisse

@sboisse, questa soluzione funziona per raccolte più piccole, tuttavia, se voglio farlo su grandi raccolte (oltre 100.000 documenti), mi imbatto in una "Dimensione totale dei documenti in collectionToUnion1 supera la dimensione massima del documento". Nei documenti, suggerisce di mettere $ svolgersi direttamente dopo la ricerca $ per evitare di creare documenti intermedi di grandi dimensioni. Non sono riuscito a modificare questa soluzione usando quel metodo. Hai riscontrato questo problema e hai dovuto usare quel metodo? Link ai documenti a cui mi riferisco : [link] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/… )
lucky7samson

@ lucky7samson purtroppo la quantità di dati che ho dovuto trattare non era così grande. Quindi non ho dovuto affrontare il problema a cui ti riferisci. Nel mio caso ho potuto applicare il filtro sulla raccolta alla ricerca prima di unire i record con il resto, quindi la quantità di dati da unire era piuttosto piccola.
sboisse,

9

usa più $ lookup per più raccolte in aggregazione

query:

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

risultato:

{
    "_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 ha questa funzione di aggiunta in aggiunta a qualsiasi cosa sia già presente nel database, quindi questo comportamento potrebbe essere usato per combinare due raccolte:

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

Non l'ho ancora provato, ma potrebbe funzionare più velocemente dell'approccio mappa / riduzione.


1

Iniziando Mongo 4.4, possiamo raggiungere questo join all'interno di una pipeline di aggregazione accoppiando la nuova $unionWithfase di aggregazione con $groupil nuovo $accumulatoroperatore:

// > 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 i record della raccolta data all'interno di documenti già presenti nella pipeline di aggregazione. Dopo le 2 fasi del sindacato, abbiamo quindi tutti gli utenti, i libri e i record di film in cantiere.

  • Quindi $groupregistriamo $usere accumuliamo articoli utilizzando l' $accumulatoroperatore che consente l'accumulo personalizzato di documenti man mano che vengono raggruppati:

    • i campi a cui siamo interessati ad accumulare sono definiti accumulateArgs.
    • init definisce lo stato che verrà accumulato man mano che raggruppiamo gli elementi.
    • la accumulatefunzione consente di eseguire un'azione personalizzata con un record raggruppato per creare lo stato accumulato. Ad esempio, se l'elemento da raggruppare ha il bookcampo definito, allora aggiorniamo la booksparte dello stato.
    • mergeviene utilizzato per unire due stati interni. Viene utilizzato solo per aggregazioni in esecuzione su cluster frammentati o quando l'operazione supera i limiti di memoria.

è possibile recuperare un output simile per: 4.2.6 versione
Nixit Patel

0

Sì, puoi: prendi questa funzione di utilità che ho scritto oggi:

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

Puoi passare a questa funzione un numero qualsiasi di raccolte, la prima sarà quella di destinazione. Tutte le altre raccolte sono fonti da trasferire a quella di destinazione.


-1

Snippet di codice. Per gentile concessione: più post in overflow dello stack, incluso questo.

 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

Devi farlo nel tuo livello di applicazione. Se stai usando un ORM, potrebbe usare le annotazioni (o qualcosa di simile) per estrarre i riferimenti esistenti in altre raccolte. Ho lavorato solo con Morphia e l' @Referenceannotazione recupera l'entità referenziata quando viene interrogata, quindi sono in grado di evitare di farlo da sola nel codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.