mongodb / mongoose mapreduce - 将所有值连接到单个数组
Posted
技术标签:
【中文标题】mongodb / mongoose mapreduce - 将所有值连接到单个数组【英文标题】:mongodb / mongoose mapreduce - concat all values to single array 【发布时间】:2013-12-15 04:32:48 【问题描述】:我正在开发一个在 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) 中进行另一个 DB 调用,并在查询中使用名称获取 Persons
...
,
function(err, data)
Persons.find(name : data[0].allValues,
function(err, friends)
console.log(friends);
);
);
但在此过程中总共有 3 个 DB 调用: - 地图减少 - 聚合 - 搜索查询
最后一个 .find() 电话一直困扰着我。您是否看到任何在 mapreduce 或聚合期间/期间填充朋友的方法?如果您对我的问题有完全不同的解决方案,请分享。
【问题讨论】:
【参考方案1】:Require 和 Import 之间的主要区别
【讨论】:
【参考方案2】:为什么不使用数组?如果你这样做了,你可以在 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);
)
]);
【讨论】:
以上是关于mongodb / mongoose mapreduce - 将所有值连接到单个数组的主要内容,如果未能解决你的问题,请参考以下文章