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:
-
Utilizza
$position
per "anteporre" gli elementi dell'array: Questo è un semplice modificatore di$push
utilizzando il0
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...
-
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 unaDate
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.