El concepto básico aquí es que necesita el marco de agregación para aplicar condiciones para "filtrar" los elementos de la matriz a las condiciones. Dependiendo de la versión disponible, existen diferentes técnicas que se pueden aplicar.
En todos los casos este es el resultado:
{
"_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 y superior
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
]
}
}
}
}
}}
])
Esta es la forma más eficiente que aprovecha $replaceRoot
lo que nos permite declarar variables para usar en la estructura "reemplazada" usando $let
. La principal ventaja aquí es que esto requiere solo "dos" etapas de canalización.
Para hacer coincidir el contenido de la matriz, usa $filter
donde aplica el $eq
operación lógica para probar el valor de "sender"
. Cuando la condición coincide, solo se devuelven las entradas de matriz coincidentes.
Usando el mismo $filter
para que solo se consideren las entradas coincidentes del "remitente", queremos aplicar $max
sobre la lista "filtrada" a los valores en "datetime"
. El $max
]5
el valor es la fecha "más reciente" según las condiciones.
Queremos este valor para que luego podamos comparar los resultados devueltos de la matriz "filtrada" con esta "fecha máxima". Que es lo que sucede dentro del "in"
bloque de $let
donde las dos "variables" declaradas anteriormente para el contenido filtrado y "maxDate" se aplican nuevamente a $filter
para devolver lo que debería ser el único valor que cumpliera ambas condiciones teniendo también la "fecha más reciente".
Como solo quiere "un" resultado, usamos $arrayElemAt
para usar el valor en lugar de la matriz.
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
]
}
}}
])
Este es básicamente el mismo proceso que se describe, pero sin el $replaceRoot
etapa de canalización, debemos aplicar en dos $project
etapas La razón de esto es que necesitamos el "valor calculado" de "maxDate" para hacer ese final $filter
, y no está disponible para hacerlo en una declaración compuesta, por lo que en su lugar dividimos las canalizaciones. Esto tiene un pequeño impacto en el costo total de la operación.
En MongoDB 2.6 a 3.0 podemos usar la mayor parte de la técnica excepto $arrayElemAt
y acepte el resultado de la "matriz" con una sola entrada o ingrese un $unwind
etapa para tratar con lo que ahora debería ser una sola entrada.
Versiones anteriores de 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" }
}}
])
Si bien parece breve, esta es, con mucho, la operación más costosa. Aquí debe usar $unwind
para aplicar las condiciones a los elementos del arreglo. Este es un proceso muy costoso ya que produce una copia de cada documento para cada entrada de la matriz, y es esencialmente reemplazado por los operadores modernos que evitan esto en el caso de "filtrado".
El segundo $match
etapa aquí descarta cualquier elemento (ahora "documentos") que no coincidía con la condición de "remitente". Luego aplicamos un $sort
para poner la fecha "más reciente" en la parte superior de cada documento por el _id
, de ahí las dos claves de "clasificación".
Finalmente aplicamos $group
para simplemente referirse al documento original, usando $first
como acumulador para obtener el elemento que está "encima".