Como faço para realizar a junção SQL equivalente em MongoDB?

Como é que faço a junção SQL equivalente em MongoDB?

por exemplo, diga que você tem duas coleções (usuários e comentários) e eu quero puxar todos os comentários com pid=444, juntamente com a informação do Usuário para cada um.

comments
  { uid:12345, pid:444, comment="blah" }
  { uid:12345, pid:888, comment="asdf" }
  { uid:99999, pid:444, comment="qwer" }

users
  { uid:12345, name:"john" }
  { uid:99999, name:"mia"  }

Existe uma maneira de puxar todos os comentários com um determinado campo (por exemplo. ...find ({pid:444}) e a informação do utilizador associada a cada comentário de uma só vez?

Neste momento, estou primeiro a receber os comentários que correspondem aos meus critérios, depois a descobrir todos os uid estão nesse conjunto de resultados, obtendo os objetos do Usuário, e fundindo-os com os resultados do comentário. Parece que estou a fazer mal.

Author: Ozair Kafray, 2010-02-28

19 answers

A partir de Mongo 3.2, as respostas a esta pergunta já não estão mais corretas. O novo operador $lookup adicionado ao pipeline de agregação é essencialmente idêntico a uma junção exterior à esquerda:

Https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup

Dos documentos:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}
É claro que Mongo não é uma base de dados relacional, e os devs estão a ter o cuidado de recomendar casos específicos de uso para $lookup, mas pelo menos a partir de 3.2 fazer a adesão é agora possível com MongoDB.
 242
Author: Clayton Gulick, 2017-04-10 05:25:37

Esta página nos endereços oficiais do sítio mongodb exactamente esta pergunta:

Http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/

Quando mostrarmos a nossa lista de histórias, teremos de mostrar o nome do utilizador que publicou a história. Se estivéssemos usando um banco de dados relacional, poderíamos realizar uma ligação em usuários e lojas, e obter todos os nossos objetos em uma única consulta. Mas o MongoDB não apoia as ligações e, por vezes, exige um pouco de desnormalização. Aqui, isso significa cache o atributo 'username'. Os puristas relacionais podem já estar a sentir-se inquietos, como se estivéssemos a violar alguma lei universal. Mas vamos ter em mente que as coleções MongoDB não são equivalentes a tabelas relacionais; cada uma serve a um objetivo de design único. Uma tabela normalizada fornece um pedaço atômico e isolado de dados. Um documento, porém, representa mais de perto um objeto como um todo. No caso de um site de notícias sociais, pode-se argumentar que um nome de usuário é intrínseco à história que está sendo postada.
 131
Author: William Stein, 2015-07-13 17:11:35

Podemos fundir / juntar todos os dados dentro de apenas uma coleção com uma função fácil em poucas linhas usando o console cliente mongodb, e agora podemos ser capazes de executar a consulta desejada. Abaixo de um exemplo completo,

.- Autores:

db.authors.insert([
    {
        _id: 'a1',
        name: { first: 'orlando', last: 'becerra' },
        age: 27
    },
    {
        _id: 'a2',
        name: { first: 'mayra', last: 'sanchez' },
        age: 21
    }
]);

.- Categorias:

db.categories.insert([
    {
        _id: 'c1',
        name: 'sci-fi'
    },
    {
        _id: 'c2',
        name: 'romance'
    }
]);

.- Livros

db.books.insert([
    {
        _id: 'b1',
        name: 'Groovy Book',
        category: 'c1',
        authors: ['a1']
    },
    {
        _id: 'b2',
        name: 'Java Book',
        category: 'c2',
        authors: ['a1','a2']
    },
]);

.- Crédito contabilístico

db.lendings.insert([
    {
        _id: 'l1',
        book: 'b1',
        date: new Date('01/01/11'),
        lendingBy: 'jose'
    },
    {
        _id: 'l2',
        book: 'b1',
        date: new Date('02/02/12'),
        lendingBy: 'maria'
    }
]);

.- A magia:

db.books.find().forEach(
    function (newBook) {
        newBook.category = db.categories.findOne( { "_id": newBook.category } );
        newBook.lendings = db.lendings.find( { "book": newBook._id  } ).toArray();
        newBook.authors = db.authors.find( { "_id": { $in: newBook.authors }  } ).toArray();
        db.booksReloaded.insert(newBook);
    }
);

.- Obter os novos dados de recolha:

db.booksReloaded.find().pretty()

.- Resposta:)

{
    "_id" : "b1",
    "name" : "Groovy Book",
    "category" : {
        "_id" : "c1",
        "name" : "sci-fi"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        }
    ],
    "lendings" : [
        {
            "_id" : "l1",
            "book" : "b1",
            "date" : ISODate("2011-01-01T00:00:00Z"),
            "lendingBy" : "jose"
        },
        {
            "_id" : "l2",
            "book" : "b1",
            "date" : ISODate("2012-02-02T00:00:00Z"),
            "lendingBy" : "maria"
        }
    ]
}
{
    "_id" : "b2",
    "name" : "Java Book",
    "category" : {
        "_id" : "c2",
        "name" : "romance"
    },
    "authors" : [
        {
            "_id" : "a1",
            "name" : {
                "first" : "orlando",
                "last" : "becerra"
            },
            "age" : 27
        },
        {
            "_id" : "a2",
            "name" : {
                "first" : "mayra",
                "last" : "sanchez"
            },
            "age" : 21
        }
    ],
    "lendings" : [ ]
}
Espero que estas linhas possam ajudar. você.
 127
Author: Orlando Becerra, 2014-03-30 03:12:08
Tens de o fazer como descreveste. MongoDB é uma base de dados não-relacional e não suporta juntas.
 35
Author: Otto Allmendinger, 2010-02-28 11:34:00
Aqui está um exemplo de uma junção" * actores e Filmes colecções:

Https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt

Utiliza o método .mapReduce()

* join - uma alternativa para se juntar em bases de dados orientadas para documentos

 17
Author: antitoxic, 2015-08-28 16:42:40

Como outros têm apontado que você está tentando criar um banco de dados relacional a partir de nenhum banco de dados relacional, que você realmente não quer fazer, mas de qualquer forma, se você tem um caso que você tem para fazer isso aqui é uma solução que você pode usar. Primeiro fazemos uma pesquisa de busca na coleção A (ou em seus usuários de caso) e, em seguida, obtemos cada item como um objeto, em seguida, usamos propriedade objeto (no seu caso uid) para procurar em nossa segunda coleção (em seus comentários de caso) se pudermos encontrá-lo, então temos uma correspondência e nós pode imprimir ou fazer algo com ele. Espero que isto te ajude e boa sorte.

db.users.find().forEach(
function (object) {
    var commonInBoth=db.comments.findOne({ "uid": object.uid} );
    if (commonInBoth != null) {
        printjson(commonInBoth) ;
        printjson(object) ;
    }else {
        // did not match so we don't care in this case
    }
});
 16
Author: CPU 100, 2016-02-17 13:06:01
Depende do que estás a tentar fazer.

Você tem atualmente configurado como uma base de dados normalizada, o que é bom, e a forma como você está fazendo isso é apropriado.

No entanto, há outras formas de o fazer.

Você pode ter uma coleção de posts que tem incorporado comentários para cada post com referências aos usuários que você pode consultar iterativamente para obter. Você poderia armazenar o nome do usuário com os comentários, você poderia armazená-los todos em um documento.

A a coisa com NoSQL é que ele é projetado para esquemas flexíveis e muito rápido leitura e escrita. Em uma fazenda de dados grande típico o banco de dados é o maior gargalo, você tem menos motores de banco de dados do que você faz aplicativos e servidores front-end...eles são mais caros, mas mais poderosos, também o espaço de disco rígido é muito barato comparativamente. A normalização vem do conceito de tentar economizar espaço, mas vem com um custo em fazer com que suas bases de dados executem juntas complicadas e verificar a integridade de relacionamentos, realizando operações em cascata. Tudo o que poupa aos desenvolvedores algumas dores de cabeça se eles projetaram o banco de dados corretamente.

Com NoSQL, se você aceitar que a redundância e o espaço de armazenamento não são problemas devido ao seu custo (tanto em tempo de processador necessários para fazer atualizações e unidade de disco rígido de custos para armazenar mais dados), denormalizing não é um problema (para incorporado matrizes que se tornam centenas de milhares de itens pode ser um problema de desempenho, mas na maioria das vezes isso não é um problema). Além disso, você terá vários servidores de aplicação e front-end para cada conjunto de banco de dados. Tê-los fazer o levantamento pesado das junções e deixar os servidores de banco de dados ficar para a leitura e escrita.

O que está a fazer está bem, e há outras formas de o fazer. Confira os modelos de dados da documentação mongodb para alguns grandes exemplos. http://docs.mongodb.org/manual/data-modeling/
 10
Author: Snowburnt, 2013-11-27 19:18:51
Há uma especificação de que muitos motoristas suportam o que se chama DBRef.

O DBRef é uma especificação mais formal para criar referências entre documentos. DBRefs (geralmente) incluem um nome de coleção, bem como um ID de objeto. A maioria dos desenvolvedores só usa DBRefs se a coleção puder mudar de um documento para o próximo. Se a sua colecção referenciada for sempre a mesma, as referências manuais descritas acima são mais eficientes.

Retirado de Documentação MongoDB: Modelos De Dados > Referência Do Modelo De Dados > Referências À Base De Dados

 9
Author: Pickels, 2015-11-23 13:13:55

Pode juntar-se a duas colecções em Mongo usando a pesquisa que é oferecida na versão 3.2. No seu caso, a consulta seria

db.comments.aggregate({
    $lookup:{
        from:"users",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})

Ou você também pode se juntar em relação aos usuários, então haverá uma pequena mudança como indicado abaixo.

db.users.aggregate({
    $lookup:{
        from:"comments",
        localField:"uid",
        foreignField:"uid",
        as:"users_comments"
    }
})
Vai funcionar da mesma forma que a esquerda e a direita no SQL.
 7
Author: jarry jafery, 2016-08-09 13:35:48

Com a combinação certa de $procurar, $project e $ match , você pode juntar-se a tabelas de vários parâmetros. Isso é porque eles podem ser acorrentados várias vezes.

Suponha que queremos fazer o seguinte (referência)

SELECT S.* FROM LeftTable S
LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND 
S.MOB IS NOT NULL

Passo 1: Ligar todas as tabelas

Podes procurar quantas mesas quiseres.

$procurar - Um para cada tabela na consulta

$- porque os dados são desnormalizado correctamente, envolto em matrizes

Código Python..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"}

                        ])

Passo 2: Definir todas as condições

$projecto : Defina aqui todas as declarações condicionais, mais todas as variáveis que deseja seleccionar.

Código Python..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }}
                        ])

Passo 3: juntar todos os condicionalismos

$match - Junte-se a todas as condições usando ou e etc. Pode haver múltiplos destes.

$projecto : undefine all condicionais

Código Python..

db.LeftTable.aggregate([
                        # connect all tables

                        {"$lookup": {
                          "from": "RightTable",
                          "localField": "ID",
                          "foreignField": "ID",
                          "as": "R"
                        }},
                        {"$unwind": "$R"},

                        # define conditionals + variables

                        {"$project": {
                          "midEq": {"$eq": ["$MID", "$R.MID"]},
                          "ID": 1, "MOB": 1, "MID": 1
                        }},

                        # join all conditionals

                        {"$match": {
                          "$and": [
                            {"R.TIM": {"$gt": 0}}, 
                            {"MOB": {"$exists": True}},
                            {"midEq": {"$eq": True}}
                        ]}},

                        # undefine conditionals

                        {"$project": {
                          "midEq": 0
                        }}

                        ])

Praticamente qualquer combinação de tabelas, condicionalismos e juntas pode ser feita desta forma.

 7
Author: Shaurabh Bharti, 2017-05-23 12:18:22
Antes 3.2.6, o Mongodb não suporta a consulta de junção como o mysql. abaixo a solução que funciona para você.
 db.getCollection('comments').aggregate([
        {$match : {pid : 444}},
        {$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}},
   ])
 5
Author: Anish Agarwal, 2016-10-22 04:19:49

Pode executar consultas SQL, incluindo juntar-se ao MongoDB com mongo_fdw de Postgres.

 3
Author: metdos, 2013-06-14 15:21:57

$procurar (agregação)

Efectua uma junção exterior à esquerda de uma colecção não protegida na mesma base de dados para filtrar em documentos da colecção "unida" para processamento. Para cada documento de entrada, a etapa $lookup adiciona um novo campo de array cujos elementos são os documentos correspondentes da coleção "joined". A etapa $lookup passa estes documentos reformulados para a próxima etapa. A etapa $lookup tem os seguintes sintaxes:

Igualdade De Oportunidades

Para realizar um igualdade de correspondência entre um campo a partir dos documentos de entrada com um campo a partir dos documentos da coleção" Unidos", a etapa $lookup tem a seguinte sintaxe:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

A operação corresponderia à seguinte declaração pseudo-SQL:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT <documents as determined from the pipeline>
                               FROM <collection to join>
                               WHERE <pipeline> );

URL do Mongo

 3
Author: GoutamS, 2018-03-27 12:19:53

MongoDB não permite ligações, mas você pode usar plugins para lidar com isso. Verifique o 'plugin' de junção do mongo. É o melhor e já o usei. Você pode instalá-lo usando o npm diretamente como este npm install mongo-join. Você pode verificar a documentação completa com exemplos .

(++) ferramenta realmente útil quando precisamos de juntar (N) colecções

(--) podemos aplicar condições apenas no nível superior da consulta

Exemplo

var Join = require('mongo-join').Join, mongodb = require('mongodb'), Db = mongodb.Db, Server = mongodb.Server;
db.open(function (err, Database) {
    Database.collection('Appoint', function (err, Appoints) {

        /* we can put conditions just on the top level */
        Appoints.find({_id_Doctor: id_doctor ,full_date :{ $gte: start_date },
            full_date :{ $lte: end_date }}, function (err, cursor) {
            var join = new Join(Database).on({
                field: '_id_Doctor', // <- field in Appoints document
                to: '_id',         // <- field in User doc. treated as ObjectID automatically.
                from: 'User'  // <- collection name for User doc
            }).on({
                field: '_id_Patient', // <- field in Appoints doc
                to: '_id',         // <- field in User doc. treated as ObjectID automatically.
                from: 'User'  // <- collection name for User doc
            })
            join.toArray(cursor, function (err, joinedDocs) {

                /* do what ever you want here */
                /* you can fetch the table and apply your own conditions */
                .....
                .....
                .....


                resp.status(200);
                resp.json({
                    "status": 200,
                    "message": "success",
                    "Appoints_Range": joinedDocs,


                });
                return resp;


            });

    });
 2
Author: Amine_Dev, 2015-10-15 21:15:44

O PlayORM pode fazê-lo por si usando S-SQL(SQL escalável), que apenas adiciona particionamento de modo a que possa fazer ligações dentro das partições.

 0
Author: Dean Hiller, 2012-09-07 17:30:48
Podes fazê-lo usando o oleoduto da agregação, mas é uma dor escrevê-lo tu mesmo.

Pode usar mongo-join-query para criar o pipeline de agregação automaticamente a partir da sua consulta.

É assim que a sua pergunta se pareceria:

const mongoose = require("mongoose");
const joinQuery = require("mongo-join-query");

joinQuery(
    mongoose.models.Comment,
    {
        find: { pid:444 },
        populate: ["uid"]
    },
    (err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);

O seu resultado teria o objecto do utilizador no campo uid e você pode ligar os níveis mais profundos que quiser. Você pode preencher a referência ao usuário, que faz referência a uma equipe, que faz referência a algo mais, etc..

Disclaimer : Eu escrevi mongo-join-query para resolver este problema exato.

 0
Author: Marcelo Lazaroni, 2017-12-13 14:28:43
Acho que, se precisar de tabelas de dados normalizadas, tem de tentar outras soluções de base de dados. Mas tenho a soluição para o MOngo no Git. A propósito, em código inserido-tem o nome do Filme, mas o ID do filme noi .

Problema

Tens uma colecção de actores com uma série de filmes que eles fizeram. Você quer gerar uma coleção de filmes com uma série de atores em cada um.

Alguma amostra data

 db.actors.insert( { actor: "Richard Gere", movies: ['Pretty Woman', 'Runaway Bride', 'Chicago'] });
 db.actors.insert( { actor: "Julia Roberts", movies: ['Pretty Woman', 'Runaway Bride', 'Erin Brockovich'] });

Solução

Temos de rever cada filme no documento do Actor e emitir cada filme individualmente. A captura aqui está na fase de redução. Não podemos emitir um array a partir da fase de redução, então devemos construir um array de atores dentro do documento "valor" que é devolvido. Codigo
map = function() {
  for(var i in this.movies){
    key = { movie: this.movies[i] };
    value = { actors: [ this.actor ] };
    emit(key, value);
  }
}

reduce = function(key, values) {
  actor_list = { actors: [] };
  for(var i in values) {
    actor_list.actors = values[i].actors.concat(actor_list.actors);
  }
  return actor_list;
}

Note como actor_list é na verdade um objeto javascript que contém um array. Observe também que o mapa emite a mesma estrutura.

[6] depois de executar o mapa / reduzir, coloque-o na colecção "pivô" e imprima o resultado:

Printjson (db.interveniente.mapReduce (mapa, reduzir, "pivô"); PO.dinamica.encontrar().forEach (printjson);

Aqui está a amostra, note que "Pretty Woman" e "Runaway Bride" têm "Richard Gere"e" Julia Roberts".
{ "_id" : { "movie" : "Chicago" }, "value" : { "actors" : [ "Richard Gere" ] } }
{ "_id" : { "movie" : "Erin Brockovich" }, "value" : { "actors" : [ "Julia Roberts" ] } }
{ "_id" : { "movie" : "Pretty Woman" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } }
{ "_id" : { "movie" : "Runaway Bride" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } }

 -2
Author: Max Sherbakov, 2015-09-23 06:57:27
Não, Não me parece que estejas a fazer mal. As juntas MongoDB são "lado cliente". Tal como disseste:
Neste momento, estou primeiro a receber os comentários que correspondem aos meus critérios, depois a descobrir todos os uid nesse conjunto de resultados, a obter os objectos do utilizador e a fundi-los com os resultados do comentário. Parece que estou a fazer mal.
1) Select from the collection you're interested in.
2) From that collection pull out ID's you need
3) Select from other collections
4) Decorate your original results.
Não é uma adesão "real" , mas é muito mais útil do que uma adesão SQL porque não é preciso lidar com linhas duplicadas para "muitos" cortes laterais, em vez de decorar o conjunto originalmente selecionado. Há muitos disparates e disparates nesta página. Acontece que 5 anos depois MongoDB ainda é uma coisa.
 -3
Author: Michael Cole, 2015-09-04 15:23:14

Podemos fundir duas colecções usando a consulta Sub mongoDB. Aqui está o exemplo, Commentss--

`db.commentss.insert([
  { uid:12345, pid:444, comment:"blah" },
  { uid:12345, pid:888, comment:"asdf" },
  { uid:99999, pid:444, comment:"qwer" }])`

Userss--

db.userss.insert([
  { uid:12345, name:"john" },
  { uid:99999, name:"mia"  }])

A sub-pesquisa MongoDB para o JOIN--

`db.commentss.find().forEach(
    function (newComments) {
        newComments.userss = db.userss.find( { "uid": newComments.uid } ).toArray();
        db.newCommentUsers.insert(newComments);
    }
);`

Obter o resultado da colecção recém-gerada--

db.newCommentUsers.find().pretty()

Resultado--

`{
    "_id" : ObjectId("5511236e29709afa03f226ef"),
    "uid" : 12345,
    "pid" : 444,
    "comment" : "blah",
    "userss" : [
        {
            "_id" : ObjectId("5511238129709afa03f226f2"),
            "uid" : 12345,
            "name" : "john"
        }
    ]
}
{
    "_id" : ObjectId("5511236e29709afa03f226f0"),
    "uid" : 12345,
    "pid" : 888,
    "comment" : "asdf",
    "userss" : [
        {
            "_id" : ObjectId("5511238129709afa03f226f2"),
            "uid" : 12345,
            "name" : "john"
        }
    ]
}
{
    "_id" : ObjectId("5511236e29709afa03f226f1"),
    "uid" : 99999,
    "pid" : 444,
    "comment" : "qwer",
    "userss" : [
        {
            "_id" : ObjectId("5511238129709afa03f226f3"),
            "uid" : 99999,
            "name" : "mia"
        }
    ]
}`
Espero que isto ajude.
 -4
Author: Krishna, 2015-03-24 09:27:19