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 - 将所有值连接到单个数组的主要内容,如果未能解决你的问题,请参考以下文章

Node 使用mongodb的mongoose是否很坑

Mongoose / Mongodb 迁移到 MySQL

MongoDB / Mongoose 一对多关系

mongoose \ mongodb 按一些列表排序? [复制]

初学mongodb和mongoose

如何使用用户名和密码连接 mongodb(mongoose)?