我正在开发一个在 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或者聚合中填充朋友?如果您对我的问题有完全不同的解决方案,请分享。
答案 0 :(得分:1)
答案 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);
})
}
]);