Associação Muitos-para-Muitos do MongoDB

143

Como você faria uma associação muitos-para-muitos com o MongoDB?

Por exemplo; digamos que você tenha uma tabela Usuários e uma tabela Funções. Os usuários têm muitas funções, e as funções têm muitos usuários. Na área SQL, você criaria uma tabela UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

Como o mesmo tipo de relacionamento é tratado no MongoDB?

Josh Close
fonte
Veja também respostas para esta pergunta e esta pergunta
Matthew Murdoch

Respostas:

96

Dependendo das necessidades da sua consulta, você pode colocar tudo no documento do usuário:

{name:"Joe"
,roles:["Admin","User","Engineer"]
}

Para obter todos os engenheiros, use:

db.things.find( { roles : "Engineer" } );

Se você deseja manter as funções em documentos separados, pode incluir o _id do documento na matriz de funções em vez do nome:

{name:"Joe"
,roles:["4b5783300334000000000aa9","5783300334000000000aa943","6c6793300334001000000006"]
}

e configure os papéis como:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}
morrerikh
fonte
7
O último seria melhor, pois preciso obter uma lista de todas as funções disponíveis. A única parte ruim é que preciso configurar os dois extremos da associação. Ao fazer o caminho do SQL, adicionar um UserRole fará com que o usuário saiba sobre a função e a função saiba sobre o usuário. Dessa forma, terei que definir a função no usuário e o usuário na função. Eu acho que está bem.
Josh Close
46
Só porque um banco de dados não SQL apoio não significa que as referências não são ferramentas úteis NoSQL = NoReference ver esta explicação: mongodb.org/display/DOCS/Schema+Design
Tom Gruner
8
Isso não parece uma boa ideia. Se você tiver apenas seis funções, claro, mas e se você tivesse 20.000 objetos que poderiam ser vinculados a mais 20.000 objetos (em um relacionamento muitos-muitos)? Até os documentos do MongoDB sugerem que você deve evitar ter grandes e mutáveis ​​matrizes de referências. docs.mongodb.org/manual/tutorial/…
CaptSaltyJack
Obviamente, para relacionamentos muitos para muitos com muitos objetos, você deseja usar uma solução diferente (como o exemplo do editor / livro nos documentos). Nesse caso, ele funciona bem e só complicaria as coisas se você criar documentos separados da função do usuário.
234152Data de publicação
1
Isso funciona para a maioria dos sistemas, pois as funções são geralmente um conjunto pequeno e geralmente utilizamos um usuário e, em seguida, analisamos suas funções. Mas e se os papéis forem grandes? ou e se eu pedir para você me fornecer uma lista de usuários com função == "Engenheiro"? Agora você precisaria consultar toda a sua coleção de usuários (visitando todos os usuários que também não possuem a função Engenheiro) para obter 2 ou 3 usuários que possam ter essa função entre milhões de usuários, por exemplo. Uma tabela ou coleção separada é muito melhor.
theprogrammer
31

Em vez de tentar modelar de acordo com nossos anos de experiência com RDBMS, achei muito mais fácil modelar soluções de repositório de documentos usando MongoDB, Redis e outros armazenamentos de dados NoSQL otimizando para os casos de uso de leitura, considerando a capacidade atômica operações de gravação que precisam ser suportadas pelos casos de uso de gravação.

Por exemplo, os usos de um domínio "Usuários em Funções" seguem:

  1. Função - Criar, Ler, Atualizar, Excluir, Listar Usuários, Adicionar Usuário, Remover Usuário, Limpar Todos os Usuários, Índice de Usuário ou similar ao suporte "É Usuário na Função" (operações como um contêiner + seus próprios metadados).
  2. Usuário - Criar, Ler, Atualizar, Excluir (operações CRUD como uma entidade independente)

Isso pode ser modelado como os seguintes modelos de documento:

User: { _id: UniqueId, name: string, roles: string[] }
    Indexes: unique: [ name ]
Role: { _id: UniqueId, name: string, users: string[] }
    Indexes: unique: [ name ]

Para dar suporte aos usos de alta frequência, como os recursos relacionados à função da entidade Usuário, o User.Roles é intencionalmente desnormalizado, armazenado no Usuário e no Role.Users que possuem armazenamento duplicado.

Se não estiver prontamente aparente no texto, mas esse é o tipo de pensamento incentivado ao usar repositórios de documentos.

Espero que isso ajude a preencher a lacuna em relação ao lado de leitura das operações.

Para o lado da gravação, o que é incentivado é modelar de acordo com as gravações atômicas. Por exemplo, se as estruturas do documento exigirem a aquisição de um bloqueio, a atualização de um documento, o outro e, possivelmente, mais documentos, e a liberação do bloqueio, provavelmente o modelo falhou. Só porque podemos construir bloqueios distribuídos não significa que devemos usá-los.

No caso do modelo Usuário em Funções, as operações que esticam nossa prevenção de gravação atômica de bloqueios estão adicionando ou removendo um Usuário de uma Função. Nos dois casos, uma operação bem-sucedida resulta na atualização de um único usuário e um único documento de função. Se algo falhar, é fácil executar a limpeza. Essa é a única razão pela qual o padrão Unit of Work aparece bastante onde repositórios de documentos são usados.

A operação que realmente amplia nossa prevenção de gravação atômica de bloqueios está limpando uma Função, o que resultaria em muitas atualizações do Usuário para remover o Role.name da matriz User.roles. Essa operação de clear então é geralmente desencorajada, mas se necessário, pode ser implementada ordenando as operações:

  1. Obtenha a lista de nomes de usuário de Role.users.
  2. Itere os nomes de usuário da etapa 1, remova o nome da função de User.roles.
  3. Limpe os Role.users.

No caso de um problema, que provavelmente ocorrerá na etapa 2, é fácil fazer uma reversão, pois o mesmo conjunto de nomes de usuário da etapa 1 pode ser usado para recuperar ou continuar.

paegun
fonte
15

Acabei de me deparar com essa pergunta e, embora seja antiga, pensei que seria útil adicionar algumas possibilidades não mencionadas nas respostas dadas. Além disso, as coisas mudaram um pouco nos últimos anos, portanto, vale ressaltar que SQL e NoSQL estão se aproximando.

Um dos comentadores levantou a sábia atitude de advertência de que "se os dados são relacionais, use relacionais". No entanto, esse comentário só faz sentido no mundo relacional, onde os esquemas sempre vêm antes do aplicativo.

MUNDO RELACIONAL: estruturar dados> Gravar aplicativo para obtê-lo
NOSQL WORLD: projetar aplicativo> estruturar dados de acordo

Mesmo que os dados sejam relacionais, o NoSQL ainda é uma opção. Por exemplo, relacionamentos um-para-muitos não são problema algum e são amplamente abordados nos documentos do MongoDB

UMA SOLUÇÃO DE 2015 PARA UM PROBLEMA DE 2010

Desde que esta pergunta foi publicada, houve sérias tentativas de aproximar o noSQL do SQL. A equipe liderada por Yannis Papakonstantinou da Universidade da Califórnia (San Diego) está trabalhando no FORWARD , uma implementação do SQL ++ que em breve poderá ser a solução para problemas persistentes, como o postado aqui.

Em um nível mais prático, o lançamento do Couchbase 4.0 significou que, pela primeira vez, você pode criar JOINs nativos no NoSQL. Eles usam seu próprio N1QL. Este é um exemplo de um JOINde seus tutoriais :

SELECT usr.personal_details, orders 
        FROM users_with_orders usr 
            USE KEYS "Elinor_33313792" 
                JOIN orders_with_users orders 
                    ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END

O N1QL permite a maioria, se não todas, as operações SQL, incluindo agregação, filtragem, etc.

A SOLUÇÃO HÍBRIDA NÃO TÃO NOVA

Se o MongoDB ainda for a única opção, gostaria de voltar ao meu argumento de que o aplicativo deve ter precedência sobre a estrutura dos dados. Nenhuma das respostas menciona a incorporação híbrida, na qual a maioria dos dados consultados é incorporada no documento / objeto e as referências são mantidas para uma minoria de casos.

Exemplo: as informações (exceto o nome da função) podem esperar? o bootstrapping do aplicativo poderia ser mais rápido se você não solicitasse nada que o usuário ainda não precisas?

Pode ser esse o caso se o usuário efetuar login e precisar ver todas as opções para todas as funções às quais ele pertence. No entanto, o usuário é um "engenheiro" e as opções para essa função raramente são usadas. Isso significa que o aplicativo precisa apenas mostrar as opções para um engenheiro, caso ele queira clicar nelas.

Isso pode ser alcançado com um documento que informa ao aplicativo no início (1) quais funções o usuário pertence e (2) onde obter informações sobre um evento vinculado a uma função específica.

   {_id: ObjectID(),
    roles: [[“Engineer”, ObjectId()”],
            [“Administrator”, ObjectId()”]]
   }

Ou, melhor ainda, indexe o campo role.name na coleção de funções e talvez você não precise incorporar ObjectID () também.

Outro exemplo: as informações sobre TODAS as funções solicitadas TODAS AS VEZES?

Também pode ser que o usuário efetue login no painel e 90% do tempo execute tarefas vinculadas à função "Engenheiro". A incorporação híbrida pode ser feita para esse papel específico na íntegra e manter referências apenas para o resto.

{_id: ObjectID(),
  roles: [{name: Engineer”, 
           property1: value1,
           property2: value2
          },   
          [“Administrator”, ObjectId()”]
         ]
}

Ser sem esquema não é apenas uma característica do NoSQL; pode ser uma vantagem nesse caso. É perfeitamente válido aninhar diferentes tipos de objetos na propriedade "Funções" de um objeto de usuário.

cortopia
fonte
5

no caso de empregado e empresa estarem objeto de entidade, tente usar o seguinte esquema:

employee{
   //put your contract to employee
   contracts:{ item1, item2, item3,...}
}

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}
Andrei Andrushkevich
fonte