在MongoDB中,我的文档结构如下:
{
_id: "123456...", // an ObjectId
name: "foobar",
classification: {
class_1: 0.45,
class_2: 0.11,
class_3: 0.44
}
}
使用聚合管道,是否可以为我提供包含最高分类的对象?所以,鉴于上述情况,我希望得到这样的结果:
{
_id: "123456...", // an ObjectId
name: "foobar",
classification: "class_1"
}
我以为我可以使用$unwind
,但classification
属性不是数组。
对于它的价值:我知道classification
中总会有三个属性,因此可以对查询中的密钥进行硬编码。
答案 0 :(得分:1)
你应该在这里注意,所应用的每一种技术都基于"强制" "键/值"成对"阵列"比较和提取的格式。因此,要学习的真正教训是,您的文档"应该"实际上将它存储为"数组"代替。但是在技术上。
如果你有MongoDB 3.4,那么你可以使用$objectToArray
打开"键"进入一个数组,这样你就可以得到这个值:
db.collection.aggregate([
{ "$addFields": {
"classification": {
"$arrayElemAt": [
{ "$map": {
"input": {
"$filter": {
"input": { "$objectToArray": "$classification" },
"as": "c",
"cond": {
"$eq": [
"$$c.v",
{ "$max": {
"$map": {
"input": { "$objectToArray": "$classification" },
"as": "c",
"in": "$$c.v"
}
}}
]
}
}
},
"as": "c",
"in": "$$c.k",
}},
0
]
}
}}
])
否则只是在迭代光标时进行转换,如果你真的不需要它进行进一步的聚合。作为一个基本的JavaScript示例:
db.collection.find().map(d => Object.assign(
d,
{ classification: Object.keys(d.classification)
.filter(k => d.classification[k] === Math.max.apply(null,
Object.keys(d.classification).map(k => d.classification[k])
))[0]
}
));
如果您实际上正在聚合某些东西,那么这也是您使用mapReduce应用的基本逻辑。
两者都产生:
/* 1 */
{
"_id" : "123456...",
"name" : "foobar",
"classification" : "class_1"
}
关于"硬编码"你说的情况还可以。然后,您可以使用$switch
通过提供每个值来构建这样的内容:
db.collection.aggregate([
{ "$addFields": {
"classification": {
"$let": {
"vars": {
"max": {
"$max": [
"$classification.class_1",
"$classification.class_2",
"$classification.class_3"
]
}
},
"in": {
"$switch": {
"branches": [
{ "case": { "$eq": [ "$classification.class_1", "$$max" ] }, "then": "class_1" },
{ "case": { "$eq": [ "$classification.class_2", "$$max" ] }, "then": "class_2" },
{ "case": { "$eq": [ "$classification.class_3", "$$max" ] }, "then": "class_3" },
]
}
}
}
}
}}
])
然后实际上能够使用$max
更长时间地写出来,然后唯一真正的约束是MongoDB 3.2的$cond
的变化,它允许一组参数而不是以前作为"累加器的角色":
db.collection.aggregate([
{ "$addFields": {
"classification": {
"$let": {
"vars": {
"max": {
"$max": [
"$classification.class_1",
"$classification.class_2",
"$classification.class_3"
]
}
},
"in": {
"$cond": {
"if": { "$eq": [ "$classification.class_1", "$$max" ] },
"then": "class_1",
"else": {
"$cond": {
"if": { "$eq": [ "$classification.class_2", "$$max" ] },
"then": "class_2",
"else": "class_3"
}
}
}
}
}
}
}}
])
如果你真的"真的"然后你就可以强迫"强迫" "max"
通过单独的管道阶段,然后使用$max
和$map
再次使用$unwind
和$group
。这将使操作与MongoDB 2.6兼容:
db.collection.aggregate([
{ "$project": {
"name": 1,
"classification": 1,
"max": {
"$map": {
"input": [1,2,3],
"as": "e",
"in": {
"$cond": {
"if": { "$eq": [ "$$e", 1 ] },
"then": "$classification.class_1",
"else": {
"$cond": {
"if": { "$eq": [ "$$e", 2 ] },
"then": "$classification.class_2",
"else": "$classification.class_3"
}
}
}
}
}
}
}},
{ "$unwind": "$max" },
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"classification": { "$first": "$classification" },
"max": { "$max": "$max" }
}},
{ "$project": {
"name": 1,
"classification": {
"$cond": {
"if": { "$eq": [ "$classification.class_1", "$max" ] },
"then": "class_1",
"else": {
"$cond": {
"if": { "$eq": [ "$classification.class_2", "$max" ] },
"then": "class_2",
"else": "class_3"
}
}
}
}
}}
])
而且非常古老,那么我们可以$const
来自db.collection.aggregate([
{ "$project": {
"name": 1,
"classification": 1,
"temp": { "$const": [1,2,3] }
}},
{ "$unwind": "$temp" },
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"classification": { "$first": "$classification" },
"max": {
"$max": {
"$cond": [
{ "$eq": [ "$temp", 1 ] },
"$classification.class_1",
{ "$cond": [
{ "$eq": [ "$temp", 2 ] },
"$classification.class_2",
"$classification.class_3"
]}
]
}
}
}},
{ "$project": {
"name": 1,
"classification": {
"$cond": [
{ "$eq": [ "$max", "$classification.class_1" ] },
"class_1",
{ "$cond": [
{ "$eq": [ "$max", "$classification.class_2" ] },
"class_2",
"class_3"
]}
]
}
}}
])
,这是(现在仍然是)隐藏的"在现代版本中,未记录的运算符与$unwind
(在技术上别名)的函数相同,但也使用$literal
的替代语法作为"数组"由于存在聚合框架,因此三元运算与所有版本兼容:
{{1}}
但它当然是可能的,即使非常混乱。
答案 1 :(得分:1)
您可以使用$indexOfArray
运算符查找$max
中的classification
值,然后投射密钥。 $objectToArray
将classification
嵌入式文档转换为3.4.4版本中的键值对数组。
db.collection.aggregate([
{
"$addFields": {
"classification": {
"$let": {
"vars": {
"classificationkv": {
"$objectToArray": "$classification"
}
},
"in": {
"$let": {
"vars": {
"classificationmax": {
"$arrayElemAt": [
"$$classificationkv",
{
"$indexOfArray": [
"$$classificationkv.v",
{
"$max": "$$classificationkv.v"
}
]
}
]
}
},
"in": "$$classificationmax.k"
}
}
}
}
}
}
])
答案 2 :(得分:0)
最后,我选择了一个更简单的解决方案,但不像其他发布的那样通用。我用这个开关案例声明:
{'$project': {'_id': 1, 'name': 1,
'classification': {'$switch': {
'branches': [
{'case': {'$and': [{'$gt': ['$classification.class_1', '$classification.class_2']},
{'$gt': ['$classification.class_1', '$classification.class_3']}]},
'then': "class1"},
{'case': {'$and': [{'$gt': ['$classification.class_2', '$classification.class_1']},
{'$gt': ['$classification.class_2', '$classification.class_3']}]},
'then': "class_2"},
{'case': {'$and': [{'$gt': ['$classification.class_3', '$classification.class_1']},
{'$gt': ['$classification.class_3', '$classification.class_2']}]},
'then': "class_3"}],
'default': ''}}
}}
这对我有用,但其他答案可能是更好的选择,YMMV。