我有一个关于mongodb的map-reduce的快速问题。我有如下的文档结构
{
"_id": "ffc74819-c844-4d61-8657-b6ab09617271",
"value": {
"mid_tag": {
"0": {
"0": "Prakash Javadekar",
"1": "Shastri Bhawan",
"2": "Prime Minister's Office (PMO)",
"3": "Narendra Modi"
},
"1": {
"0": "explosion",
"1": "GAIL",
"2": "Andhra Pradesh",
"3": "N Chandrababu Naidu"
},
"2": {
"0": "Prime Minister",
"1": "Narendra Modi",
"2": "Bharatiya Janata Party (BJP)",
"3": "Government"
}
},
"total": 3
}
}
当我在这个文档集合上执行map reduce代码时,我想指定total作为这个命令中的排序字段
db.ana_mid_big.mapReduce(map, reduce,
{
out: "analysis_result",
sort: {"value.total": -1}
}
);
但这似乎不起作用。如何指定用于排序的嵌套键?请帮助。
----------------------- 编辑 ---------------------------------
根据评论,我在这里张贴了我的整个问题。我从一个超过350万文档的集合开始(这只是一个旧的快照,已经超过了5.5 M),看起来像这样
{
"_id": ObjectId("53b394d6f9c747e33d19234d"),
"autoUid": "ffc74819-c844-4d61-8657-b6ab09617271"
"createDate": ISODate("2014-07-02T05:12:54.171Z"),
"account_details": {
"tag_cloud": {
"0": "FIFA World Cup 2014",
"1": "Brazil",
"2": "Football",
"3": "Argentina",
"4": "Belgium"
}
}
}
因此,可以有许多具有相同autoUid但具有不同(或部分相同甚至相同)tag_cloud的文档。
我已经编写了下面的map-reduce来生成一个中间集合,它看起来像问题开头的集合。因此,很明显,在一个文档中,所有tag_cloud的集合属于一个人。为了实现这一点,我使用的MR代码如下所示
var map = function(){
final_val = {
tag_cloud: this.account_details.tag_cloud,
total: 1
};
emit(this.autoUid, final_val)
}
var reduce = function(key, values){
var fv = {
mid_tags: [],
total: 0
}
try{
for (i in values){
fv.mid_tags.push(values[i].tag_cloud);
fv.total = fv.total + 1;
}
}catch(e){
fv.mid_tags.push(values)
fv.total = fv.total + 1;
}
return fv;
}
db.my_orig_collection.mapReduce(map, reduce,
{
out: "analysis_mid",
sort: {createDate: -1}
}
);
这里是问题1当某人有多个记录时,它服从简化函数。但是当某人只有一个而不是命名为"mid_tag"时,它保留了名称"tag_cloud"。我知道有一些问题与减少代码,但无法找到什么。
现在我想达到最终结果,看起来像
{"_id": "ffc74819-c844-4d61-8657-b6ab09617271",
"value": {
"tags": {
"Prakash Javadekar": 1,
"Shastri Bhawan": 1,
"Prime Minister's Office (PMO)": 1,
"Narendra Modi": 2,
"explosion": 1,
"GAIL": 1,
"Andhra Pradesh": 1,
"N Chandrababu Naidu": 1,
"Prime Minister": 1,
"Bharatiya Janata Party (BJP)": 1,
"Government": 1
}
}
最后是每个人的一个文档,代表他们使用的标签密度。我试图使用的MR代码(尚未测试)看起来像这样——
var map = function(){
var val = {};
if ("mid_tags" in this.value){
for (i in this.value.mid_tags){
for (j in this.value.mid_tags[i]){
k = this.value.mid_tags[i][j].trim();
if (!(k in val)){
val[k] = 1;
}else{
val[k] = val[k] + 1;
}
}
}
var final_val = {
tag: val,
total: this.value.total
}
emit(this._id, final_val);
}else if("tag_cloud" in this.value){
for (i in this.value.tag_cloud){
k = this.value.tag_cloud[i].trim();
if (!(k in val)){
val[k] = 1;
}else{
val[k] = val[k] + 1;
}
}
var final_val = {
tag: val,
total: this.value.total
}
emit(this._id, final_val);
}
}
var reduce = function(key, values){
return values;
}
db.analysis_mid.mapReduce(map, reduce,
{
out: "analysis_result"
}
);
最后一段代码尚未经过测试。这就是我想做的。请帮助
您的PHP背景似乎正在显示。您所表示的数据结构并没有以典型的JSON表示法显示数组,但是在您的mapReduce代码中有注意到的"push"调用,至少在您的"临时文档"中值实际上是数组。你似乎以同样的方式"标注"了它们,所以假设它们是合理的。
实际数组是存储的最佳选择,特别是考虑到您想要的结果。因此,即使它们不这样做,您的原始文档应该看起来像这样,因为它们将在shell中表示:
{
"_id": ObjectId("53b394d6f9c747e33d19234d"),
"autoUid": "ffc74819-c844-4d61-8657-b6ab09617271"
"createDate": ISODate("2014-07-02T05:12:54.171Z"),
"account_details": {
"tag_cloud": [
"FIFA World Cup 2014",
"Brazil",
"Football",
"Argentina",
"Belgium"
]
}
}
对于这样的文档,或者如果您将它们更改为这样的文档,那么执行此操作的正确工具是聚合框架。它在本地代码中工作,不需要JavaScript解释,因此速度要快得多。
得到最终结果的聚合语句如下:
db.collection.aggregate([
// Unwind the array to "de-normalize"
{ "$unwind": "$account_details.tag_cloud" },
// Group by "autoUid" and "tag", summing totals
{ "$group": {
"_id": {
"autoUid": "$autoUid",
"tag": "$account_details.tag_cloud"
},
"total": { "$sum": 1 }
}},
// Sort the results to largest count per user
{ "$sort": { "_id.autoUid": 1, "total": -1 }
// Group to a single user with an array of "tags" if you must
{ "$group": {
"_id": "$_id.autoUid",
"tags": {
"$push": {
"tag": "$_id.tag",
"total": "$total"
}
}
}}
])
输出略有不同,但处理起来更简单,速度更快:
{
"_id": "ffc74819-c844-4d61-8657-b6ab09617271",
"tags": [
{ "tag": "Narendra Modi", "total": 2 },
{ "tag": "Prakash Javadekar", "total": 1 },
{ "tag": "Shastri Bhawan", "total": 1 },
{ "tag": "Prime Minister's Office (PMO)", "total": 1 },
{ "tag": "explosion", "total": 1 },
{ "tag": "GAIL", "total": 1 },
{ "tag": "Andhra Pradesh", "total": 1 },
{ "tag": "N Chandrababu Naidu", "total": 1 },
{ "tag": "Prime Minister", "total": 1 },
{ "tag": "Bharatiya Janata Party (BJP)", "total": 1 },
{ "tag": "Government", "total": 1 }
]
}
也可以根据"标签相关性评分"对用户进行分类,但您可以考虑放弃该阶段,甚至根据您的实际情况将最后两个阶段都删除。
仍然是目前最好的选择。了解如何使用聚合框架。如果你的"输出"仍然是"大"(超过16MB),那么试着看看移动到MongoDB 2.6或更高版本。聚合语句可以产生一个可以迭代的"游标",而不是一次提取所有结果。还有 $out
操作符,它可以创建一个集合,就像mapReduce一样。
如果你的数据实际上是类似于"哈希"格式的子文档,就像你在你的符号中指出的那样(它遵循PHP对数组的"转储"约定),那么你需要使用mapReduce,因为聚合框架不能遍历这些表示方式的"哈希键"。这不是最好的结构,如果是这种情况,你应该改变它。
你的方法仍然有一些修正,这实际上变成了最终结果的一步操作。不过,最后的输出将再次包含一个"标签"的"数组",因为使用"data"作为"键"名称确实不是好的做法:
db.collection.mapReduce(
function() {
var tag_cloud = this.account_details.tag_cloud;
var obj = {};
for ( var k in tag_cloud ) {
obj[tag_cloud[k]] = 1;
}
emit( this.autoUid, obj );
},
function(key,values) {
var reduced = {};
// Combine keys and totals
values.forEach(function(value) {
for ( var k in value ) {
if (!reduced.hasOwnProperty(k))
reduced[k] = 0;
reduced[k] += value[k];
}
});
return reduced;
},
{
"out": { "inline": 1 },
"finalize": function(key,value) {
var output = [];
// Mapped to array for output
for ( var k in value ) {
output.push({
"tag": k,
"total": value[k]
});
}
// Even sorted just the same
return output.sort(function(a,b) {
return ( a.total < b.total ) ? -1 : ( a.total > b.total ) ? 1 : 0;
});
}
}
)
或者如果它实际上是原始文档中的"标签"的"数组",但最终输出将太大,您无法移动到最近的版本,那么初始数组处理只是略有不同:
db.collection.mapReduce(
function() {
var tag_cloud = this.account_details.tag_cloud;
var obj = {};
tag_cloud.forEach(function(tag) {
obj[tag] = 1;
});
emit( this.autoUid, obj );
},
function(key,values) {
var reduced = {};
// Combine keys and totals
values.forEach(function(value) {
for ( var k in value ) {
if (!reduced.hasOwnProperty(k))
reduced[k] = 0;
reduced[k] += value[k];
}
});
return reduced;
},
{
"out": { "replace": "newcollection" },
"finalize": function(key,value) {
var output = [];
// Mapped to array for output
for ( var k in value ) {
output.push({
"tag": k,
"total": value[k]
});
}
// Even sorted just the same
return output.sort(function(a,b) {
return ( a.total < b.total ) ? -1 : ( a.total > b.total ) ? 1 : 0;
});
}
}
)
每件事基本上都遵循相同的原则来达到最终结果:
- 反规范化为"用户"one_answers"标签"组合,并使用"用户"和分组键
- 将每个用户的结果与"标签"的总值相结合。
在这里的mapReduce方法中,除了比您尝试的更干净之外,这里要考虑的另一个要点是,reducer需要"输出"与来自映射器的相同类型的"输入"。原因实际上是有案可查的,因为"reducer"实际上可以被调用多次,基本上是"再次减少"已经通过reduce处理的输出。
这通常是mapReduce处理"大输入"的方式,在这种情况下,给定的"键"有很多值,而"reducer"一次只处理这么多。例如,一个reducer实际上可能只接收30个左右的具有相同键的文档,将这30个文档中的两组减少到2个文档,然后最终减少到单个键的单个输出。
这里的最终结果与上面显示的其他输出相同,与mapReduce不同的是,所有内容都在"value"键下,因为这就是它的工作方式。
根据你的数据有几种方法。尽可能坚持使用聚合框架,因为它要快得多,而且现代版本可以使用和输出与mapReduce一样多的数据。