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

Condizioni di corrispondenza e ultima data dall'array

Il concetto di base qui è che è necessario il framework di aggregazione per applicare condizioni per "filtrare" gli elementi dell'array alle condizioni. A seconda della versione disponibile ci sono diverse tecniche che possono essere applicate.

In tutti i casi questo è il risultato:

{
    "_id" : ObjectId("593921425ccc8150f35e7664"),
    "user1" : 1,
    "user2" : 4,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-09T10:04:50Z"),
            "body" : "hiii 1"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7663"),
    "user1" : 1,
    "user2" : 3,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-10T10:04:50Z"),
            "body" : "hiii 2"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7662"),
    "user1" : 1,
    "user2" : 2,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-08T10:04:50Z"),
            "body" : "hiii 0"
    }
}

MongoDB 3.4 e versioni successive

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$replaceRoot": {
    "newRoot": {
      "$let": {
        "vars": {
          "messages": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "maxDate": {
            "$max": {
              "$map": {
                "input": {
                  "$filter": {
                    "input": "$messages",
                    "as": "m",
                    "cond": { "$eq": [ "$$m.sender", 1 ] }
                  }
                },
                "as": "m",
                "in": "$$m.datetime"
              }
            }
          }
        },
        "in": {
          "_id": "$_id",
          "user1": "$user1",
          "user2": "$user2",
          "messages": {
            "$arrayElemAt": [
              { "$filter": {
                "input": "$$messages",
                "as": "m",
                "cond": { "$eq": [ "$$m.datetime", "$$maxDate" ] }
              }},
              0
            ]
          }    
        }
      }
    }
  }}
])

Questo è il modo più efficiente che sfrutta $replaceRoot che ci consente di dichiarare le variabili da utilizzare nella struttura "sostituita" utilizzando $let . Il vantaggio principale qui è che ciò richiede solo "due" fasi della pipeline.

Per abbinare il contenuto dell'array, utilizzi $filter dove applichi $eq operazione logica per testare il valore di "sender" . Se la condizione corrisponde, vengono restituite solo le voci dell'array corrispondenti.

Utilizzando lo stesso $filter in modo che vengano considerate solo le voci "mittente" corrispondenti, vogliamo quindi applicare $max sopra l'elenco "filtrato" ai valori in "datetime" . Il $max ]5 value è la data "ultima" in base alle condizioni.

Vogliamo questo valore in modo da poter confrontare in seguito i risultati restituiti dall'array "filtrato" con questo "maxDate". Che è ciò che accade all'interno di "in" blocco di $let dove le due "variabili" dichiarate in precedenza per il contenuto filtrato e "maxDate" vengono nuovamente applicate a $filter per restituire quello che dovrebbe essere l'unico valore che soddisfa entrambe le condizioni avendo anche la "data più recente".

Poiché desideri solo "un" risultato, utilizziamo $arrayElemAt per utilizzare il valore anziché l'array.

MongoDB 3.2

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$filter": {
        "input": "$messages",
        "as": "m",
        "cond": { "$eq": [ "$$m.sender", 1 ] }
      }
    },
    "maxDate": {
      "$max": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "as": "m",
          "in": "$$m.datetime"
        }
      }
    }         
  }},
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$arrayElemAt":[
       { "$filter": {
         "input": "$messages",
          "as": "m",
          "cond": { "$eq": [ "$$m.datetime", "$maxDate" ] }
       }},
       0
      ]
    }
  }}
])

Questo è fondamentalmente lo stesso processo descritto, ma senza $replaceRoot fase della pipeline, dobbiamo applicare in due $project fasi. La ragione di ciò è che abbiamo bisogno del "valore calcolato" da "maxDate" per farlo $filter , e non è possibile farlo in un'istruzione composta, quindi dividiamo invece le pipeline. Ciò ha un piccolo impatto sul costo complessivo dell'operazione.

In MongoDB da 2.6 a 3.0 possiamo usare la maggior parte della tecnica qui ad eccezione di $arrayElemAt e accettare il risultato "array" con una singola voce o inserire un $unwind fase per affrontare quella che ora dovrebbe essere un'unica voce.

Versioni precedenti di MongoDB

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$unwind": "$messages" },
  { "$match": { "messages.sender": 1 } },
  { "$sort": { "_id": 1, "messages.datetime": -1 } },
  { "$group": {
    "_id": "$_id",
    "user1": { "$first": "$user1" },
    "user2": { "$first": "$user2" },
    "messages": { "$first": "$messages" }
  }}
])

Anche se sembra breve, questa è di gran lunga l'operazione più costosa. Qui devi utilizzare $unwind per applicare le condizioni agli elementi dell'array. Questo è un processo molto costoso in quanto produce una copia di ogni documento per ogni voce dell'array, ed è sostanzialmente sostituito dai moderni operatori che lo evitano in caso di "filtraggio".

Il secondo $match stage qui scarta tutti gli elementi (ora "documenti") che non corrispondono alla condizione "mittente". Quindi applichiamo un $sort per mettere la data "ultima" in cima a ogni documento con il _id , da qui le due chiavi di "ordinamento".

Infine applichiamo $group per fare riferimento solo al documento originale, utilizzando $first come accumulatore per ottenere l'elemento che è "in cima".