将数据聚合到数组MongoDB,Mongoose,Node

时间:2014-06-02 05:04:06

标签: node.js mongodb mongoose pug

好的,这就是我想要做的。我有一个用户架构,其中包含一个团队密钥和一个数组作为其值。例如:

user = {
    team : Array
}

数组的值是字符串(团队名称)。我有一个团队架构,其中包含团队信息,如名册,州等。这是我的问题。如果我的用户在数组中有多个团队名称,那么我如何将所有数据聚合到一个变量然后将其传递给我的视图。我以为我可以使用for循环,但视图似乎在聚合完成之前呈现。这是我试过的:

// if there is there is more than one team name in the user's team array
if( data[0].teams.length > 1 ){

    for(var i = 0; i < data[0].teams.length; i++){
        // for each team name in the user array
        // find the corresponding team and add that data to the array called team
        Team.find( { team : data[0].teams[i] }, function(err, data){
            if(err){
                throw err;
            } 
            team.push(data);

        });
    }
    // render the view
    res.render('coach', {
        user: req.user,
        fname: self.fname,
        lname: self.lname,
        email: self.email,
        phone: self.phone,
        address: self.address,
        state: self.state,
        teams : [team]
    });
}

编辑(添加架构)

用户架构(查看团队数组,找到相应的团队,根据用户架构团队数组中的名称从团队架构返回数据)

var userSchema = new Schema({
    username : { type: String, required: true, index: { unique: true } },
    fname : { type: String, required: true },
    lname : { type: String, required: true },
    password : { type: String, required: true },
    type : { type: String, required: true },
    phone : String,
    address : String,
    state: String,
    conference: String,
    email : { type: String, required: true, index: { unique : true } },
    teams: Array,
});

团队架构

var teamsSchema = new Schema({
    coach: String,
    state: String,
    conference: String,
    team: String, 
    roster: [{fname: String, lname: String, number: String}]
});

2 个答案:

答案 0 :(得分:1)

首先,查找操作是异步的,虽然您在推送到team数组之前等待响应,但在使用res.render()进行响应之前,您并没有等待所有响应。下面的内容应该表明我的意思:

// if there is there is more than one team name in the user's team array
if( data[0].teams ){
    var teamArr = [];
    var responses = 0;

    for(var i=0; i<data[0].teams.length; i++){
        // for each team name in the user array
        // find the corresponding team and add that data to the array called team
        Team.find( { team : data[0].teams[i] }, function(err, team){
            if(err){
                throw err;
            }
            responses ++;

            teamArr.push(team);

            // if all teams have been pushed then call render
            if (responses == data[0].teams.length - 1) {
                // render the view
                res.render('coach', {
                   user: req.user,
                   fname: self.fname,
                   lname: self.lname,
                   email: self.email,
                   phone: self.phone,
                   address: self.address,
                   state: self.state,
                   teams : teamArr
               });
            }
        });
    }
}

编辑:我更改了一些命名以避免潜在的命名冲突(例如两个数据变量)

其次,如果您使用mongoose更改文档结构,则另一个选项是,如果您在数组中存储对团队文档对象的引用而不是名称字符串,则可以使用mongoose populate方法并填充返回整个文档之前的数组。

更新:根据提供的Schema,您可以使用populate,如下所示:

var userSchema = new Schema({
    username : { type: String, required: true, index: { unique: true } },
    ....
    email : { type: String, required: true, index: { unique : true } },
    teams: [{
        type: Schema.Types.ObjectId, ref: 'teams'
    }],
});

然后在返回之前填充文档,避免使用for循环:

User.findOne({_id: userId})
   .populate('teams')
   .exec(function (err, user) {
       if (err) return handleError(err);
       res.render('coach', {
           user: filterUser(user);
       });
   });

其中user.teams现在包含一组填充的团队,filterUser()返回一个只包含您需要的属性的用户对象;

答案 1 :(得分:1)

现在你已经发布了你的架构很清楚你正在尝试做什么,也清楚如何展示你的各种方法来做得更好。

第一个和基本的情况是,您似乎有一个“团队”数组,其中包含可能存储在您的用户对象上的“团队名称”的“字符串”值。这似乎对您有用,并且确实具有在检索用户时可以访问这些名称的优势。

问题是你正在迭代结果并为数组中的每个元素发出.find(),这不是最有效的方法。您基本上可以将$in运算符与查询和现有数据一起使用,并通过调用.toObject()来转换mongoose文档,绕过您将整个“团队数据”合并到用户对象时可能尝试做的事情。到一个普通的JavaScript对象:

User.findById( req.user, function(err,user) {

    var data = user.toObject();

    Team.find({ "team": "$in": data.teams }, function(err,teams) {
        data.teams = teams;
        res.render( 'coach', data );
    });
});

这是一种简单的方法,但实际上有一些mongoose可用的功能,如果您稍微更改模式以引用Team模型中的数据,它将为您执行此操作。因此,您只需使用.populate()填写:

var userSchema = new Schema({
    username : { type: String, required: true, index: { unique: true } },
    fname : { type: String, required: true },
    lname : { type: String, required: true },
    password : { type: String, required: true },
    type : { type: String, required: true },
    phone : String,
    address : String,
    state: String,
    conference: String,
    email : { type: String, required: true, index: { unique : true } },
    teams: [{ type: ObjectId, ref: "Team" }]
});

现在只存储引用_id值以及从何处获取它。所以现在您的查询变为:

User.findById( req.user).populate("teams").exec(function(err,user) {

    res.render("coach", user);
});

所以这样做基本上等同于第一个例子,但是使用_id值并以更简洁的方式,user对象现在已经从{Team中提取了所有数据{1}}模式匹配存储在用户数组中的_id值列表。

当然,您没有像以前那样立即访问团队名称,但您可以通过选择要填充的字段来解决此问题:

User.findById( req.user).populate("teams","team").exec(function(err,user) {

    res.render("coach", user);
});

因此,只会从每个对象中提取“团队”字段,并且与原来的结果大致相同,尽管实际上有两个查询(在引擎盖下)。

最后,如果你能够接受一点点数据复制的概念,那么最有效的方法就是简单地嵌入数据。虽然存储了重复数据,但读取效率最高,因为它是单个查询。如果您不需要定期更新该数据,则可能值得考虑:

var userSchema = new Schema({
    username : { type: String, required: true, index: { unique: true } },
    fname : { type: String, required: true },
    lname : { type: String, required: true },
    password : { type: String, required: true },
    type : { type: String, required: true },
    phone : String,
    address : String,
    state: String,
    conference: String,
    email : { type: String, required: true, index: { unique : true } },
    teams: [teamSchema]
});

因此,嵌入了这些数据,您只需要检索user,并且“团队”数据已存在:

User.findById( req.user, function(err,user) {

     res.render("coach", user);
});

所以这些是一些方法。所有这些都表明不需要循环数组,并且实际发出的查询数量可以大大减少,如果你可以使用它,甚至可以减少到1。