MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Condições de correspondência e data mais recente da matriz


O conceito básico aqui é que você precisa da estrutura de agregação para aplicar condições para "filtrar" os elementos da matriz para as condições. Dependendo da versão disponível, existem diferentes técnicas que podem ser aplicadas.

Em todos os casos, este é o 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 e 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
            ]
          }    
        }
      }
    }
  }}
])

Essa é a maneira mais eficiente que aproveita $replaceRoot que nos permite declarar variáveis ​​para usar na estrutura "substituída" usando $let . A principal vantagem aqui é que isso requer apenas "dois" estágios de pipeline.

Para corresponder ao conteúdo da matriz, você usa $filter onde você aplica o $eq operação lógica para testar o valor de "sender" . Onde a condição corresponde, apenas as entradas de matriz correspondentes são retornadas.

Usando o mesmo $filter para que apenas as entradas correspondentes do "remetente" sejam consideradas, queremos aplicar $max sobre a lista "filtrada" para os valores em "datetime" . O $max ]5 value é a data "mais recente" pelas condições.

Queremos esse valor para que possamos comparar posteriormente os resultados retornados do array "filtrado" com este "maxDate". Que é o que acontece dentro do "in" bloco de $let onde as duas "variáveis" declaradas anteriormente para o conteúdo filtrado e a "maxDate" são novamente aplicadas a $filtro para retornar o que deveria ser o único valor que atendeu as duas condições tendo também a "última data".

Como você deseja apenas "um" resultado, usamos $arrayElemAt para usar o valor em vez da 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 é basicamente o mesmo processo descrito, mas sem o $ replaceRoot estágio de pipeline, precisamos nos inscrever em dois $project estágios. A razão para isso é que precisamos do "valor calculado" de "maxDate" para fazer isso $filtro , e não está disponível em uma instrução composta, então dividimos os pipelines. Isso tem um pequeno impacto no custo total da operação.

No MongoDB 2.6 a 3.0, podemos usar a maior parte da técnica aqui, exceto $arrayElemAt e aceite o resultado "array" com uma única entrada ou coloque um $unwind etapa para lidar com o que agora deveria ser uma única entrada.

Versões anteriores do 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" }
  }}
])

Embora pareça breve, esta é de longe a operação mais cara. Aqui você deve usar $unwind para aplicar as condições aos elementos da matriz. Este é um processo muito caro, pois produz uma cópia de cada documento para cada entrada do array, e é essencialmente substituído pelos operadores modernos que evitam isso no caso de "filtragem".

O segundo $match stage aqui descarta quaisquer elementos (agora "documents") que não correspondam à condição "remetente". Em seguida, aplicamos um $sort para colocar a data "mais recente" no topo de cada documento pelo _id , daí as duas chaves de "classificação".

Por fim, aplicamos $group para se referir apenas ao documento original, usando $first como o acumulador para obter o elemento que está "no topo".