测试数据:
db.moretest.insert(
[
{ "title" : { "a" : 1, "b" : 2 } },
{ "title" : { "a" : 1, "b" : 2 } },
{ "title" : { "b" : 2, "a" : 1 } },
{ "title" : { "foo" : 42, "a" : 1 } },
]
)
我想计算一个键在“标题”中出现的频率,忽略顺序。例如。 { "a" : 1, "b" : 2 }
和{ "b" : 2, "a" : 1 }
应该被视为相同。
但是,此查询不会产生所需的结果:
db.moretest.aggregate(
[
{ $group: { "_id": "$title", "count": { $sum: 1 } } }
]
);
结果
{ "_id" : { "foo" : 42, "a" : 1 }, "count" : 1 }
{ "_id" : { "b" : 2, "a" : 1 }, "count" : 1 }
{ "_id" : { "a" : 1, "b" : 2 }, "count" : 2 }
但我想要的是以下内容:
{ "_id" : { "foo" : 42, "a" : 1 }, "count" : 1 }
{ "_id" : { "a" : 1, "b" : 2 }, "count" : 3 }
答案 0 :(得分:1)
MongoDB实际上确实考虑了对象键中的这种不同顺序来表示"唯一性"。对于一般"查询"目的,这就是"dot notation"表单存在的原因,指定path to keys at "depth"而不是完全匹配格式。
出于同样的原因,这也适用于聚合。如果你想以任何顺序组合,那么你实际需要强迫订单"保持一致。
这在现代版since MongoDB 3.4.4中完成:
db.moretest.aggregate([
{ "$project": {
"title": { "$objectToArray": "$title" },
}},
{ "$unwind": "$title" },
{ "$sort": { "_id": 1, "title.k": 1 } },
{ "$group": {
"_id": "$_id",
"title": { "$push": "$title" }
}},
{ "$group": {
"_id": { "$arrayToObject": "$title" },
"count": { "$sum": 1 }
}}
])
可以使用$objectToArray
来转换"键"进入"数组"然后可以"排序"。问题是,为了做到这一点,您仍然需要$unwind
数组元素并应用$sort
管道阶段然后$group
返回到数组中,然后再转换回{{3} }。
但确实得到了结果:
/* 1 */
{
"_id" : {
"a" : 1.0,
"b" : 2.0
},
"count" : 3.0
}
/* 2 */
{
"_id" : {
"a" : 1.0,
"foo" : 42.0
},
"count" : 1.0
}
即使不是很有效率。因此,能够对阵列进行排序会更好。
你"可以"通过测试"特定键"来交替决定呈现"title"
的方式,尽管是以一种非常黑客的方式:
db.moretest.aggregate([
{ "$group": {
"_id": {
"$cond": {
"if": { "$ifNull": [ "$title.b", false ] },
"then": { "a": "$title.a", "b": "$title.b" },
"else": "$title"
}
},
"count": { "$sum": 1 }
}}
])
这是相同的,当然实际上会重新排序"任何不符合提供条件的物体的钥匙。然而,它需要预先知道目标对象中的键实际上是为了提供条件。但如果您的实际用例支持实用,那么它可能是一个可行的选择。
对于其他版本并且更有效(即使依赖JavaScript解释来执行此操作)正在使用.mapReduce()
:
db.moretest.mapReduce(
function() {
emit(
Object.keys(this.title).sort()
.reduce((acc,curr) => Object.assign(acc,{ [curr]: this.title[curr] }), {}),
1
);
},
function(key,values) { return Array.sum(values) },
{ "out": { "inline": 1 } }
)
这或多或少都是相同的,但它有自己的结果集格式:
"results" : [
{
"_id" : {
"a" : 1.0,
"b" : 2.0
},
"value" : 3.0
},
{
"_id" : {
"a" : 1.0,
"foo" : 42.0
},
"value" : 1.0
}
],