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

Haga coincidir al menos N elementos de una matriz con una lista de condiciones

Su pregunta tiene dos posibilidades para mí, pero tal vez alguna explicación para comenzar.

En primer lugar, debo explicarle que no comprende la intención de $elemMatch y se usa mal en este caso.

La idea de $elemMatch es crear un "documento de consulta" que en realidad se aplica a los elementos de la matriz. La intención es donde tiene "múltiples condiciones" en un documento dentro de la matriz para que coincida discretamente dentro del documento miembro, y no dentro de toda la matriz del documento externo. es decir:

{
   "data": [
       { "a": 1, "b": 3 },
       { "a": 2, "b": 2 }
   ]
}

Y la siguiente consulta funcionará, aunque no coincida ningún elemento individual real en esa matriz, pero sí lo hará todo el documento:

db.collection.find({ "data.a": 1, "data.b": 2 })

Pero para verificar si un elemento real cumple con ambas condiciones, aquí es donde usa $elemMatch :

db.collection.find({ "data": { "a": 1, "b": 2 } })

Por lo tanto, no hay coincidencia en esa muestra, y solo coincidirá donde un elemento de matriz específico tenía ambos elementos.

Ahora tenemos $elemMatch explicado, aquí está su consulta simplificada:

db.collection.find({ "tracks.artist": { "$in": arr } })

Mucho más simple, y funciona mirando todos los miembros de la matriz por un solo campo y regresando donde cualquier elemento en el documento contiene al menos uno de esos resultados posibles.

Pero no lo que estás preguntando, así que sigue con tu pregunta. Si lee esa última declaración, debería darse cuenta de que $in es en realidad un $or condición. Es solo una forma abreviada para preguntar "o" sobre el mismo elemento en el documento.

Con eso en mente, en el corazón de lo que está pidiendo es un "y" operación donde están contenidos los "tres" valores. Suponiendo que solo estaba enviando "tres" elementos en la prueba, entonces podría usar una forma de $and que está en la forma abreviada de $all :

db.collection.find({ "tracks.artist": { "$all": arr } })

Eso solo le devolvería los documentos que tenían el elemento dentro de los miembros de esa matriz que coincidía con "todos" los elementos especificados en la condición de prueba. Eso bien puede ser lo que desea, pero existe el caso en el que, por supuesto, desea especificar una lista de, digamos, "cuatro o más" artistas para probar y solo quiere "tres" o un número menor de eso, en cuyo caso an $all el operador es demasiado conciso.

Pero hay una forma lógica de resolver esto, solo se necesita un poco más de procesamiento con operadores que no están disponibles para consultas básicas pero que están disponibles para marco de agregación :

var arr = ["A","B","C","D"];     // List for testing

db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Test the array conditions
    { "$project": {
        "user": 1,
        "tracks": 1,                         // any fields you want to keep
        "matched": {
            "$gte": [
                 { "$size": {
                     "$setIntersection": [
                         { "$map": {
                             "input": "$tracks",
                             "as": "t",
                             "in": { "$$t.artist" }
                         }},
                         arr
                     ]
                 }},
                 3
             ]
        }
    }},

    // Filter out anything that did not match
    { "$match": { "matched": true } }
])

La primera etapa implementa una consulta estándar $match condición para filtrar los documentos a solo aquellos que "probablemente" coincidan con las condiciones. El caso lógico aquí es usar $in como antes, encontrará aquellos documentos donde al menos uno de los elementos presentes en su matriz de "prueba" está presente dentro de al menos uno de los campos de miembro en la propia matriz de documentos.

La siguiente cláusula es algo que idealmente debería construir en código en lo que respecta a la "longitud" de la matriz. La idea aquí es donde desea al menos "tres" coincidencias, entonces la matriz que está probando en el documento debe tener al menos "tres" elementos para cumplir con eso, por lo que no tiene sentido recuperar documentos con "dos" o menos elementos de matriz ya que nunca pueden coincidir con "tres".

Dado que todas las consultas de MongoDB son esencialmente solo una representación de una estructura de datos, hace que esto sea muy fácil de construir. es decir, para JavaScript:

var matchCount = 3;    // how many matches we want

var match1 = { "$match": { "tracks.artist": { "$in": arr } } };

match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };

La lógica es que la "notación de puntos" se forma con $exists prueba la presencia de un elemento en el índice especificado ( n-1 ), y debe estar allí para que la matriz tenga al menos esa longitud.

El resto de la reducción utiliza idealmente el $setIntersection método para devolver los elementos coincidentes entre la matriz real y la matriz probada. Dado que la matriz en el documento no coincide con la estructura de la "matriz de prueba", debe transformarse a través de $map operación que está configurada para devolver solo el campo "artista" de cada elemento de la matriz.

A medida que se realiza la "intersección" de esas dos matrices, finalmente se prueba para $size de esa lista resultante de elementos comunes donde se aplica la prueba para ver que "al menos tres" de esos elementos fueron encontrados en común.

Finalmente, simplemente "filtra" cualquier cosa que no sea cierta usando un $match condición.

Idealmente, está utilizando MongoDB 2.6 o superior para tener esos operadores disponibles. Para las versiones anteriores de 2.2.x y 2.4.x, todavía es posible, pero solo requiere un poco más de trabajo y sobrecarga de procesamiento:

db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Unwind the document array
    { "$unwind": "$tracks" },

    // Filter the content
    { "$match": { "tracks.artist": { "$in": arr } }},

    // Group for distinct values
    { "$group": {
        "_id": { 
           "_id": "$_id",
           "artist": "$tracks.artist"
        }
    }},

    // Make arrays with length
    { "$group": {
        "_id": "$_id._id",
        "artist": { "$push": "$_id.artist" },
        "length": { "$sum": 1 }
    }},

    // Filter out the sizes
    { "$match": { "length": { "$gte": 3 } }}
])