【问题标题】:Aggregate Data into Array MongoDB, Mongoose, Node将数据聚合到数组 MongoDB、Mongoose、Node
【发布时间】:2014-06-02 05:04:05
【问题描述】:

好的,这就是我想要做的。我有一个用户模式,它有一个团队键和一个数组作为它的值。例如:

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}]
});

【问题讨论】:

  • 不确定您在这里的实际意思。这实际上是如何在 Mongoose 模式中建模的?和/或您的收藏中文档的基本结构是什么?您可能想要比循环 .find() 更简单的方法
  • 我试图弄清楚如何根据用户模式的团队数组中的值从另一个模式获取数据。例如.. 在用户模式团队数组中有 ['Bengals', 'Bobcats'] 现在基于这两个字符串我想找到名称为 Bengals 的团队模式和名称为 Bobcats 的团队模式并将它们组合起来返回该数据。
  • 我明白这一点,但我是说肯定有比你的做法更好的方法。请编辑您的问题,以包括您从中获取 data 的架构以及您正在循环使用 .find()Team 模型架构。
  • 当然谢谢。我已经添加了架构

标签: node.js mongodb mongoose pug


【解决方案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 );
    });
});

这是一种简单的方法,但如果您稍微更改架构以引用 Team 模型中的数据,mongoose 确实有一些可用的功能可以为您做到这一点。因此,您可以使用.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 模式匹配中提取了所有数据存储在用户数组中的_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。

【讨论】:

    【解决方案2】:

    首先,查找操作是异步的,尽管您在推送到 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,您可以按如下方式使用填充:

    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() 返回一个仅包含您需要的属性的用户对象;

    【讨论】:

    • 您的解决方案确实有效。我只需要从 if(responses == ...) 中删除负 1。如果尼尔没有回应,我会标记你的答案,谢谢。
    • 没问题。我已经根据您现在提供的架构更新了答案,在我的第二点中包含一个示例。
    猜你喜欢
    • 2016-07-02
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 2017-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    相关资源
    最近更新 更多