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

Como posso diminuir os estágios de desenrolamento no pipeline de agregação para documentos aninhados?


Desde que seus dados tenham leituras exclusivas de sensores e tags por documento, que até o momento o que você apresentou parece, você simplesmente não precisa de $unwind de forma alguma.

Na verdade, tudo o que você realmente precisa é de um único $group :
db.endpoints.aggregate([
  // In reality you would $match to limit the selection of documents
  { "$match": { 
    "DateTime": { "$gte": new Date("2018-05-01"), "$lt": new Date("2018-06-01") }
  }},
  { "$group": {
    "_id": "$EndpointId",
    "FirstActivity" : { "$min" : "$DateTime" },
    "LastActivity" : { "$max" : "$DateTime" },
    "RequestCount": { "$sum": 1 },
    "TagCount": {
      "$sum": {
        "$size": { "$setUnion": ["$Tags.Uid",[]] }
      }
    },
    "SensorCount": {
      "$sum": {
        "$sum": {
          "$map": {
            "input": { "$setUnion": ["$Tags.Uid",[]] },
            "as": "tag",
            "in": {
              "$size": {
                "$reduce": {
                  "input": {
                    "$filter": {
                      "input": {
                        "$map": {
                          "input": "$Tags",
                          "in": {
                            "Uid": "$$this.Uid",
                            "Type": "$$this.Sensors.Type"
                          }
                        }
                      },
                      "cond": { "$eq": [ "$$this.Uid", "$$tag" ] }
                    }
                  },
                  "initialValue": [],
                  "in": { "$setUnion": [ "$$value", "$$this.Type" ] }
                }
              }
            }
          }
        }
      }
    }
  }}
])

Ou, se você realmente precisar acumular esses valores "únicos" de "Sensores" e "Tags" de diferentes documentos, ainda precisará de $unwind declarações para obter o agrupamento certo, mas nem de longe tanto quanto você tem atualmente:
db.endpoints.aggregate([
  // In reality you would $match to limit the selection of documents
  { "$match": { 
    "DateTime": { "$gte": new Date("2018-05-01"), "$lt": new Date("2018-06-01") }
  }},
  { "$unwind": "$Tags" },
  { "$unwind": "$Tags.Sensors" },
  { "$group": {
    "_id": {
      "EndpointId": "$EndpointId",
      "Uid": "$Tags.Uid",
      "Type": "$Tags.Sensors.Type"
    },
    "FirstActivity": { "$min": "$DateTime" },
    "LastActivity": { "$max": "$DateTime" },
    "RequestCount": { "$addToSet": "$_id" }
  }},
  { "$group": {
    "_id": {
      "EndpointId": "$_id.EndpointId",
      "Uid": "$_id.Uid",
    },
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "count": { "$sum": 1 },
    "RequestCount": { "$addToSet": "$RequestCount" }
  }},
  { "$group": {
    "_id": "$_id.EndpointId",
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "TagCount": { "$sum": 1 },
    "SensorCount": { "$sum": "$count" },
    "RequestCount": { "$addToSet": "$RequestCount" }
  }},
  { "$addFields": {
    "RequestCount": {
      "$size": {
        "$reduce": {
          "input": {
            "$reduce": {
              "input": "$RequestCount",
              "initialValue": [],
              "in": { "$setUnion": [ "$$value", "$$this" ] }
            }
          },
          "initialValue": [],
          "in": { "$setUnion": [ "$$value", "$$this" ] }
        }
      }
    }
  }}
],{ "allowDiskUse": true })

E no MongoDB 4.0 você pode usar $toString no ObjectId dentro de _id e simplesmente mescle as chaves exclusivas para manter o RequestCount usando $mergeObjects . Isso é mais limpo e um pouco mais escalável do que enviar conteúdo de matriz aninhada e achatá-lo
db.endpoints.aggregate([
  // In reality you would $match to limit the selection of documents
  { "$match": { 
    "DateTime": { "$gte": new Date("2018-05-01"), "$lt": new Date("2018-06-01") }
  }},
  { "$unwind": "$Tags" },
  { "$unwind": "$Tags.Sensors" },
  { "$group": {
    "_id": {
      "EndpointId": "$EndpointId",
      "Uid": "$Tags.Uid",
      "Type": "$Tags.Sensors.Type"
    },
    "FirstActivity": { "$min": "$DateTime" },
    "LastActivity": { "$max": "$DateTime" },
    "RequestCount": {
      "$mergeObjects": {
        "$arrayToObject": [[{ "k": { "$toString": "$_id" }, "v": 1 }]]
      }
    }
  }},
  { "$group": {
    "_id": {
      "EndpointId": "$_id.EndpointId",
      "Uid": "$_id.Uid",
    },
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "count": { "$sum": 1 },
    "RequestCount": { "$mergeObjects": "$RequestCount" }
  }},
  { "$group": {
    "_id": "$_id.EndpointId",
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "TagCount": { "$sum": 1 },
    "SensorCount": { "$sum": "$count" },
    "RequestCount": { "$mergeObjects": "$RequestCount" }
  }},
  { "$addFields": {
    "RequestCount": {
      "$size": {
        "$objectToArray": "$RequestCount"
      }
    }
  }}
],{ "allowDiskUse": true })

Qualquer forma retorna os mesmos dados, embora a ordem das chaves no resultado possa variar:
{
        "_id" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",
        "FirstActivity" : ISODate("2018-05-06T19:05:02.666Z"),
        "LastActivity" : ISODate("2018-05-06T19:05:02.666Z"),
        "RequestCount" : 2,
        "TagCount" : 4,
        "SensorCount" : 16
}

O resultado é obtido desses documentos de amostra que você forneceu originalmente como fonte de amostra na a pergunta original sobre o tópico :
{
    "_id" : ObjectId("5aef51dfaf42ea1b70d0c4db"),    
    "EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",    
    "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
    "Url" : "test",
    "Tags" : [ 
        {
            "Uid" : "C1:3D:CA:D4:45:11",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-95")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-59")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("11.029802536740132")
                }, 
                {
                    "Type" : 4,
                    "Value" : NumberDecimal("27.25")
                }, 
                {
                    "Type" : 6,
                    "Value" : NumberDecimal("2924")
                }
            ]
        },         
        {
            "Uid" : "C1:3D:CA:D4:45:11",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-95")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-59")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("11.413037961112279")
                }, 
                {
                    "Type" : 4,
                    "Value" : NumberDecimal("27.25")
                }, 
                {
                    "Type" : 6,
                    "Value" : NumberDecimal("2924")
                }
            ]
        },          
        {
            "Uid" : "E5:FA:2A:35:AF:DD",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-97")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-58")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("10.171658037099185")
                }
            ]
        }
    ]
}

/* 2 */
{
    "_id" : ObjectId("5aef51e0af42ea1b70d0c4dc"),    
    "EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",    
    "Url" : "test",
    "Tags" : [ 
        {
            "Uid" : "E2:02:00:18:DA:40",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:04.574Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-98")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-65")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("7.845424441900629")
                }, 
                {
                    "Type" : 4,
                    "Value" : NumberDecimal("0.0")
                }, 
                {
                    "Type" : 6,
                    "Value" : NumberDecimal("3012")
                }
            ]
        }, 
        {
            "Uid" : "12:3B:6A:1A:B7:F9",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:04.574Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-95")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-59")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("12.939770381907275")
                }
            ]
        }
    ]
}

A conclusão é que você pode usar o primeiro formulário fornecido aqui, que acumulará "dentro de cada documento" e, em seguida, "acumulará por ponto de extremidade" em um único estágio e é o mais ideal, ou você realmente precisa identificar coisas como o "Uid" nas tags ou no "Type" no sensor onde esses valores ocorrem mais de uma vez em qualquer combinação de documentos agrupados pelo terminal.

Seus dados de amostra fornecidos até o momento mostram apenas que esses valores são "únicos em cada documento", portanto, o primeiro formulário fornecido seria o mais ideal se esse for o caso de todos os dados restantes.

Caso não seja, então "desenrolar" as duas matrizes aninhadas para "agregar os detalhes entre os documentos" é a única maneira de abordar isso. Você pode limitar o intervalo de datas ou outros critérios, pois a maioria das "consultas" normalmente tem alguns limites e não funciona realmente nos dados "todos" da coleção, mas o fato principal é que as matrizes seriam "desenroladas" criando essencialmente uma cópia do documento para cada membro da matriz.

O ponto de otimização significa que você só precisa fazer isso "duas vezes", pois existem apenas duas matrizes. Fazendo sucessivas $group para $unwind para $group é sempre um sinal claro de que você está fazendo algo realmente errado. Depois de "desmontar algo", você só precisará "montá-lo novamente" uma vez . Em uma série de etapas graduadas, conforme demonstrado aqui, está a uma vez abordagem que otimiza.

Fora do escopo da sua pergunta ainda permanece:
  • Adicione outras restrições realistas à consulta para reduzir os documentos processados, talvez até fazê-lo em "lotes" e combinar resultados
  • Adicione o allowDiskUse opção para o pipeline para permitir que o armazenamento temporário seja usado. ( realmente demonstrado nos comandos )
  • Considere que "matrizes aninhadas" provavelmente não são o melhor método de armazenamento para a análise que você deseja fazer. É sempre mais eficiente quando você sabe que precisa $unwind simplesmente escrever os dados nesse formato "desenrolado" diretamente em uma coleção.