sql >> Base de Datos >  >> NoSQL >> MongoDB

Encontrar dos documentos en MongoDB que comparten un valor clave

Si bien me quedo con los comentarios de que no creo que la forma en que está formulando su pregunta esté realmente relacionada con un problema específico que tenga, de alguna manera explicaré la forma idiomática de SQL en un tipo de solución MongoDB. Estoy convencido de que su solución real sería diferente, pero no nos ha presentado ese problema, sino solo SQL.

Así que considere los siguientes documentos como un conjunto de muestra, eliminando los campos _id de esta lista para mayor claridad:

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

Si ejecutamos el SQL presentado sobre los mismos datos, obtendríamos este resultado:

a|b
a|c
a|c
b|c
b|a
b|a
a|b
b|c

Podemos ver que 2 documentos no coinciden y luego resolver la lógica de la operación SQL. Entonces, la otra forma de decirlo es "¿Qué documentos, dada una clave de "nombre" hacen tener más de uno valor posible en la clave "tipo".

Dado que, con un enfoque mongo, podemos consultar los elementos que no coincida con la condición dada. Tan efectivamente el reverso del 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 operación arrojará los resultados:

{ "name" : "f", "type" : "e" }
{ "name" : "z", "type" : "z" }

Ahora, para obtener el mismo resultado que la consulta SQL, tomaríamos esos resultados y los canalizaríamos a otra consulta:

db.sample.find({$nor: [{ name: "f", type: "e"},{ name: "z", type: "z"}] })

Que llega como el resultado final de coincidencia:

{ "name" : "a", "type" : "b" }
{ "name" : "a", "type" : "c" }
{ "name" : "b", "type" : "c" }
{ "name" : "b", "type" : "a" }
{ "name" : "a", "type" : "b" }
{ "name" : "b", "type" : "c" }

Así que esto funcionará, sin embargo, lo único que puede hacer que esto no sea práctico es la cantidad de documentos que se comparan es muy grande, alcanzamos un límite de trabajo al compactar esos resultados en una matriz.

También sufre un poco por el uso de un negativo en la operación de búsqueda final que forzaría un escaneo de la colección. Pero, con toda justicia, se podría decir lo mismo de la consulta SQL que usa el mismo negativo premisa.

Editar

Por supuesto, lo que no mencioné es que si el conjunto de resultados va al revés y estás haciendo coincidir más da como resultado los elementos excluidos del agregado, luego invierta la lógica para obtener las claves que desea. Simplemente cambie $match de la siguiente manera:

{$match: {$gt: 1}}

Y ese será el resultado, tal vez no los documentos reales, pero es un resultado. Por lo tanto, no necesita otra consulta para hacer coincidir los casos negativos.

Y, en última instancia, esto fue mi culpa porque estaba tan concentrado en la traducción idiomática que no leí. la última línea de su pregunta, dónde hacer di que estabas buscando uno documento.

Por supuesto, actualmente si el tamaño del resultado es superior a 16 MB, entonces está atascado. Al menos hasta la 2.6 lanzamiento, donde los resultados de las operaciones de agregación son un cursor , por lo que puede iterarlo como un .find() .

También introducido en 2.6 es el $size operador que se utiliza para encontrar el tamaño de una matriz en el documento. Entonces esto ayudaría a eliminar el segundo $unwind y $group que se utilizan para obtener la longitud del conjunto. Esto altera la consulta a una forma más rápida:

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

Y MongoDB 2.6.0-rc0 está disponible actualmente si lo hace solo para uso personal o desarrollo/prueba.

Moraleja de la historia. Sí, puedes hazlo, pero de verdad quiero o necesito para hacerlo de esa manera? Entonces probablemente no, y si hizo una pregunta diferente sobre el caso de negocios específico, puede obtener una respuesta diferente. Pero, de nuevo, esto puede ser exactamente adecuado para lo que desea.

Nota

Vale la pena mencionar que cuando observa los resultados del SQL, se duplicará erróneamente varios artículos debido a las otras opciones de tipo disponibles si no usó un DISTINCT para esos valores o esencialmente otra agrupación. Pero ese es el resultado que estaba produciendo este proceso usando MongoDB.

Para Alejandro

Esta es la salida del agregado en el shell de las versiones 2.4.x actuales:

{
    "result" : [
            {
                    "name" : "f",
                    "type" : "e"
            },
            {
                    "name" : "z",
                    "type" : "z"
            }
    ],
    "ok" : 1
}

Así que haga esto para obtener una var para pasar como argumento a la condición $nor en la segunda búsqueda, así:

var cond = db.sample.aggregate([ .....

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

Y deberías obtener los mismos resultados. De lo contrario, consulte a su conductor.