使用map / reduce映射集合中的属性

时间:2010-06-08 11:55:09

标签: mongodb mapreduce

更新: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片段,我们会得到:

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

但我们不会得到 bar baaar 键,这些键嵌套在数据结构中。问题是:如何检索所有密钥,无论其深度如何?理想情况下,我实际上希望脚本能够深入到所有级别的深度,产生如下输出:

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

提前谢谢!

4 个答案:

答案 0 :(得分:26)

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

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

步骤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 = 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 有点冒险。您需要所有键,技术上“egg.0” 是有效密钥。您可以修改m_sub以忽略此类数字键。但是,这种情况也很容易让人感到厌恶。假设您在常规数组中有一个关联数组,那么您希望显示“0”。我会把剩下的解决方案留给你。

答案 1 :(得分:7)

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

希望你会发现它很有用。如果您有任何问题或使用它有任何问题,请与我们联系。

答案 2 :(得分:0)

作为简单功能;

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

答案 3 :(得分:0)

我解决了盖茨所说的问题#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))

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