mongodb / mongoose mapreduce - 将所有值连接到单个数组

时间:2013-11-29 03:36:42

标签: node.js mongodb mongoose mapreduce aggregation-framework

我正在开发一个在 node.js 上运行的小应用程序,它通过 Mongoose ORM连接到 mongodb 。其中一个模型是Person模型 模型架构:

{
    id : Number,
    name : String
    concatVals : String
}

示例:

[
    {
        id : 1,
        name : 'jerry'
        friends : 'adam#peter#robert#steven'
    },
    {
        id : 2,
        name : 'tony'
        friends : 'richard#robert#steven'
    },
    {
        id : 3,
        name : 'mike'
        friends : 'henry#steven#jerry#adam#tony'
    },
    {
        id : 4,
        name : 'peter'
        friends : 'jerry#bill#bobby#steven#mike#paul'
    }
]        

如您所见,friends字段基本上是一个包含以'#'分隔的名称的字符串。朋友字段作为字符串而不是数组存在的重要原因。所以我们不能改变它的类型或结构。 这个“朋友列表”实际上在实际数据库中要长得多。如您所见,这些对象中的大多数将具有相交的朋友列表(steven出现在多个文档中)。

目标: 我需要弄清楚如何有效地分割每个文档中的朋友字段,将其转换为数组,并列出所有不同的填充人员子集的朋友。所以基本上我要求'tony'和'mike'的人得到的结果是:

[
  {
    name : jerry,
    id : 1,
    friends : 'adam#peter#robert#steven'
  },
  {
    name : tony,
    id : 2,
    friends : 'richard#robert#steven'
  },
  {
    richard ...
  }, 
  {
    henry ...
  },
  {
    steven ...
  },
  {
    robert ...
  },
  {
    adam ...
  }
] // POPULATED friends of tony and mike

问题是数据量是巨大的,所以我想尽可能多地将数据移动到数据库端,在服务器端进行最少的数据处理。到目前为止我的解决方案看起来像这样:

Person.mapReduce({
    map: function() {
        emit(this.name, this.friends.split('#')); 
    },
    reduce: function(key, values) {
        return values;
    },
    query: {
        name: {
            $in: ['tony', 'mike']
        }
    },
            out: 'friends_output'
}, // at this point we have docs with friends String splitted into array
        function(err, mapReduceObject) {
    mapReducePipeline.aggregate(
            { $unwind: '$value'}, 
            {
        $group: {_id: '$value'} // distinct friend docs
    }, 
            {
                // combining all distinct friends
        $group: {
            _id: null,
            allValues: { $addToSet: '$_id'}
                }
    },
    function(err, data) {
        console.log(data[0].allValues)
                // here I get the list of names, not populated docs
    });
});

这样我部分实现了我的目标:我能够得到'tony'和'mike'的所有不同朋友。但我希望这些朋友能够被填充,我无法在mapreduce期间找到填充它们的好方法。 当然,我可以在函数(错误,数据)中进行另一个数据库调用,并在查询中使用名称获取人员

...
},
function(err, data) {
    Persons.find({name : data[0].allValues},
        function(err, friends){
            console.log(friends);
        }
    );
});

但在此过程中,总计3个DB调用总计: - mapReduce - 聚合 - 搜索查询

这最后一次 .find()来电一直困扰着我。你有没有办法在mapreduce或者聚合中填充朋友?如果您对我的问题有完全不同的解决方案,请分享。

2 个答案:

答案 0 :(得分:1)

需求和导入之间的主要区别

enter image description here

答案 1 :(得分:0)

为什么不使用数组?如果你这样做,你可以在mongo中使用各种巧妙的技巧来处理你的数据(例如,在数组中找到一个带有“field”:“value”的值。)如果你需要那个散列格式的数据,你可以只需加入它就可以使用virtual getter将它们混合在一起,而不是相反,并且您的数据将更接近地反映它的模型。由于这都定义了一种关系,populate也可能是合适的,但可能会使事情变得更加迟钝。这是一个例子,其中“朋友”是单向关系,如“跟随”。我正在使用async所以所有内容都以正确的顺序保存。

var async = require('async');

// return all unique valuesin an Array.filter
var filterUnique = function(value, index, self) { return self.indexOf(value) === index; };

var PersonSchema = new mongoose.Schema({
  'name': String,
  '_friends': [{ type: mongoose.Schema.Types.ObjectId, ref: 'Person' }]
});

PersonSchema.virtual('friends').get(function () {
  return this['_friends'].map(function(f){ return f.name; }).join('#');
});

PersonSchema.methods.addFriend = function (friend) {
  this['_friends'] = this['_friends'] || [];
  this['_friends'].push(friend);
  this['_friends'] = this['_friends'].filter(filterUnique);
}

var Person = mongoose.model('Person', PersonSchema);

function generatePeople(cb){
  var generatePerson = function(name, cb){
    Person({"name": name}).save(cb);
  }
  async.map(['Paul', 'Peter', 'Mary', 'Emily', 'David', 'Christy'], generatePerson, cb);
}

function addFriendsPaul(cb){
  Person.findOne({"name":"Paul"}, function(err, Paul){
    var addFriend = function(person, cb){
      person.addFriend(Paul);
      person.save(cb);

      // paul adds them back
      Paul.addFriend(person);
      Paul.save();
    }
    Person.find({"name":{"$ne":"Paul"}}, function(err, people){
      async.map(people, addFriend, cb);
    });
  });
}

function addFriendsDavid(cb){
  Person.findOne({"name":"David"}, function(err, David){
    var addFriend = function(person, cb){
      person.addFriend(David);
      person.save(cb);
    }
    Person.find({"name":{"$ne":"David"}}, function(err, people){
      async.map(people, addFriend, cb);
    });
  });
}

async.series([
  generatePeople,
  addFriendsPaul,
  addFriendsDavid,
  function(){
    Person.findOne({"name":"Paul"})
    .populate('_friends')
    .exec(function(err, Paul){
      console.log('Paul:', Paul.friends);
    })
  }
]);