2017-08-10 108 views
1

测试数据:聚合子文档忽略键顺序

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 } 

回答

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返回到数组中,然后再转换回$arrayToObject

但它得到的结果是:

/* 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 
    } 
], 
+0

第一个查询正常工作!幸运的是,数据库不是那么大,查询运行时没问题。然而,“title”是什么:{“$ push”:“$ title”}'呢? – user3648650

+1

@ user3648650最好阅读手册并理解内容。错过了['$ push']的链接(https://docs.mongodb.com/manual/reference/operator/aggregation/push/),当然还有['$ unwind'](https://docs.mongodb.com /手动/参考/运营商/聚集/放松/)。尝试运行“一次一个阶段”的聚合,即首先仅仅是“$ project”。然后'$ project'和'$ unwind',依此类推,直到你添加了列出的所有阶段。那么你应该清楚地看到发生了什么以及为什么。 –