sql >> Base de Datos >  >> NoSQL >> MongoDB

Condiciones de coincidencia y fecha más reciente de la matriz

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".