MongoDB: Combine dados de várias coleções em um .. como?


229

Como posso (no MongoDB) combinar dados de várias coleções em uma coleção?

Posso usar a redução de mapa e, em caso afirmativo, como?

Eu apreciaria muito alguns exemplos, pois sou um novato.


18
Deseja copiar documentos de diferentes coleções em uma única coleção ou qual é o seu plano? Você pode especificar "combinar"? Se você apenas deseja copiar via mongo shell, a db.collection1.find().forEach(function(doc){db.collection2.save(doc)});é suficiente. Por favor, especifique o driver usado (java, php, ...) se você não usar o mongo shell.
proximidadesus 15/04

então eu tenho uma coleção (digamos usuários) do que outras coleções, como coleção de catálogo de endereços, lista de coleções de livros etc. Como posso, com base na tecla say user_id, combinar essas coleções em apenas uma única coleção. ?
user697697

Respostas:


147

Embora você não possa fazer isso em tempo real, é possível executar a redução de mapa várias vezes para mesclar dados usando a opção "reduzir" no MongoDB 1.8+ map / reduz (consulte http://www.mongodb.org/ display / DOCS / MapReduce # MapReduce-Outputoptions ). Você precisa ter alguma chave nas duas coleções que possa ser usada como um _id.

Por exemplo, digamos que você tenha uma userscoleção e uma commentscoleção e deseje ter uma nova coleção com algumas informações demográficas do usuário para cada comentário.

Digamos que a userscoleção tenha os seguintes campos:

  • _Eu iria
  • primeiro nome
  • último nome
  • país
  • gênero
  • era

E então a commentscoleção possui os seguintes campos:

  • _Eu iria
  • ID do usuário
  • Comente
  • criada

Você faria este mapa / reduziria:

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

Nesse ponto, você terá uma nova coleção chamada users_commentsque contém os dados mesclados e agora poderá usá-lo. Todas essas coleções reduzidas possuem _idqual é a chave que você estava emitindo nas funções do mapa e, em seguida, todos os valores são um subobjeto dentro da valuechave - os valores não estão no nível superior desses documentos reduzidos.

Este é um exemplo um pouco simples. Você pode repetir isso com mais coleções o quanto quiser continuar construindo a coleção reduzida. Você também pode fazer resumos e agregações de dados no processo. É provável que você defina mais de uma função de redução, pois a lógica para agregar e preservar campos existentes fica mais complexa.

Você também observará que agora existe um documento para cada usuário com todos os comentários desse usuário em uma matriz. Se estivéssemos mesclando dados que têm um relacionamento um para um em vez de um para muitos, seria simples e você poderia simplesmente usar uma função de redução como esta:

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

Se você deseja nivelar a users_commentscoleção, de modo que seja um documento por comentário, execute adicionalmente este:

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

Esta técnica definitivamente não deve ser realizada em tempo real. É adequado para um trabalho cron ou algo assim que atualiza os dados mesclados periodicamente. Provavelmente, você desejará executar ensureIndexa nova coleção para garantir que as consultas executadas com rapidez sejam executadas rapidamente (lembre-se de que seus dados ainda estão dentro de uma valuechave; portanto, se você indexar comments_with_demographicso createdtempo dos comentários , seriadb.comments_with_demographics.ensureIndex({"value.created": 1});


1
Provavelmente eu nunca faria isso em software de produção, mas ainda é uma técnica bem legal.
Dave Griffith

3
Obrigado Dave. Eu usei essa técnica para gerar tabelas de exportação e relatórios para um site de alto tráfego em produção nos últimos 3 meses sem problemas. Aqui está outro artigo que descreve um uso semelhante da técnica: tebros.com/2011/07/…
rmarscher

1
Obrigado @rmarscher, seus detalhes extras realmente me ajudaram a entender melhor tudo.
benstr

5
Eu devo atualizar esta resposta com um exemplo usando o pipeline de agregação e a nova operação de pesquisa $. Mencionando aqui até que eu possa montar uma redação adequada. docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher

1
FYI para aqueles que querem Grokar rapidamente o que este faz, aqui é o que está na users_commentscoleção após o primeiro bloco de código gist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Nolan Amy

127

O MongoDB 3.2 agora permite combinar dados de várias coleções em uma até o estágio de agregação de pesquisa $ . Como exemplo prático, digamos que você tenha dados sobre livros divididos em duas coleções diferentes.

Primeira coleção, chamada books, com os seguintes dados:

{
    "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 a segunda coleção, chamada books_selling_data, com os seguintes dados:

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

Para mesclar as duas coleções, basta usar a pesquisa $ da seguinte maneira:

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

Após essa agregação, a bookscoleção terá a seguinte aparência:

{
    "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 observar algumas coisas:

  1. A "de" coleção, neste caso books_selling_data, não podem ser fragmentados.
  2. O campo "como" será uma matriz, como no exemplo acima.
  3. As opções "localField" e "ForeignField" no estágio $ lookup serão tratadas como nulas para fins de correspondência, se não existirem em suas respectivas coleções (os documentos $ lookup têm um exemplo perfeito disso).

Portanto, como conclusão, se você deseja consolidar as duas coleções, tendo, nesse caso, um campo simples de cópias_soldado com o total de cópias vendidas, será necessário trabalhar um pouco mais, provavelmente usando uma coleção intermediária que, então, seja $ out para a coleção final.


oi lá, por favor, você pode dizer qual será a maneira otimizada de gerenciar dados como este: User, file.files e file.chunks são três coleções, eu quero um usuário específico com todo o seu arquivo relacionado em uma resposta é possível.? {"name": "batMan", "email": "bt@gmail.com", "files": [{file1}, {file2}, {file3}, .... etc.]]
mfaisalhyder

Exemplos de documentação oficial para a solução acima podem ser encontrados aqui: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Jakub Czaplicki

4
Bem, na verdade minha resposta já tinha três links para a documentação oficial. Mas obrigado pela sua contribuição de qualquer maneira. @JakubCzaplicki
Bruno Krebs

2
Eu posso estar tendo um mau funcionamento cerebral total (provavelmente), mas $lookupnem todos "localField" e "ForeignField" devem ser iguais a "isbn"? não "_id" e "isbn"?
Dev01

13

Se não houver inserção em massa no mongodb, fazemos um loop de todos os objetos no small_collectione os inserimos um a um no big_collection:

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

db.colleciton.insert ([{}, {}, {}]) A inserção aceita matrizes.
augurone

2
esta multa funciona para coleções pequenas, mas não se esqueça de índices Migrar :)
Sebastien Lorber

12

Exemplo muito básico com $ 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 }}
])

Aqui é usado

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

Ao invés de

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

Como {$ unwind: "$ userRoleData"}, isso retornará um resultado vazio ou 0 se nenhum registro correspondente for encontrado com a pesquisa $.


11

É possível fazer uniões no MongoDB da maneira 'SQL UNION' usando agregações e pesquisas, em uma única consulta. Aqui está um exemplo que testei que funciona com o 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' } }
  ]);

Aqui está a explicação de como funciona:

  1. Instanciar um aggregatefora de qualquer coleção de seu banco de dados que tem pelo menos um documento na mesma. Se você não pode garantir que nenhuma coleção do seu banco de dados esteja vazia, você pode solucionar esse problema criando no seu banco de dados algum tipo de coleção 'fictícia' contendo um único documento vazio que estará lá especificamente para realizar consultas de união.

  2. Faça o primeiro estágio do seu pipeline { $limit: 1 }. Isso removerá todos os documentos da coleção, exceto o primeiro.

  3. Retire todos os campos do documento restante usando um $projectestágio:

    { $project: { _id: '$$REMOVE' } }
  4. Seu agregado agora contém um único documento vazio. É hora de adicionar pesquisas para cada coleção que você deseja unir. Você pode usar o pipelinecampo para fazer alguma filtragem específica ou deixar localFielde foreignFieldcomo nulo para corresponder à coleção inteira.

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. Agora você tem um agregado que contém um único documento que contém três matrizes como este:

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

    Você pode mesclá-los em uma única matriz usando um $projectestágio junto com o $concatArraysoperador de agregação:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. Agora você tem um agregado que contém um único documento, no qual está localizada uma matriz que contém sua união de coleções. O que falta fazer é adicionar um $unwinde um $replaceRootestágio para dividir sua matriz em documentos separados:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. Voilà. Agora você tem um conjunto de resultados contendo as coleções que deseja unir. Você pode adicionar mais estágios para filtrá-lo ainda mais, classificá-lo, aplicar skip () e limit (). Praticamente qualquer coisa que você quiser.


A consulta está falhando com a mensagem "$ projection requer pelo menos um campo de saída".
Abhishek_ganta

@abhishek Se você entendeu, é porque tentou remover todos os campos do documento único em um único estágio de projeção. O MongoDB não permitirá que você faça isso. Para contornar isso, você precisa fazer 2 projeções sucessivas, em que a primeira retira tudo, exceto o _id, e a segunda retira o _id restante.
Sboisse 9/05/19

@abhishek Simplifiquei ainda mais a consulta, substituindo as etapas do projeto $ por uma única que usa a variável '$$ REMOVE'. Também adicionei um exemplo concreto de que você pode copiar e colar diretamente no seu testador de consultas para ver se funciona.
Sboisse 9/05/19

@sboisse, esta solução funciona para coleções menores, no entanto, se eu quiser fazer isso em grandes coleções, (mais de 100.000 documentos), encontro um "Tamanho total de documentos em collectionToUnion1 excede o tamanho máximo de documento". Nos documentos, ele sugere colocar um $ desenrolar diretamente após a pesquisa $ para evitar a criação de grandes documentos intermediários. Não consegui modificar esta solução usando esse método. Você já encontrou esse problema e precisou usar esse método? Link para os docs que eu sou ref para: [link] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/... )
lucky7samson

@ lucky7samson, infelizmente, a quantidade de dados que eu tive que lidar não era tão grande. Portanto, não tive que enfrentar o problema a que você está se referindo. No meu caso, eu poderia aplicar a filtragem na coleção à pesquisa antes de mesclar os registros com o restante, para que a quantidade de dados a serem unidos fosse muito pequena.
Sboisse 16/08/19

9

use várias pesquisas de $ para várias coleções na agregação

inquerir:

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

resultado:

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

1

O Mongorestore possui esse recurso de anexar sobre o que já está no banco de dados, portanto esse comportamento pode ser usado para combinar duas coleções:

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

Ainda não tentei, mas pode ter um desempenho mais rápido que a abordagem de mapa / redução.


1

Começando Mongo 4.4, podemos conseguir essa junção em um pipeline de agregação, acoplando o novo $unionWithestágio de agregação ao $groupnovo $accumulatoroperador:

// > db.users.find()
//   [{ user: 1, name: "x" }, { user: 2, name: "y" }]
// > db.books.find()
//   [{ user: 1, book: "a" }, { user: 1, book: "b" }, { user: 2, book: "c" }]
// > db.movies.find()
//   [{ user: 1, movie: "g" }, { user: 2, movie: "h" }, { user: 2, movie: "i" }]
db.users.aggregate([
  { $unionWith: "books"  },
  { $unionWith: "movies" },
  { $group: {
    _id: "$user",
    user: {
      $accumulator: {
        accumulateArgs: ["$name", "$book", "$movie"],
        init: function() { return { books: [], movies: [] } },
        accumulate: function(user, name, book, movie) {
          if (name) user.name = name;
          if (book) user.books.push(book);
          if (movie) user.movies.push(movie);
          return user;
        },
        merge: function(userV1, userV2) {
          if (userV2.name) userV1.name = userV2.name;
          userV1.books.concat(userV2.books);
          userV1.movies.concat(userV2.movies);
          return userV1;
        },
        lang: "js"
      }
    }
  }}
])
// { _id: 1, user: { books: ["a", "b"], movies: ["g"], name: "x" } }
// { _id: 2, user: { books: ["c"], movies: ["h", "i"], name: "y" } }
  • $unionWithcombina registros da coleção fornecida em documentos já no pipeline de agregação. Após os 2 estágios da união, temos todos os registros de usuários, livros e filmes no pipeline.

  • Em seguida, $groupregistramos $usere acumulamos itens usando o $accumulatoroperador, permitindo acumulações personalizadas de documentos conforme eles são agrupados:

    • os campos com os quais estamos interessados ​​em acumular são definidos accumulateArgs.
    • init define o estado que será acumulado à medida que agrupamos elementos.
    • a accumulatefunção permite executar uma ação personalizada com um registro sendo agrupado para criar o estado acumulado. Por exemplo, se o item que está sendo agrupado tiver o bookcampo definido, atualizamos a booksparte do estado.
    • mergeé usado para mesclar dois estados internos. É usado apenas para agregações executadas em clusters fragmentados ou quando a operação excede os limites de memória.

é possível recuperar uma saída semelhante para: versão 4.2.6
Nixit Patel

0

Sim, você pode: Pegue esta função de utilidade que escrevi hoje:

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

Você pode passar para esta função qualquer número de coleções, a primeira será a de destino. Todas as demais coleções são fontes a serem transferidas para a de destino.


-1

Fragmento de código. Cortesia - várias postagens no estouro de pilha, incluindo esta.

 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

Você deve fazer isso na sua camada de aplicativo. Se você estiver usando um ORM, ele poderá usar anotações (ou algo semelhante) para extrair referências que existem em outras coleções. Eu só trabalhei com Morphia , e a @Referenceanotação busca a entidade referenciada quando consultada, por isso sou capaz de evitar fazer isso sozinha no código.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.