MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Encontrando dois documentos no MongoDB que compartilham um valor de chave


Enquanto eu mantenho os comentários de que não acho que a maneira como você está formulando sua pergunta esteja realmente relacionada a um problema específico que você tem, vou explicar de alguma forma a maneira SQL idiomática em um tipo de solução do MongoDB. Acredito que sua solução real seria diferente, mas você não nos apresentou esse problema, mas apenas SQL.

Portanto, considere os seguintes documentos como um conjunto de amostra, removendo os campos _id nesta listagem para maior clareza:
{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }
{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }
{ "name" : "z", "type" : "z" }

Se executássemos o SQL apresentado sobre os mesmos dados, obteríamos este resultado:
a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c

Podemos ver que 2 documentos não coincidem e, em seguida, trabalhar a lógica da operação SQL. Então, a outra maneira de dizer é "Quais documentos recebem uma chave de "nome" fazem tem mais de um valor possível na chave "tipo".

Dado que, adotando uma abordagem mongo, podemos consultar os itens que não corresponder à condição dada. Tão eficazmente o reverso do resultado:
db.sample.aggregate([

    // Store unique documents grouped by the "name"
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type" 
            }
        } 
    }},

    // Unwind the "set" results
    {$unwind: "$comp"},

    // Push the results back to get the unique count
    // *note* you could not have done this with alongside $addtoSet
    {$group: {
        _id: "$_id",
        comp: {
            $push: { 
                name: "$comp.name",
                type: "$comp.type" 
            }
        },
        count: {$sum: 1} 
    }},

    // Match only what was counted once
    {$match: {count: 1}},

    // Unwind the array
    {$unwind: "$comp"},

    // Clean up to "name" and "type" only
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}

])

Esta operação produzirá os resultados:
{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }

Agora, para obter o mesmo resultado da consulta SQL, pegaríamos esses resultados e os canalizaríamos para outra consulta:
db.sample.find({$nor: [{ name: "f", type: "e"},{ name: "z", type: "z"}] })

Que chega como o resultado final da correspondência:
{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }

Portanto, isso funcionará, no entanto, a única coisa que pode tornar isso impraticável é onde o número de documentos sendo comparados é muito grande, atingimos um limite de trabalho para compactar esses resultados em uma matriz.

Também sofre um pouco com o uso de um negativo na operação de localização final que forçaria uma varredura da coleção. Mas, com toda a justiça, o mesmo pode ser dito da consulta SQL que usa o mesmo negativo premissa.

Editar


Claro que eu não mencionei é que se o conjunto de resultados for ao contrário e você estiver combinando mais resulta nos itens excluídos do agregado, basta inverter a lógica para obter as chaves desejadas. Basta alterar $match da seguinte forma:
{$match: {$gt: 1}}

E esse será o resultado, talvez não os documentos reais, mas é um resultado. Portanto, você não precisa de outra consulta para corresponder aos casos negativos.

E, no final das contas, isso foi culpa minha porque eu estava tão focado na tradução idiomática que não li a última linha da sua pergunta, onde fazer diga que você estava procurando por um documento.

Claro, atualmente se o tamanho do resultado for maior que 16 MB, você está preso. Pelo menos até o 2.6 release, onde os resultados das operações de agregação são um cursor , então você pode iterar isso como um .find() .

Também introduzido em 2.6 é o $size operador que é usado para encontrar o tamanho de uma matriz no documento. Isso ajudaria a remover o segundo $unwind e $grupo que são usados ​​para obter o comprimento do conjunto. Isso altera a consulta para um formato mais rápido:
db.sample.aggregate([
    {$group: { 
        _id: "$name",
        comp: {
            $addToSet: { 
                name:"$name",
                type: "$type"
            }
        } 
    }},
    {$project: { 
        comp: 1,
        count: {$size: "$comp"} 
    }},
    {$match: {count: {$gt: 1}}},
    {$unwind: "$comp"},
    {$project: { _id: 0, name: "$comp.name", type: "$comp.type"}}
])

E o MongoDB 2.6.0-rc0 está atualmente disponível se você estiver fazendo isso apenas para uso pessoal ou desenvolvimento/teste.

Moral da história. Sim, você pode faça isso, Mas você realmente quer ou precisa fazer assim? Então provavelmente não, e se você fizer uma pergunta diferente sobre o caso de negócios específico, poderá obter uma resposta diferente. Mas, novamente, isso pode ser exatamente certo para o que você deseja.

Observação


Vale a pena mencionar que quando você olha para os resultados do SQL, ele será erroneamente duplicado vários itens devido às outras opções de tipo disponíveis se você não usou um DISTINCT para esses valores ou essencialmente outro agrupamento. Mas esse é o resultado que estava sendo produzido por esse processo usando o MongoDB.

Para Alexandre


Esta é a saída do agregado no shell das versões 2.4.x atuais:
{
    "result" : [
            {
                    "name" : "f",
                    "type" : "e"
            },
            {
                    "name" : "z",
                    "type" : "z"
            }
    ],
    "ok" : 1
}

Então faça isso para que um var passe como argumento para a condição $nor na segunda busca, assim:
var cond = db.sample.aggregate([ .....

db.sample.find({$nor: cond.result })

E você deve obter os mesmos resultados. Caso contrário, consulte o seu motorista.