En primer lugar, mostremos sus datos de manera que las personas puedan usarlos y producir el resultado deseado:
{ 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' } ],
}
Tenga en cuenta que el "when"
de hecho, las fechas son diferentes, por lo que habrá un $max
valor y no son lo mismo. Ahora podemos repasar los casos
Caso 1:obtenga el "singular" $max
valor
El caso básico aquí es usar $arrayElemAt
y $indexOfArray
operadores para devolver la coincidencia $max
valor:
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$arrayElemAt": [
"$change",
{ "$indexOfArray": [
"$change.when",
{ "$max": "$change.when" }
]}
]
}
}}
])
Devoluciones:
{
"_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"
}
}
Básicamente el "$max": "$change.when"
devuelve el valor que es el "máximo" dentro de esa matriz de valores. Luego, encuentra el "índice" coincidente de esa matriz de valores a través de $indexOfArray
que devuelve el primero índice coincidente encontrado. Esa posición de "índice" (en realidad solo de una matriz de "when"
valores transpuestos en el mismo orden) se usa con $arrayElemAt
para extraer el "objeto completo" del "change"
matriz en la posición de índice especificada.
Caso 2:devolver el "múltiple" $max
entradas
Más o menos lo mismo con $max
, excepto que esta vez $filter
para devolver el múltiplo "posible" valores que coinciden con $max
valor:
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$filter": {
"input": "$change",
"cond": {
"$eq": [ "$$this.when", { "$max": "$change.when" } ]
}
}
}
}}
])
Devoluciones:
{
"_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"
}
]
}
Entonces el $max
es, por supuesto, lo mismo, pero esta vez el valor singular devuelto por ese operador se usa en un $eq
comparación dentro de $filter
. Esto inspecciona cada elemento de la matriz y mira el actual "when"
valor ( "$$this.when"
). Donde "igual" luego se devuelve el elemento.
Básicamente lo mismo que el primer enfoque pero con la excepción de que $filter
permite "múltiples" elementos a devolver. Por lo tanto todo con el mismo $max
valor.
Caso 3 - Ordenar previamente el contenido de la matriz.
Ahora puede notar que en los datos de ejemplo que incluí (adaptados de los suyos pero con una fecha "máxima" real), el valor "máximo" es de hecho el último valor en la matriz. Esto puede suceder naturalmente como resultado de que $push
(por defecto) "añade" hasta el final del contenido de la matriz existente. Así que "más nuevo" las entradas tenderán a estar al final de la matriz.
Esto, por supuesto, es el predeterminado comportamiento, pero hay buenas razones por las que "puede" quiero cambiar eso. En resumen lo mejor manera de obtener el "más reciente" la entrada de la matriz es, de hecho, devolver el primer elemento de la matriz.
Todo lo que necesita hacer es asegurarse de que el "más reciente" en realidad se agrega primero en lugar de último . Hay dos enfoques:
-
Utilice
$position
para "preestablecer" los elementos de la matriz: Este es un modificador simple para$push
utilizando el0
posición para agregar siempre al frente :db.items.updateOne( { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") }, { "$push": { "change": { "$each": [{ "version": 3, "who": "ATL User", "when": new Date(), "what": "Another change" }], "$position": 0 } }} )
Esto cambiaría el documento a:
{ "_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" } ] }
Tenga en cuenta que esto requeriría que realmente vaya e "invierta" todos los elementos de su matriz de antemano para que el "más nuevo" ya esté al frente para que se mantenga el orden. Afortunadamente, esto está algo cubierto en el segundo enfoque...
-
Utilice
$sort
para modificar los documentos en orden en cada$push
: Y este es el otro modificador que en realidad "reordena" atómicamente en cada adición de un nuevo elemento. El uso normal es básicamente el mismo con cualquier elemento nuevo para$each
como arriba, o incluso simplemente una matriz "vacía" para aplicar el$sort
solo a datos existentes:db.items.updateOne( { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") }, { "$push": { "change": { "$each": [], "$sort": { "when": -1 } } }} )
Resultados en:
{ "_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" } ] }
Puede tomar un minuto comprender por qué
$push
para$sort
una matriz como esta, pero la intención general es cuando se pueden realizar modificaciones en una matriz que "alteran" una propiedad comoDate
valor que se está clasificando y usaría tal declaración para reflejar esos cambios. O simplemente agregue nuevos elementos con$sort
y deja que funcione.
Entonces, ¿por qué "almacenar" la matriz ordenada así? Como se mencionó anteriormente, desea el primero elemento como el "más reciente" , y luego la consulta a devolver que simplemente se convierte en:
db.items.find(
{
"subcategory": "Programming Languages",
"name": { "$exists": true }
},
{ "change": { "$slice": 1 } }
)
Devoluciones:
{
"_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"
}
]
}
Así que $slice
luego se puede usar solo para extraer elementos de la matriz mediante índices conocidos. Técnicamente puedes usar -1
allí para devolver el último elemento de la matriz de todos modos, pero el reordenamiento donde el más reciente es primero permite otras cosas como confirmar que la última modificación fue realizada por un determinado usuario y/u otras condiciones como una restricción de rango de fechas. es decir:
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 aquí que algo como "change.-1.when"
es una declaración ilegal, que es básicamente la razón por la que reordenamos la matriz para que pueda usar la legal 0
para primero en lugar de -1
para último .
Conclusión
Por lo tanto, hay varias cosas diferentes que puede hacer, ya sea utilizando el enfoque de agregación para filtrar el contenido de la matriz o mediante formularios de consulta estándar después de realizar alguna modificación en la forma en que se almacenan los datos. Cuál usar depende de sus propias circunstancias, pero debe tener en cuenta que cualquiera de los formularios de consulta estándar se ejecutará notablemente más rápido que cualquier manipulación a través del marco de agregación o cualquier operador calculado.