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

Consulta de intersección de matriz anidada de MongoDB

Hay un par de formas de hacer esto utilizando el marco de agregación

Solo un conjunto simple de datos, por ejemplo:

{
    "_id" : ObjectId("538181738d6bd23253654690"),
    "movies": [
        { "_id": 1, "rating": 5 },
        { "_id": 2, "rating": 6 },
        { "_id": 3, "rating": 7 }
    ]
},
{
    "_id" : ObjectId("538181738d6bd23253654691"),
    "movies": [
        { "_id": 1, "rating": 5 },
        { "_id": 4, "rating": 6 },
        { "_id": 2, "rating": 7 }
    ]
},
{
    "_id" : ObjectId("538181738d6bd23253654692"),
    "movies": [
        { "_id": 2, "rating": 5 },
        { "_id": 5, "rating": 6 },
        { "_id": 6, "rating": 7 }
    ]
}

Usando el primer "usuario" como ejemplo, ahora desea averiguar si alguno de los otros dos usuarios tiene al menos dos películas iguales.

Para MongoDB 2.6 y versiones posteriores, simplemente puede usar $setIntersection operador junto con el $size operador:

db.users.aggregate([

    // Match the possible documents to reduce the working set
    { "$match": {
        "_id": { "$ne": ObjectId("538181738d6bd23253654690") },
        "movies._id": { "$in": [ 1, 2, 3 ] },
        "$and": [
            { "movies": { "$not": { "$size": 1 } } }
        ]
    }},

    // Project a copy of the document if you want to keep more than `_id`
    { "$project": {
        "_id": {
            "_id": "$_id",
            "movies": "$movies"
        },
        "movies": 1,
    }},

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

    // Build the array back with just `_id` values
    { "$group": {
        "_id": "$_id",
        "movies": { "$push": "$movies._id" }
    }},

    // Find the "set intersection" of the two arrays
    { "$project": {
        "movies": {
            "$size": {
                "$setIntersection": [
                   [ 1, 2, 3 ],
                   "$movies"
                ]
            }
        }
    }},

    // Filter the results to those that actually match
    { "$match": { "movies": { "$gte": 2 } } }

])

Esto todavía es posible en versiones anteriores de MongoDB que no tienen esos operadores, solo siguiendo algunos pasos más:

db.users.aggregate([

    // Match the possible documents to reduce the working set
    { "$match": {
        "_id": { "$ne": ObjectId("538181738d6bd23253654690") },
        "movies._id": { "$in": [ 1, 2, 3 ] },
        "$and": [
            { "movies": { "$not": { "$size": 1 } } }
        ]
    }},

    // Project a copy of the document along with the "set" to match
    { "$project": {
        "_id": {
            "_id": "$_id",
            "movies": "$movies"
        },
        "movies": 1,
        "set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
    }},

    // Unwind both those arrays
    { "$unwind": "$movies" },
    { "$unwind": "$set" },

    // Group back the count where both `_id` values are equal
    { "$group": {
        "_id": "$_id",
        "movies": {
           "$sum": {
               "$cond":[
                   { "$eq": [ "$movies._id", "$set" ] },
                   1,
                   0
               ]
           }
        } 
    }},

    // Filter the results to those that actually match
    { "$match": { "movies": { "$gte": 2 } } }
])

En detalle

Eso puede ser un poco para asimilar, por lo que podemos echar un vistazo a cada etapa y desglosarlas para ver qué están haciendo.

$coincidencia :no desea operar en todos los documentos de la colección, por lo que esta es una oportunidad para eliminar los elementos que posiblemente no coincidan, incluso si aún queda trabajo por hacer para encontrar el exacto unos. Entonces, lo obvio es excluir al mismo "usuario" y luego solo hacer coincidir los documentos que tienen al menos una de las mismas películas que se encontraron para ese "usuario".

Lo siguiente que tiene sentido es considerar que cuando desea hacer coincidir n entradas entonces solo documentos que tienen una matriz de "películas" que es más grande que n-1 posiblemente puede contener coincidencias. El uso de $and aquí se ve divertido y no se requiere específicamente, pero si las coincidencias requeridas fueran 4 entonces esa parte real de la declaración se vería así:

        "$and": [
            { "movies": { "$not": { "$size": 1 } } },
            { "movies": { "$not": { "$size": 2 } } },
            { "movies": { "$not": { "$size": 3 } } }
        ]

Entonces, básicamente, "descartarás" las matrices que posiblemente no sean lo suficientemente largas como para tener n partidos. Notando aquí que este $size El operador en el formulario de consulta es diferente a $size para el marco de agregación. No hay forma, por ejemplo, de usar esto con un operador de desigualdad como $gt si su propósito es hacer coincidir específicamente el "tamaño" solicitado. De ahí este formulario de consulta para especificar todos los tamaños posibles que son menores que.

$proyecto :Hay algunos propósitos en esta declaración, de los cuales algunos difieren según la versión de MongoDB que tenga. En primer lugar, y opcionalmente, se guarda una copia del documento bajo el _id valor para que estos campos no sean modificados por el resto de pasos. La otra parte aquí es mantener la matriz de "películas" en la parte superior del documento como una copia para la siguiente etapa.

Lo que también está sucediendo en la versión presentada para las versiones anteriores a la 2.6 es que hay una matriz adicional que representa el _id valores para que las "películas" coincidan. El uso de $cond El operador aquí es solo una forma de crear una representación "literal" de la matriz. Curiosamente, MongoDB 2.6 presenta un operador conocido como $literal para hacer exactamente esto sin la forma graciosa en que estamos usando $cond aquí mismo.

$relajarse :Para hacer algo más, la matriz de películas debe desenrollarse, ya que en cualquier caso es la única forma de aislar el _id existente. valores para las entradas que deben compararse con el "conjunto". Entonces, para la versión anterior a la 2.6, debe "desenrollar" las dos matrices que están presentes.

$grupo :Para MongoDB 2.6 y versiones posteriores, solo se está agrupando en una matriz que solo contiene el _id valores de las películas con las "puntuaciones" eliminadas.

Antes de 2.6, dado que todos los valores se presentan "uno al lado del otro" (y con mucha duplicación), está haciendo una comparación de los dos valores para ver si son iguales. Donde eso es true , esto le dice al $cond declaración del operador para devolver un valor de 1 o 0 donde la condición es false . Esto se devuelve directamente a través de $sum para sumar el número de elementos coincidentes en la matriz al "conjunto" requerido.

$proyecto :Donde esta es la parte diferente para MongoDB 2.6 y superior es que ha retrocedido una matriz de "películas" _id valores que está usando $setIntersection para comparar directamente esas matrices. Como el resultado de esto es una matriz que contiene los elementos que son iguales, luego se envuelve en un $size operador para determinar cuántos elementos se devolvieron en ese conjunto coincidente.

$coincidencia :es la etapa final que se implementó aquí que realiza el paso claro de hacer coincidir solo aquellos documentos cuyo recuento de elementos que se cruzan fue mayor o igual que el número requerido.

Final

Así es básicamente como lo haces. Antes de 2.6 es un poco más torpe y requerirá un poco más de memoria debido a la expansión que se realiza al duplicar cada miembro de la matriz que se encuentra por todos los valores posibles del conjunto, pero sigue siendo una forma válida de hacer esto.

Todo lo que necesitas hacer es aplicar esto con el mayor n valores coincidentes para cumplir con sus condiciones y, por supuesto, asegúrese de que su coincidencia de usuario original tenga el n requerido posibilidades. De lo contrario, genere esto en n-1 de la longitud de la matriz de "películas" del "usuario".