Considere una colección de documentos que recopila puntajes como una matriz de documentos anidados, como este (estoy usando MongoDb 4.2):
{ "scores" : [ { "value" : 2 }, { "value" : 3 }, { "value" : 4 } ] }, { "scores" : [ { "value" : 8 }, { "value" : 9 }, { "value" : 10 } ] }, { "scores" : [ { "value" : 7 }, { "value" : 7 }, { "value" : 10 } ] }
Considere ahora el siguiente caso de uso: "Dame todos los documentos que contienen SOLAMENTE puntajes en el rango [X, Y]". Dicho de otra manera, filtre los documentos que tienen al menos un valor que NO está en el rango [XY]. Nota al margen: en los datos reales, las puntuaciones son números reales, por lo que hacer algunas consultas $in no ayudará aquí.
Consideremos tres rangos:
Como aún no experto en mongo, mi primer intento fue hacer esto:
db.test.find({ $and: [ { "scores.value": {$gte: 8}}, { "scores.value": {$lte: 10}} ] })
Entonces, hay algunas cosas extrañas aquí: los primeros resultados sugieren que mi consulta en realidad arroja puntajes con cualquiera de las condiciones satisfechas (algo así como $ o en lugar de $ y). Pero si eso fuera cierto, habría recuperado todos los registros en el segundo caso [0, 1] en lugar de 0. Obviamente, algo que estoy usando de manera incorrecta aquí, pero si alguien puede explicarme por qué, sería muy feliz. .
Siguiente consulta:
db.test.find({ "scores": { $elemMatch: { value: {$gte: 6.5, $lte: 6.8} } } })
Para ese, todo está bien y funciona según lo documentado/esperado, pero no responde a mi caso de uso original.
Entonces, además de comprender lo que sucedió con la primera consulta, ¿alguien sabe alguna forma de lograr lo que estoy tratando de hacer?
Tomando el ejemplo [8-10], como mencionó, puede escribir una consulta para encontrar todos los documentos "malos" donde al menos el puntaje no está en el rango [8-10]: ese es el propósito de elementMatch
.
El documento "malo" es (puntuación < 8 O puntuación > 10):
db.collection.find({ "scores": { $elemMatch: { $or: [ { value: { $lt: 8 }}, { value: { $gt: 10 }} ] } } })
Luego puede invertir la condición con un $not
para obtener el resultado que esperaba (rechazar documentos "malos"):
db.collection.find({ "scores": { $not: { $elemMatch: { $or: [ { value: { $lt: 8 }}, { value: { $gt: 10 }} ] } } } })
Comencemos por comprender qué salió mal en su primer intento.
Estabas usando la notación de puntos de Mongo, ¿qué hace esto?
Para especificar o acceder a un elemento de una matriz por la posición del índice de base cero
Básicamente evalúa todos los elementos de las matrices a la vez.
$and: [ { "scores.value": {$gte: 8}}, { "scores.value": {$lte: 10}} ]
Cada una de estas expresiones consulta la matriz completa, lo que significa que para satisfacer la consulta todo lo que necesita es un valor único que esté dentro del rango.
Lo que puede hacer es utilizar el operador $not y combinarlo con $elemMatch
para buscar elementos que excluir, así:
db.collection.find({ "scores": { $not: { $elemMatch: { $or: [ { value: { $lt: 8 } }, { value: { $gt: 10 } } ] } } } })