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

Documento di ritorno con Max Sub Document

Prima di tutto, mostriamo i tuoi dati in modo che le persone possano usarli e produrre il risultato desiderato:

{ value: 1,
  _id: ObjectId('5cb9ea0c75c61525e0176f96'),
  name: 'Test',
  category: 'Development',
  subcategory: 'Programming Languages',
  status: 'Supported',
  description: 'Test',
  change:
   [ { version: 1,
       who: 'ATL User',
       when: new Date('2019-04-19T15:30:39.912Z'),
       what: 'Item Creation' },
     { version: 2,
       who: 'ATL Other User',
       when: new Date('2019-04-19T15:31:39.912Z'),
       what: 'Name Change' } ],
}

Nota che il "when" le date sono infatti diverse quindi ci sarà un $max valore e non sono semplicemente la stessa cosa. Ora possiamo esaminare i casi

Caso 1 - Recupera il "singolare" $max valore

Il caso di base qui è usare $arrayElemAt e $indexOfArray operatori per restituire il corrispondente $max valore:

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$arrayElemAt": [
        "$change",
        { "$indexOfArray": [
          "$change.when",
          { "$max": "$change.when" }
        ]}
      ]
    }
  }}
])

Resi:

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : {
                "version" : 2,
                "who" : "ATL Other User",
                "when" : ISODate("2019-04-19T15:31:39.912Z"),
                "what" : "Name Change"
        }
}

Fondamentalmente il "$max": "$change.when" restituisce il valore che è il "massimo" dall'interno di quella matrice di valori. Quindi trovi l'"indice" corrispondente di quella matrice di valori tramite $indexOfArray che restituisce il primo indice corrispondente trovato. Quella posizione di "indice" (da in realtà solo un array di "when" valori trasposti nello stesso ordine) viene quindi utilizzato con $arrayElemAt per estrarre "l'intero oggetto" dal "change" array nella posizione dell'indice specificata.

Caso 2 - Restituisci il "multiplo" $max voci

Praticamente la stessa cosa con $max , tranne questa volta $filter per restituire il multiplo "possibile" valori corrispondenti a $max valore:

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$filter": {
        "input": "$change",
        "cond": {
          "$eq": [ "$$this.when", { "$max": "$change.when" } ]
        }
      }       
    }
  }}
])

Resi:

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
}

Quindi $max è ovviamente lo stesso, ma questa volta il valore singolare restituito da quell'operatore viene utilizzato in un $eq confronto all'interno di $filter . Questo ispeziona ogni elemento dell'array e guarda il corrente "when" valore ( "$$this.when" ). Dove "uguale" quindi viene restituito l'elemento.

Fondamentalmente lo stesso del primo approccio ma con l'eccezione che $filter consente "multiplo" elementi da restituire. Quindi tutto con lo stesso $max valore.

Caso 3:preordina il contenuto dell'array.

Ora potresti notare che nei dati di esempio che ho incluso (adattati dai tuoi ma con una data "max" effettiva) il valore "max" è in effetti l'ultimo valore nella matrice. Ciò può accadere naturalmente perché $push (per impostazione predefinita) "apend" alla fine del contenuto dell'array esistente. Quindi "più recente" le voci tenderanno ad essere alla fine della matrice.

Questo ovviamente è il predefinito comportamento, ma ci sono buone ragioni per cui "puoi" voglio cambiarlo. In breve il migliore modo per ottenere il "più recente" la voce dell'array deve infatti restituire il primo elemento dalla matrice.

Tutto quello che devi fare è assicurarti il ​​"più recente" viene effettivamente aggiunto prima anziché ultimo . Ci sono due approcci:

  1. Utilizza $position per "anteporre" gli elementi dell'array: Questo è un semplice modificatore di $push utilizzando il 0 posizione per aggiungere sempre al primario :

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [{
              "version": 3,
              "who": "ATL User",
              "when": new Date(),
              "what": "Another change"
            }],
            "$position": 0
          }
       }}
    )
    

    Questo cambierebbe il documento in:

    {
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                },
                {
                        "version" : 1,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-19T15:30:39.912Z"),
                        "what" : "Item Creation"
                },
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
    }
    

Nota che ciò richiederebbe di andare effettivamente a "invertire" tutti gli elementi dell'array in anticipo in modo che il "più nuovo" fosse già in primo piano, quindi l'ordine è stato mantenuto. Per fortuna questo è in qualche modo trattato nel secondo approccio...

  1. Utilizza $sort per modificare i documenti in ordine su ogni $push : E questo è l'altro modificatore che in realtà "riordina" atomicamente ogni nuovo elemento aggiunto. L'utilizzo normale è sostanzialmente lo stesso con tutti i nuovi elementi in $each come sopra, o anche solo un array "vuoto" per applicare il $sort solo ai dati esistenti:

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [],
            "$sort": { "when": -1 } 
          }
       }}
    )
    

    Risultati in:

    {
            "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
            "value" : 1,
            "name" : "Test",
            "category" : "Development",
            "subcategory" : "Programming Languages",
            "status" : "Supported",
            "description" : "Test",
            "change" : [
                    {
                            "version" : 3,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-20T02:40:30.024Z"),
                            "what" : "Another change"
                    },
                    {
                            "version" : 2,
                            "who" : "ATL Other User",
                            "when" : ISODate("2019-04-19T15:31:39.912Z"),
                            "what" : "Name Change"
                    },
                    {
                            "version" : 1,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-19T15:30:39.912Z"),
                            "what" : "Item Creation"
                    }
            ]
    }
    

    Potrebbe volerci un minuto per capire perché dovresti $push per $sort un array come questo, ma l'intento generale è quando è possibile apportare modifiche a un array che "alterano" una proprietà come una Date valore viene ordinato e si utilizzerà tale istruzione per riflettere tali modifiche. Oppure aggiungi semplicemente nuovi elementi con $sort e lascia che funzioni.

Allora perché "negozio" l'array ordinato in questo modo? Come accennato in precedenza, vuoi il primo elemento come "più recente" , quindi la query da restituire diventa semplicemente:

db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true }
  },
  { "change": { "$slice": 1 } }
)

Resi:

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                }
        ]
}

Quindi $slice può quindi essere utilizzato solo per estrarre elementi dell'array da indici noti. Tecnicamente puoi semplicemente usare -1 lì per restituire l'ultimo elemento dell'array in ogni caso, ma il riordino in cui è prima il più recente consente altre cose come la conferma dell'ultima modifica apportata da un determinato utente e/o altre condizioni come un vincolo di intervallo di date. cioè:

db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true },
    "change.0.who": "ATL User",
    "change.0.when": { "$gt": new Date("2018-04-01") }
  },
  { "change": { "$slice": 1 } }
)

Notando qui che qualcosa come "change.-1.when" è una dichiarazione illegale, motivo per cui riordiniamo l'array in modo che tu possa usare il legale 0 per prima invece di -1 per ultimo .

Conclusione

Quindi ci sono diverse cose che puoi fare, usando l'approccio di aggregazione per filtrare il contenuto dell'array o tramite moduli di query standard dopo aver apportato alcune modifiche al modo in cui i dati vengono effettivamente archiviati. Quale usare dipende dalle tue circostanze, ma va notato che uno qualsiasi dei moduli di query standard funzionerà notevolmente più velocemente di qualsiasi manipolazione tramite il framework di aggregazione o qualsiasi operatore calcolato.