聚合积累内部对象

时间:2018-05-21 11:48:16

标签: javascript mongodb mongodb-query aggregation-framework

我是mongo聚合的新手,我需要帮助创建一个,

我有以下文档的集合作为示例:

{
    "_id" : ObjectId("5afc2f06e1da131c9802071e"),
    "_class" : "Traveler",
    "name" : "John Due",
    "startTimestamp" : 1526476550933,
    "endTimestamp" : 1526476554823,
    "source" : "istanbul",
    "cities" : [ 
        {
            "_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
            "name" : "Moscow",
            "timestamp" : 1526476550940,
            "timeSpent" : 3180
        },

        {
            "_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
            "name" : "Cairo",
            "timestamp" : 1625476550940,
            "timeSpent" : 318000,
        },
         {
            "_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
            "name" : "Moscow",
            "timestamp" : 15211276550940,
            "timeSpent" : 318011
        }


    ],
    "variables" : [ 
        {
            "_id" : "cd4318a83c9b-a8478d76bfd3e4b6-5967",
            "name" : "Customer Profile",
            "lastValue" : "",
            "values" : [],
            "additionalData" : {}
        }, 
        {
            "_id" : "366cb8c07996-c62c37a87a86d526-d3e7",
            "name" : "Target Telephony Queue",
            "lastValue" : "",
            "values" : [],
            "additionalData" : {}
        }, 
        {
            "_id" : "4ed84742da33-d70ba8a809b712f3-bdf4",
            "name" : "IMEI",
            "lastValue" : "",
            "values" : [],
            "additionalData" : {}
        }, 

        {
            "_id" : "c8103687c1c8-97d749e349d785c8-9154",
            "name" : "Budget",
            "defaultValue" : "",
            "lastValue" : "",
            "values" : [ 
                {
                    "value" : "3000",
                    "timestamp" : NumberLong(1526476550940),
                    "element" : "c8103687c1c8-97d749e349d785c8-9154"
                }
            ],
            "additionalData" : {}
        }
    ]
}

我需要一份结果文件,显示每个旅行者在集合中访问每个城市的次数,以及平均预算(预算是变量数组中的一个元素

所以生成的文档类似于:

{
     "_id" : ObjectId("5afc2f06e1da131c9802071e"),
     "_class" : "Traveler",
     "name" : "John Due",
     "startTimestamp" : 1526476550933,
     "endTimestamp" : 1526476554823,
     "source" : "istanbul",
    "cities" : [ 
        {
            "_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
            "name" : "Moscow",
            "visited":2
        },
        {
            "_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
            "name" : "Cairo",
            "visited":1
        }
    ],
    "variables" : [ 
        {
                "_id" : "c8103687c1c8-97d749e349d785c8-9154",
                "name" : "Budget",
                "defaultValue" : "",
                "lastValue" : "",
                "values" : [ 
                    {
                        "value" : "3000",
                    }
                ],
            }
    ],
}

感谢您的帮助

1 个答案:

答案 0 :(得分:2)

快速说明,您需要将"value"内的"values"字段更改为数字,因为它现在是一个字符串。但回答:

如果您可以从MongoDB 3.4访问$reduce,那么您实际上可以执行以下操作:

db.collection.aggregate([
  { "$addFields": {
     "cities": {
       "$reduce": {
         "input": "$cities",
         "initialValue": [],
         "in": {
           "$cond": {
             "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
             "then": {
               "$concatArrays": [
                 { "$filter": {
                   "input": "$$value",
                   "as": "v",
                   "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                 }},
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": {
                     "$add": [
                       { "$arrayElemAt": [
                         "$$value.visited",
                         { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                       ]},
                       1
                     ]
                   }
                 }]
               ]
             },
             "else": {
               "$concatArrays": [
                 "$$value",
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": 1
                 }]
               ]
             }
           }
         }
       }
     },
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "_id": "$$this._id",
           "name": "$$this.name",
           "defaultValue": "$$this.defaultValue",
           "lastValue": "$$this.lastValue",
           "value": { "$avg": "$$this.values.value" }
         }
       }
     }
  }}
])

如果你有MongoDB 3.6,你可以使用$mergeObjects清除它:

db.collection.aggregate([
  { "$addFields": {
     "cities": {
       "$reduce": {
         "input": "$cities",
         "initialValue": [],
         "in": {
           "$cond": {
             "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
             "then": {
               "$concatArrays": [
                 { "$filter": {
                   "input": "$$value",
                   "as": "v",
                   "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                 }},
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": {
                     "$add": [
                       { "$arrayElemAt": [
                         "$$value.visited",
                         { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                       ]},
                       1
                     ]
                   }
                 }]
               ]
             },
             "else": {
               "$concatArrays": [
                 "$$value",
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": 1
                 }]
               ]
             }
           }
         }
       }
     },
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "$mergeObjects": [
             "$$this",
             { "values": { "$avg": "$$this.values.value" } }
           ]
         }
       }
     }
  }}
])

但除了我们保留additionalData

之外,它或多或少都是一样的

在此之前回过头来,然后你总是$unwind "cities"积累:

db.collection.aggregate([
  { "$unwind": "$cities" },
  { "$group": {
     "_id": { 
       "_id": "$_id",
       "cities": {
         "_id": "$cities._id",
         "name": "$cities.name"
       }
     },
     "_class": { "$first": "$class" },
     "name": { "$first": "$name" },
     "startTimestamp": { "$first": "$startTimestamp" },
     "endTimestamp" : { "$first": "$endTimestamp" },
     "source" : { "$first": "$source" },
     "variables": { "$first": "$variables" },
     "visited": { "$sum": 1 }
  }},
  { "$group": {
     "_id": "$_id._id",
     "_class": { "$first": "$class" },
     "name": { "$first": "$name" },
     "startTimestamp": { "$first": "$startTimestamp" },
     "endTimestamp" : { "$first": "$endTimestamp" },
     "source" : { "$first": "$source" },
     "cities": {
       "$push": {
         "_id": "$_id.cities._id",
         "name": "$_id.cities.name",
         "visited": "$visited"
       }
     },
     "variables": { "$first": "$variables" },
  }},
  { "$addFields": {
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "_id": "$$this._id",
           "name": "$$this.name",
           "defaultValue": "$$this.defaultValue",
           "lastValue": "$$this.lastValue",
           "value": { "$avg": "$$this.values.value" }
         }
       }
     }
  }}
])

所有回报(几乎)都是一样的:

{
        "_id" : ObjectId("5afc2f06e1da131c9802071e"),
        "_class" : "Traveler",
        "name" : "John Due",
        "startTimestamp" : 1526476550933,
        "endTimestamp" : 1526476554823,
        "source" : "istanbul",
        "cities" : [
                {
                        "_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
                        "name" : "Cairo",
                        "visited" : 1
                },
                {
                        "_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
                        "name" : "Moscow",
                        "visited" : 2
                }
        ],
        "variables" : [
                {
                        "_id" : "c8103687c1c8-97d749e349d785c8-9154",
                        "name" : "Budget",
                        "defaultValue" : "",
                        "lastValue" : "",
                        "value" : 3000
                }
        ]
}

前两种形式当然是最理想的事情,因为它们只是在同一文档内“”“。

$reduce之类的运算符允许数组上的“累积”表达式,因此我们可以在此处使用它来保留“简化”数组,我们使用$indexOfArray测试唯一"_id"值以查看是否已存在匹配的累积项目。 -1的结果意味着它不存在。

为了构建“简化数组”,我们将"initialValue" []作为空数组,然后通过$concatArrays添加到它。所有这一过程都是通过“三元”$cond运算符来决定的,该运算符考虑"if"条件而"then"要么“加入”当前{$filter的输出。 1}}排除当前索引$$value条目,当然还有另一个表示单个对象的“数组”。

对于那个“对象”,我们再次使用$indexOfArray来实际获取匹配的索引,因为我们知道项“在那里”,并使用它从该条目中提取当前_id值通过$arrayElemAt$add来增加。

"visited"案例中,我们只需将“数组”添加为“对象”,其默认"else"值为"visited"。使用这两种情况有效地在数组中累积唯一值以输出。

在后一个版本中,我们只是$unwind数组并使用连续的$group阶段,以便首先“计算”唯一的内部条目,然后“重新构造数组”到类似的形式。

使用$unwind看起来要简单得多,但由于它实际上做的是为每个数组条目获取文档的副本,这实际上会给处理带来相当大的开销。在现代版本中,通常有数组运算符,这意味着除非您的意图是“跨文档累积”,否则不需要使用它。因此,如果您确实需要$group来自“内部”数组的键值,那么您实际需要使用它。

至于1,我们可以在此处再次使用$filter来获取匹配的"variables"条目。我们这样做是$map运算符的输入,它允许“重新整形”数组内容。我们主要想要这样你可以获取"Budget"的内容(一旦你将它全部数字化)并使用$avg运算符,它将“字段路径表示法”直接提供给数组值因为它实际上可以从这样的输入中返回结果。

这通常使得聚合管道(不包括“集合”运算符)的所有主要“数组运算符”都在单个管道阶段中进行。

另外,永远不要忘记,您只需要$match定期Query Operators作为任何聚合管道的“第一阶段”,以便只选择您需要的文档。理想情况下使用索引。

候补

Alternates正在处理客户端代码中的文档。通常不建议使用它,因为上面的所有方法都显示它们实际上“减少”从服务器返回的内容,通常是“服务器聚合”。

由于“基于文档”的性质,“可能”可能更大的结果集可能需要花费更多的时间使用"values"并且客户端处理可能是一种选择,但我认为它更有可能

下面是一个列表,演示如何将变换应用于游标流,因为返回的结果执行相同的操作。变换有三个演示版本,显示“完全”与上面相同的逻辑,使用$unwind积累方法的实现,以及lodash实现的“自然”积累:

Map