从数组内部计算每个键的不同值

时间:2017-06-19 00:37:43

标签: javascript node.js mongodb mongoose aggregation-framework

我有一系列反馈评级(通讯,及时性和交付),所有这些都可以包含1-5值。我需要的是计算每个评级中有​​多少人评定5星,然后是4,然后是3,然后是1。

这是我的用户架构

var UserSchema = new mongoose.Schema({
    username: String,
    fullname: String,
    email: {
        type: String,
        lowercase: true,
        unique: true
    },
  password: String,
  feedback: [{
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Feedback'    
  }]
});

反馈架构

var FeedbackSchema = new mongoose.Schema({
    postname: String,
    userWhoSentFeedback: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    message: String,
    feedbacktype: String,
    thumbsup: Boolean,
    rating: {
        delivery: Number,
        timeliness: Number,
        communication: Number
    }
});

到目前为止,我使用$ match,$ unwind,$ lookup和$ count尝试获取值但失败了。这是我正在运行的代码...

router.get('/testagg', (req, res)=>{
    User.aggregate([
        {"$match": 
            { "username": "user1"}
        },
        {
            "$lookup": {
            "from": "feedbacks",
            "localField": "feedback",
            "foreignField": "_id",
            "as": "outputResult"
            }
        }, {"$unwind": "$outputResult"},
        {"$match": {"outputResult.rating.communication": {$eq: 1}}},{"$count": 'Communication_1'},
        {
            "$project": {
                outputResult: 1,
                Communication_1: 1
            }
        }
    ], (err, user)=>{
        console.log(user)
        res.json(user);
    })
})

使用此代码,这是我得到的结果

[
  {
    "Communication_1": 1
  }
]

所以我试图通过实施多个$ match(这不起作用)来获取通信的所有评级数字

router.get('/testagg', (req, res)=>{
    User.aggregate([
        {"$match": 
            { "username": "user1"}
        },
        {
            "$lookup": {
            "from": "feedbacks",
            "localField": "feedback",
            "foreignField": "_id",
            "as": "outputResult"
            }
        }, {"$unwind": "$outputResult"},
        {"$match": {"outputResult.rating.communication": {$eq: 1}}},{"$count": 'Communication_1'},
        {"$match": {"outputResult.rating.communication": {$eq: 2}}},{"$count": 'Communication_2'},
        {"$match": {"outputResult.rating.communication": {$eq: 3}}},{"$count": 'Communication_3'},
        {"$match": {"outputResult.rating.communication": {$eq: 4}}},{"$count": 'Communication_4'},
        {"$match": {"outputResult.rating.communication": {$eq: 5}}},{"$count": 'Communication_5'},
        {
            "$project": {
                outputResult: 1,
                Communication_1: 1,
                Communication_2: 1,
                Communication_3: 1,
                Communication_4: 1,
                Communication_5: 1
            }
        }
    ], (err, user)=>{
        console.log(user)
        res.json(user);
    })
})

但我得到了一个空洞的回应。所以我认为我做错了。

任何帮助将不胜感激!谢谢!

**更新

我也试过这段代码。只是为了获得1和4的通信值。

router.get('/testagg', (req, res)=>{
    User.aggregate([
        {"$match": 
            { "username": "user1"}
        },
        {
            "$lookup": {
            "from": "feedbacks",
            "localField": "feedback",
            "foreignField": "_id",
            "as": "outputResult"
            }
        }, {"$unwind": "$outputResult"}, {
            "$project": {
                "outputResult.rating": 1,
                comm1: { $cond: [{$eq: ['$outputResult.rating.communication', 1]}, 1, 0]},
                comm4: { $cond: [{$eq: ['$outputResult.rating.communication', 4]}, 1, 0]}
            }
        },
         { $group: {
            _id: '$outputResult.rating',
            total: { $sum: 1 },
            comm1: { $sum: '$comm1'},
            comm4: { $sum: '$comm4'}
            }
        }
    ], (err, user)=>{
        console.log(user)
        res.json(user);
    })
})

这是我得到的结果

[
  {
    "_id": {
      "communication": 1,
      "timeliness": 1,
      "delivery": 1
    },
    "total": 1,
    "comm1": 1,
    "comm4": 0
  },
  {
    "_id": {
      "communication": 5,
      "timeliness": 5,
      "delivery": 5
    },
    "total": 1,
    "comm1": 0,
    "comm4": 0
  },
  {
    "_id": {
      "communication": 4,
      "timeliness": 4,
      "delivery": 5
    },
    "total": 1,
    "comm1": 0,
    "comm4": 1
  }
]

嗯,这是重要的,但这不是我想要的,我想要的是每个评级的总数

这是我想要的输出

{
"comm1" : 1,
"comm2" : 0,
"comm3" : 0,
"comm4" : 1,
"comm5" : 1
}

1 个答案:

答案 0 :(得分:1)

这里可能更大的问题是你实际上是跨文档汇总的吗?或者你实际上只是想要一个"单个用户"?因为这对于你应该如何"真的有所不同。接近这个。但是,让我们根据您可以采取的措施来解决问题。

老实说,我认为你正在以错误的方式看待这个问题。如果您要获得多个密钥的多个评级("通信","交付","及时性"),那么尝试命名为"命名密钥"对于每一个都是繁琐和笨拙的。事实上恕我直言,这样的输出是彻头彻尾的"凌乱"。

正如我所看到的,你最好使用自然结构来制作"列表"这是一个"数组",您可以在以后的代码中轻松迭代和访问。

为此,我建议您寻找包含每个"键"的结果。然后包含它自己的数组"得分"和他们的"不同的数量"你实际上是在追求它是一种更清洁和机器可读的方法,因为替代方法意味着“迭代密钥”#34;在后面的代码中,然后真的"凌乱"。

汇总您实际汇总的地方'跨文件

所以,考虑到你实际上已经考虑到了#34;聚合"跨文档,那么你应该做这样的事情:

User.aggregate(
  [
    { "$match": { "username": "user1" }
    { "$lookup": {
      "from": "feedbacks",
      "localField": "feedback",
      "foreignField": "_id",
      "as": "feedback"
    }},
    { "$unwind": "$feedback" },
    { "$project": {
      "username": 1,
      "feedback": [
        { "k": "delivery", "v": "$feedback.rating.delivery" }, 
        { "k": "timeliness", "v": "$feedback.rating.timeliness" },
        { "k": "communication", "v": "$feedback.rating.communication" }
      ]
    }},
    { "$unwind": "$feedback" },
    { "$group": {
       "_id": {
         "username": "$username",
         "type": "$feedback.k",
         "score": "$feedback.v",
       },
       "count": { "$sum": 1 }
    }},
    { "$group": {
      "_id": { 
        "username": "$_id.username",
        "type": "$_id.type"
      },
      "scores": { 
        "$push": { 
          "score": "$_id.score",
          "count": "$count"
        }
      }
    }},
    { "$group": {
      "_id": "$_id.username",
      "feedback": {
        "$push": {
          "type": "$_id.type",
          "scores": "$scores"
        }
      }
    }}
  ],
  function(err,users) {

  }
)

此处的基本流程是在$lookup到"加入"之后,您$unwind生成的数组,因为您希望汇总其中的详细信息" #34;无论如何。接下来我们要做的就是让你上面提到的"键"数组的成员也是如此。这是因为为了进一步处理这个问题,我们还将$unwind该内容根据"反馈"生成有效的新文档。会员以及"每个键"。

接下来的几个阶段都是用$group完成的,这些是按顺序进行的:

  1. 分组并计算" distinct"根据所提供的"用户名"形成的每个分数的反馈密钥。

  2. 分组"键"按"用户名"分隔文件,添加"分数"和他们对阵列的不同计数。

  3. 仅使用"用户名"还要为每个键创建一个数组条目"包含"分数"阵列也是如此。

  4. 当您实际上没有聚合'

    时处理光标

    另一种方法是考虑到你只需要一个单一的用户名",而这些数据是在一个单一的文件中获得的。无论如何。因此,你真正想要做的唯一事情是在服务器上#34;是$lookup执行"加入"。在那之后,处理"反馈"更加简单。带有客户端代码的数组,产生完全相同的不同结果。

    只需使用$lookup进行连接,然后处理结果:

    User.aggregate(
      [
        { "$match": { "username": "user1" }
        { "$lookup": {
          "from": "feedbacks",
          "localField": "feedback",
          "foreignField": "_id",
          "as": "feedback"
        }},
      ],
      function(err,users) {
        users = users.map(doc => {
          doc.feedback = [].concat.apply([],doc.feedback.map(
            r => Object.keys(r.rating).map(k => ({ k: k, v: r.rating[k] }) )
          )).reduce((a,b) => {
            if ( a.findIndex(e => JSON.stringify({ k: e.k , v: e.v }) == JSON.stringify(b) ) != -1 ) {
              a[a.findIndex(e => JSON.stringify({ k: e.k , v: e.v }) == JSON.stringify(b) )].count += 1;
            } else {
             a = a.concat([{ k: b.k, v: b.v, count: 1 }]);
            }
            return a;
          },[]).reduce((a,b) => {
            if ( a.findIndex(e => e.type == b.k) != -1 ) {
              a[a.findIndex(e => e.type == b.k)].scores.push({ score: b.v, count: b.count })
            } else {
              a = a.concat([{ type: b.k, scores: [{ score: b.v, count: b.count }] }]);
            }
            return a;
          },[]);
          return doc;
       });
    
       res.json(users)       
      }
    )
    

    事实上,如果它是一个单一的"用户,那么你最后可能不会res.json(users[0]),因为.aggregate()的结果始终是一个数组,无论返回多少结果。

    这实际上只是在"feedback"数组上使用.map().reduce() JavaScript函数,以便重新整形并返回"不同的计数"。应用了相同类型的方法,但如果这确实是单个文档响应,或者甚至是一个小的响应而没有实际需要跨文档聚合,那么它就更清晰,可能和#34;快"处理方式。

    理论上我们可以写一个非常复杂的"聚合管道的版本,对于单个文档执行完全相同的步骤,如此处的代码所示。然而,它真的很复杂"并且依赖于现代方法,对于真正没有意义的东西,减少"是从服务器传输的数据。

    所以如果你需要这个"跨文件"然后在第一个列表中使用完整聚合管道。但如果你只需要一个"单个用户"一次,或者"截然不同的结果"对于小型选择的每个用户,客户端处理代码是可行的方法。

    两者都产生相同的输出,正如我所提到的那样,它比你前进的方向更友好,并且根据需要在更多代码中更容易处理:

    {
            "_id" : "user 1",
            "feedback" : [
                    {
                            "type" : "communication",
                            "scores" : [
                                    {
                                            "score" : 2,
                                            "count" : 2
                                    },
                                    {
                                            "score" : 4,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 5,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 3,
                                            "count" : 1
                                    }
                            ]
                    },
                    {
                            "type" : "delivery",
                            "scores" : [
                                    {
                                            "score" : 3,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 4,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 2,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 5,
                                            "count" : 2
                                    }
                            ]
                    },
                    {
                            "type" : "timeliness",
                            "scores" : [
                                    {
                                            "score" : 1,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 5,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 4,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 3,
                                            "count" : 1
                                    },
                                    {
                                            "score" : 2,
                                            "count" : 1
                                    }
                            ]
                    }
            ]
    }