MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Recherche de deux éléments dans un tableau de documents qui apparaissent dans un ordre donné

Si vous voulez ce type de contrainte dans la requête, vous avez essentiellement deux options en fonction de ce que votre version de MongoDB prend en charge :

MongoDB 3.6

Vous utiliserez de préférence $expr "en plus" à toutes les conditions de requête normales pour sélectionner réellement des documents valides :

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 correspondant au "dernier" occurrence :

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 ] }
        ]}
      ]}
    ]
  }
})

Versions antérieures

Même chose, mais sans les opérateurs natifs, vous devez utiliser l'évaluation 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`
})

Ou correspondant au "dernier" occurrence :

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 vous en aviez besoin dans un pipeline d'agrégation, vous utiliseriez alors $redact et une logique similaire au premier exemple :

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"
    }
  }}
])

Il suffit de dire que la "logique de comparaison" n'est pas réellement native des "expressions d'opérateur de requête" elles-mêmes, donc la seule partie qui "de manière optimale" peut être appliqué à un index en utilisant le $all opérateur de requête dans tous les cas. La logique restante essentielle s'applique en fait "après" que l'expression principale est évaluée et "en plus de" afin qu'aucun résultat ne soit renvoyé autre que ceux répondant à l'expression avec soit $expr ou $where .

La logique de base de chacun est essentiellement d'extraire la valeur de l'"index" propriété du "premier" membre du tableau qui correspond réellement à la valeur respective dans le "value" propriété. Où c'est "inférieur à", alors la condition est true et cela satisfait le document renvoyé.

Notez donc que soit "l'évaluation calculée" correspond à l'efficacité des opérateurs de requête, et sans être utilisée "en combinaison" d'autres conditions d'opérateur de requête qui peuvent accéder à un "index", alors une "analyse complète de la collection" sera lancée.

Mais le résultat global est certainement plus efficace que de renvoyer tous les éléments correspondants à la première condition de requête, puis de les rejeter sur le curseur "après" son retour de la base de données.

Voir aussi la documentation pour $arrayElemAt , $indexOfArray , $lt et Array.find() pour JavaScript