这通常应该是可能的,但实际上对于这种类型的操作,您可以通过使用聚合框架获得更多性能。目前在用 Mongoid 定义的类上没有“聚合”方法,但有一个 .collection 访问器,它公开了底层驱动程序对象。所以你可以从这里拨打.aggregate():
result = this.collection.aggregate([
# Include each field and an array for "type" in all documents
{ "$project" => {
"action" => 1,
"browser" => 1,
"country" => 1,
"type" => { "$const" => [ "action", "browser", "country" ] },
}},
# Unwind that "type" array
{ "$unwind" => "$type" },
# Group by "type" and the values of each field which matches
{ "$group" => {
"_id" => {
"type" => "$type",
"value" => {
"$cond" => [
{ "$eq" => [ "$type", "action" ] },
"$action",
{ "$cond" => [
{ "$eq" => [ "$type", "browser" ] },
"$browser",
"$country"
]}
]
}
},
"count" => { "$sum" => 1 }
}},
# Just in case all fields were not present in all documents
{ "$match" => { "_id.value" => { "$ne" => null } } },
# Group to a single document with each "type" as the keys
{ "$group" => {
"_id" => null,
"action" => {
"$addToSet" => {
"$cond" => [
{ "$eq" => [ "$_id.type", "action" ] },
{ "value" => "$_id.value", "count": "$count" },
null
]
}
},
"browser" => {
"$addToSet" => {
"$cond" => [
{ "$eq" => [ "$_id.type", "browser" ] },
{ "value" => "$_id.value", "count": "$count" },
null
]
}
},
"country" => {
"$addToSet" => {
"$cond" => [
{ "$eq" => [ "$_id.type", "country" ] },
{ "value" => "$_id.value", "count": "$count" },
null
]
}
}
}},
# Filter out any null values from the conditional allocation
{ "$project" => {
"action" => { "$setDifference" => [ "$action", [null] ] },
"browser" => { "$setDifference" => [ "$browser", [null] ] },
"country" => { "$setDifference" => [ "$country", [null] ] }
}}
])
这利用了更新的 MongoDB 2.6 引入的 $setDifference 运算符,以便从结果数组中过滤掉任何空值。以前的版本也可以做同样的事情,对处理的影响很小,只是更多的步骤:
result = this.collection.aggregate([
# Include each field and an array for "type" in all documents
{ "$project" => {
"action" => 1,
"browser" => 1,
"country" => 1,
"type" => { "$const" => [ "action", "browser", "country" ] },
}},
# Unwind that "type" array
{ "$unwind" => "$type" },
# Group by "type" and the values of each field which matches
{ "$group" => {
"_id" => {
"type" => "$type",
"value" => {
"$cond" => [
{ "$eq" => [ "$type", "action" ] },
"$action",
{ "$cond" => [
{ "$eq" => [ "$type", "browser" ] },
"$browser",
"$country"
]}
]
}
},
"count" => { "$sum" => 1 }
}},
# Just in case all fields were not present in all documents
{ "$match" => { "_id.value" => { "$ne" => null } } },
# Group to a single document with each "type" as the keys
{ "$group" => {
"_id" => null,
"action" => {
"$addToSet" => {
"$cond" => [
{ "$eq" => [ "$_id.type", "action" ] },
{ "value" => "$_id.value", "count": "$count" },
null
]
}
},
"browser" => {
"$addToSet" => {
"$cond" => [
{ "$eq" => [ "$_id.type", "browser" ] },
{ "value" => "$_id.value", "count": "$count" },
null
]
}
},
"country" => {
"$addToSet" => {
"$cond" => [
{ "$eq" => [ "$_id.type", "country" ] },
{ "value" => "$_id.value", "count": "$count" },
null
]
}
}
}},
# Filter out any null values from the conditional allocation
{ "$unwind": "$country" },
{ "$match": { "country": { "$ne": null } } },
{ "$group": {
"_id": "$_id",
"action": { "$first": "$action" },
"browser": { "$first": "$browser" },
"country": { "$push": "$country" }
}},
{ "$unwind": "$browser" },
{ "$match": { "browser": { "$ne": null } } },
{ "$group": {
"_id": "$_id",
"action": { "$first": "$action" },
"browser": { "$push": "$browser" },
"country": { "$first": "$country" }
}},
{ "$unwind": "$action" },
{ "$match": { "action": { "$ne": null } } },
{ "$group": {
"_id": "$_id",
"action": { "$push": "$action" },
"browser": { "$first": "$browser" },
"country": { "$first": "$country" }
}}
])
输出与键/值形式略有不同,但可以通过与您目前所做的大致相同的后处理轻松地将其操作为一个。所以像这样的输入:
{ "action" : "change_password", "browser" : "ie", "country" : "US" }
{ "action" : "change_password", "browser" : "ie", "country" : "UK" }
{ "action" : "change_password", "browser" : "chrome", "country" : "AU" }
得到的结果是这样的:
{
"_id" : null,
"action" : [
{
"value" : "change_password",
"count" : 3
}
],
"browser" : [
{
"value" : "ie",
"count" : 2
},
{
"value" : "chrome",
"count" : 1
}
],
"country" : [
{
"value" : "US",
"count" : 1
},
{
"value" : "UK",
"count" : 1
},
{
"value" : "AU",
"count" : 1
}
]
}
所以你对 mapReduce 的输出有一点不同,但无论如何,任何 mapReduce 输出也将“不完全”是你想要的输出格式。聚合框架以原生代码实现,运行速度更快