我是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",
}
],
}
],
}
感谢您的帮助
答案 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