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

Encontrar dos elementos en una matriz de documentos que aparecen en un orden dado

Si desea ese tipo de restricción en la consulta, básicamente tiene dos opciones según lo que admita su versión de MongoDB:

MongoDB 3.6

Preferiblemente, usaría $expr "además" a cualquier condición de consulta normal para seleccionar documentos válidos:

var A = 10, B = 40;

Model.find({
  "subDocs.value": { "$all": [A, B] },
  "$expr": {
    "$lt": [
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$indexOfArray": [ "$subDocs.value", A ]}
      ]},
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$indexOfArray": [ "$subDocs.value", B ]}  
      ]}
    ]
  }
})

O haciendo coincidir el "último" ocurrencia:

Model.find({
  "subDocs.value": { "$all": [A, B] },
  "$expr": {
    "$lt": [
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$subtract": [
          { "$subtract": [{ "$size": "$subDocs.value" }, 1 ] },
          { "$indexOfArray": [ { "$reverseArray": "$subDocs.value" }, A ] }
        ]}
      ]},
      { "$arrayElemAt": [
        "$subDocs.index",
        { "$subtract": [
          { "$subtract": [{ "$size": "$subDocs.value" }, 1 ] },
          { "$indexOfArray": [ { "$reverseArray": "$subDocs.value" }, B ] }
        ]}
      ]}
    ]
  }
})

Versiones anteriores

Lo mismo, pero sin operadores nativos, debe usar la evaluación de JavaScript de $where :

var A = 10, B = 40;

Model.find({
  "subDocs.value": { "$all": [A, B] },
  "$where": `this.subDocs.find( e => e.value === ${A}).index
      < this.subDocs.find( e => e.value === ${B}).index`
})

O haciendo coincidir el "último" ocurrencia:

Model.find({
  "subDocs.value": { "$all": [10,40] },
  "$where": `let arr = this.subDocs.reverse();
      return arr.find( e => e.value === ${A}).index
        > arr.find( e => e.value === ${B}).index`
})

Si necesita eso en una canalización de agregación, entonces usaría $redact y una lógica similar al primer ejemplo en su lugar:

var A = 10, B = 40;

Model.aggregate([
  { "$match": { "subDocs.value": { "$all": [A, B] } } },
  { "$redact": {
    "$cond": {
      "if": {
        "$lt": [
          { "$arrayElemAt": [
            "$subDocs.index",
            { "$indexOfArray": [ "$subDocs.value", A ]}
          ]},
          { "$arrayElemAt": [
            "$subDocs.index",
            { "$indexOfArray": [ "$subDocs.value", B ]}  
          ]}
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

Baste decir que la "lógica de comparación" no es en realidad nativa de las "expresiones de operador de consulta", por lo que la única parte que "óptimamente" se puede aplicar a un índice usando $all operador de consulta en todos los casos. La lógica restante esencial en realidad se aplica "después" de que se evalúe la expresión principal y "además de" para que no se devuelvan resultados que no sean los que cumplen con la expresión con $expr o $where .

La lógica básica de cada uno es esencialmente extraer el valor del "index" propiedad del "primer" miembro de la matriz que realmente coincide con el valor respectivo en el "value" propiedad. Donde esto es "menor que", entonces la condición es true y esto cumple con el documento que se devuelve.

Por lo tanto, tenga en cuenta que la "evaluación calculada" coincide con la eficiencia de los operadores de consulta, y sin ser utilizada "en combinación" con otras condiciones de operadores de consulta que pueden acceder a un "índice", se iniciará un "análisis completo de la colección".

Pero el resultado general es ciertamente más eficiente que devolver todos los elementos coincidentes a la primera condición de consulta y luego rechazarlos en el cursor "después" de regresar de la base de datos.

Consulte también la documentación para $arrayElemAt , $indexOfArray , $lt y Array.find() para JavaScript