MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Trovare due elementi in una matrice di documenti che appaiono in un determinato ordine

Se vuoi quel tipo di vincolo nella query, in pratica hai due opzioni a seconda di ciò che supporta la tua versione di MongoDB:

MongoDB 3.6

È preferibile utilizzare $expr "in aggiunta" a qualsiasi normale condizione di query per selezionare effettivamente documenti validi:

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 corrispondendo a "ultimo" occorrenza:

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

Versioni precedenti

Stessa cosa, ma senza operatori nativi è necessario utilizzare la valutazione JavaScript di $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 corrispondendo a "ultimo" occorrenza:

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 ne avessi bisogno in una pipeline di aggregazione, dovresti utilizzare $redact e logica simile al primo esempio invece:

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

Basti dire che la "logica di confronto" non è effettivamente nativa delle "espressioni degli operatori di query" stesse, quindi l'unica parte che "in modo ottimale" può essere applicato a un indice utilizzando $all operatore di query in tutti i casi. La logica rimanente essenziale si applica effettivamente "dopo" che l'espressione principale è stata valutata e "in aggiunta a" in modo che non vengano restituiti risultati diversi da quelli che soddisfano l'espressione con $expr o $where .

La logica di base di ciascuno è essenzialmente estrarre il valore del "index" proprietà dal "primo" membro dell'array che corrisponde effettivamente al rispettivo valore nel "value" proprietà. Dove questo è "minore di", la condizione è true e questo soddisfa il documento da restituire.

Si noti quindi che la "valutazione calcolata" corrisponde all'efficienza degli operatori di query e, senza essere utilizzata "in combinazione" con altre condizioni dell'operatore di query che sono in grado di accedere a un "indice", verrà avviata una "scansione completa della raccolta".

Ma il risultato complessivo è sicuramente più efficiente rispetto a riportare tutti gli elementi corrispondenti alla prima condizione di query e quindi rifiutarli sul cursore "dopo" il ritorno dal database.

Vedi anche la documentazione per $arrayElemAt , $indexOfArray , $lt e Array.find() per JavaScript