Mapreduce与内部文档mongodb排序



我有一个关于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;
            });
        }
    }
)

每件事基本上都遵循相同的原则来达到最终结果:

  1. 反规范化为"用户"one_answers"标签"组合,并使用"用户"和分组键
  2. 将每个用户的结果与"标签"的总值相结合。

在这里的mapReduce方法中,除了比您尝试的更干净之外,这里要考虑的另一个要点是,reducer需要"输出"与来自映射器的相同类型的"输入"。原因实际上是有案可查的,因为"reducer"实际上可以被调用多次,基本上是"再次减少"已经通过reduce处理的输出。

这通常是mapReduce处理"大输入"的方式,在这种情况下,给定的"键"有很多值,而"reducer"一次只处理这么多。例如,一个reducer实际上可能只接收30个左右的具有相同键的文档,将这30个文档中的两组减少到2个文档,然后最终减少到单个键的单个输出。


这里的最终结果与上面显示的其他输出相同,与mapReduce不同的是,所有内容都在"value"键下,因为这就是它的工作方式。

根据你的数据有几种方法。尽可能坚持使用聚合框架,因为它要快得多,而且现代版本可以使用和输出与mapReduce一样多的数据。

最新更新