MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Encontrar dois elementos em uma matriz de documentos que aparecem em uma determinada ordem


Se você deseja esse tipo de restrição na consulta, basicamente tem duas opções, dependendo do suporte da sua versão do MongoDB:

MongoDB 3.6


Você usaria preferencialmente $expr "além" a quaisquer condições normais de consulta para selecionar 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 ]}  
      ]}
    ]
  }
})

Ou correspondendo ao "último" ocorrência:
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 ] }
        ]}
      ]}
    ]
  }
})

Versões anteriores


Mesma coisa, mas sem operadores nativos, você precisa usar a avaliação JavaScript de $onde :
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`
})

Ou correspondendo ao "último" ocorrência:
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`
})

Se você precisasse disso em um pipeline de agregação, usaria $redact e lógica semelhante ao primeiro exemplo:
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"
    }
  }}
])

Basta dizer que a "lógica de comparação" não é realmente nativa das próprias "expressões do operador de consulta", portanto, a única parte que "otimamente" pode ser aplicado a um índice usando o $all operador de consulta em todos os casos. A lógica restante essencial realmente se aplica "depois" que a expressão principal é avaliada e "além de" para que nenhum resultado seja retornado além daqueles que atendem à expressão com $expr ou $where .

A lógica básica de cada um é essencialmente extrair o valor do "index" propriedade do "primeiro" membro da matriz que realmente corresponde ao respectivo valor no "valor" propriedade. Onde for "menor que", então a condição é true e isso satisfaz o documento que está sendo devolvido.

Portanto, observe que a "avaliação calculada" corresponde à eficiência dos operadores de consulta e, sem ser usada "em combinação" com outras condições do operador de consulta que são capazes de acessar um "índice", uma "varredura de coleção completa" será iniciada.

Mas o resultado geral é certamente mais eficiente do que retornar todos os itens correspondentes à primeira condição de consulta e rejeitá-los no cursor "depois" de retornar do banco de dados.

Consulte também a documentação para $arrayElemAt , $indexOfArray , $lt e Array.find() para JavaScript