【问题标题】:Using map/reduce for mapping the properties in a collection使用 map/reduce 映射集合中的属性
【发布时间】:2011-03-01 03:12:08
【问题描述】:

更新:MongoDB Get names of all keys in collection 的后续行动。

正如Kristina 所指出的,可以使用 Mongodb 的 map/reduce 列出集合中的键:

db.things.insert( { type : ['dog', 'cat'] } );
db.things.insert( { egg : ['cat'] } );
db.things.insert( { type :  [] }); 
db.things.insert( { hello : []  } );

mr = db.runCommand({"mapreduce" : "things",
"map" : function() {
    for (var key in this) { emit(key, null); }
},  
"reduce" : function(key, stuff) { 
   return null;
}}) 

db[mr.result].distinct("_id")

//output: [ "_id", "egg", "hello", "type" ]

只要我们只想获取位于第一层深度的键,就可以正常工作。但是,它将无法检索位于更深层次的那些密钥。如果我们添加一条新记录:

db.things.insert({foo: {bar: {baaar: true}}})

我们再次运行上面的map-reduce +distinct sn-p,我们会得到:

[ "_id", "egg", "foo", "hello", "type" ] 

但是我们不会得到嵌套在数据结构中的 barbaaar 键。问题是:我如何检索所有键,无论它们的深度如何?理想情况下,我实际上希望脚本深入到所有深度,产生如下输出:

["_id","egg","foo","foo.bar","foo.bar.baaar","hello","type"]      

提前谢谢你!

【问题讨论】:

    标签: mongodb mapreduce


    【解决方案1】:

    好的,这有点复杂,因为您需要使用一些递归。

    要实现递归,您需要能够在服务器上存储一些函数。

    第一步:定义一些函数,放到服务器端

    isArray = function (v) {
      return v && typeof v === 'object' && typeof v.length === 'number' && !(v.propertyIsEnumerable('length'));
    }
    
    m_sub = function(base, value){
      for(var key in value) {
        emit(base + "." + key, null);
        if( isArray(value[key]) || typeof value[key] == 'object'){
          m_sub(base + "." + key, value[key]);
        }
      }
    }
    
    db.system.js.save( { _id : "isArray", value : isArray } );
    db.system.js.save( { _id : "m_sub", value : m_sub } );
    

    第 2 步:定义 map 和 reduce 函数

    map = function(){
      for(var key in this) {
        emit(key, null);
        if( isArray(this[key]) || typeof this[key] == 'object'){
          m_sub(key, this[key]);
        }
      }
    }
    
    reduce = function(key, stuff){ return null; }
    

    第 3 步:运行 map reduce 并查看结果

    mr = db.runCommand({"mapreduce" : "things", "map" : map, "reduce" : reduce,"out": "things" + "_keys"});
    db[mr.result].distinct("_id");
    

    你会得到的结果是:

    ["_id", "_id.isObjectId", "_id.str", "_id.tojson", "egg", "egg.0", "foo", "foo.bar", "foo.bar.baaaar", "hello", "type", "type.0", "type.1"]
    

    这里有一个明显的问题,我们在这里添加了一些意想不到的字段: 1. _id 数据 2. .0(关于鸡蛋和类型)

    第 4 步:一些可能的修复方法

    对于问题#1,修复相对容易。只需修改map 函数即可。改变这个:

    emit(base + "." + key, null); if( isArray...
    

    到这里:

    if(key != "_id") { emit(base + "." + key, null); if( isArray... }
    

    问题 #2 有点冒险。您想要 all 键,并且从技术上讲,“egg.0”is 是一个有效的键。您可以修改m_sub 以忽略此类数字键。但也很容易看到事与愿违的情况。假设您在常规数组中有一个关联数组,那么您希望“0”出现。我将把剩下的解决方案留给你。

    【讨论】:

    • 感谢盖茨!我还找到了另一种解决方案(但不涉及使用 map/reduce)在这里解释:groups.google.com/group/mongodb-user/browse_thread/thread/…
    • 查看新代码。第 3 步的第一部分引用了第 2 步中命名的函数。
    • 所以这个答案的日期是 2010 年 6 月,您很可能需要添加在撰写本文时不存在的新 out 参数。坦率地说,您可能甚至不想为此使用 M/R,因为它可能可以使用更好的聚合框架来完成。
    • 你太棒了,即使是在 2017 年!
    【解决方案2】:

    以盖茨副总裁和克里斯蒂娜的回答为灵感,我创建了一个名为 Variety 的开源工具,它正是这样做的:https://github.com/variety/variety

    希望您会发现它很有用。如果您有任何问题或使用它的任何问题,请告诉我。

    【讨论】:

    • 我们需要一个开源工具来查询“模式”,这有点令人难过。我明白人们选择 MongoDB 的原因:将它的适用性用于真正的“文档”存储(在高度嵌套的意义上,或者无法先验地知道结构,或者查询的性质使得由于某种原因只有 MongoDB 是way to go),第一和第二,作为懒惰或初级开发人员避免使用更多结构强制(通常是关系)数据库系统的一种方式。根据我的经验,后者太普遍了,尤其是在初创企业中。仅仅因为 JSON 简单并不意味着它是正确的。
    【解决方案3】:

    作为一个简单的函数;

    const getProps = (db, collection) => new Promise((resolve, reject) => {
      db
      .collection(collection)
      .mapReduce(function() {
        for (var key in this) { emit(key, null) }
      }, (prev, next) => null, {
        out: collection + '_keys'
      }, (err, collection_props) => {
        if (err) reject(err)
    
        collection_props
        .find()
        .toArray()
        .then(
          props => resolve(props.map(({_id}) => _id))
        )
      })
    })
    

    【讨论】:

      【解决方案4】:

      我解决了 Gates 提出的问题 #2,例如返回了 data.0, data.1, data.2。尽管这些是如上所述的有效键,但出于演示目的,我想摆脱它们。我通过 m_sub 函数中的快速编辑解决了这个问题,如下所示。

      const m_sub = function (base, value) {
      for (var key in value) {
          if(key != "_id" && isNaN(key)){
              emit(base + "." + key, null);
              if (isArray(value[key]) || typeof value[key] == 'object') {
                  m_sub(base + "." + key, value[key]);
              }
          }
      }
      

      此更改还实现了上述问题 #1 的解决方案,唯一的更改是在我更改此的第一个 if 语句中:

      if(key != "_id")
      

      为此使用isNaN(x) 函数:

      if(key != "_id" && isNaN(key))
      

      希望这对某人有所帮助,如果此解决方案有问题,请提供反馈!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-03
        • 1970-01-01
        • 1970-01-01
        • 2021-12-11
        相关资源
        最近更新 更多