mapreduce与内部文件mongodb上的排序

时间:2014-07-07 11:30:37

标签: javascript mongodb mapreduce aggregation-framework

我对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
  }
}

当我在做我的地图时减少此文档集合上的代码我想在此命令中指定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_clouds的集合属于单个文档中的一个人。为实现这一点,我使用的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”。我知道reduce代码存在一些问题但无法找到。

现在我想达到一个看起来像

的最终结果
{"_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"
        }
);

最后一段代码尚未经过测试。这就是我想做的一切。请帮忙

1 个答案:

答案 0 :(得分:2)

您的PHP背景似乎正在显示。您所代表的数据结构不是以典型的JSON表示法显示数组,但是有人注意到了#34; push"在你的mapReduce代码中,至少在你的"临时文件中#34;这些值实际上是数组。你似乎已经注意到了#34;它们是一样的,所以假设它们似乎是合理的。

实际数组是您存储的最佳选择,特别是考虑到您期望的结果。因此,即使他们不这样做,您的原始文档应该如下所示,因为它们将在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" dump"数组约定),那么你需要使用mapReduce,因为聚合框架不能遍历" hash-密钥"这些代表的方式。不是最好的结构,如果是这种情况你应该改变它。

您的方法仍有几处更正,这实际上是对最终结果的单步操作。但同样,最终输出将包含" array" "标签",因为使用你的"数据"真的不是一个好习惯。 as" key"名称:

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;
            });

        }
    }
)

或者如果它实际上是一个&#34;数组&#34; &#34;标签&#34;在您的原始文档中但是您的结束输出太大而且您无法升级到最近的版本,那么初始阵列处理只是有点不同:

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. 取消规范化为&#34;用户&#34;和&#34;标记&#34;与&#34;用户&#34;组合和分组键
  2. 将每个用户的结果与&#34;标记&#34;值。
  3. 在这里的mapReduce方法中,除了比你想要尝试的更干净之外,这里要考虑的另一个要点是减速器需要&#34;输出&#34; 完全相同的&#34;输入&#34;来自映射器。原因实际上有很好的记录,因为&#34;减速机&#34;实际上可以多次调用,基本上#34;再次减少&#34;已经通过减少处理的输出。

    这通常是mapReduce如何处理&#34;大输入&#34;,其中有很多值给定的&#34;键&#34;和#34;减速机&#34;只能同时处理这么多。例如,reducer实际上只需要使用相同的密钥发出30个左右的文档,将这些30个文档中的两组减少到2个文档,然后最终减少为单个键的单个输出。


    这里的最终结果与上面显示的其他输出相同,mapReduce的区别在于所有内容都在&#34;值&#34;因为这就是它的工作原理。

    根据您的数据,有两种方法可以做到这一点。尽可能坚持使用聚合框架,因为它更快,现代版本可以消耗和输出尽可能多的数据,就像你可以在mapReduce上一样。