统计数组中每个键的不同值

我有一个反馈评级(沟通,及时性和交付)的集合,所有这些可以包含1-5的价值。 我需要的是统计每个评级有多less人评为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 } }); 

到目前为止,我正在使用$匹配,$ 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 } ] 

所以我试图通过实施多个$匹配获得所有的通信评级数字(这是行不通的)

 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 } 

可能这里的一个更大的问题是,你是否真的汇集了不同的文档? 或者你实际上只是想要这个“单一用户”? 因为这对于你“应该”如何处理这个问题确实有所不同。 但是,让我们来谈谈你能做些什么来获得你的结果。

我真的以为你看着这个错误的方式。 如果你要得到多个键的多个评分(“沟通”,“交付”,“及时性”),那么尝试为每个键分配一个“命名键”是很麻烦和不实的。 其实恕我直言,这样的输出是彻头彻尾的“凌乱”。

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

为此,我build议您改为查找包含每个“键”的结果,然后将其包含在自己的数组中,作为“分数”以及您实际使用的“分数”。 这是一个更清洁和机器可读的方法,因为替代scheme意味着在后面的代码中“迭代键”,这真是“杂乱”。

实际上在文档中“汇总”的汇总

所以为了把这个传递给聚合框架,考虑到你实际上是在整个文档中“聚合”,那么你应该这样做:

 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到“join”后,你$unwind结果数组,因为你想要汇总的细节“跨文件”无论如何。 接下来我们要做的是实际上让你的上述“键”成员的数组。 这是因为要进一步处理这个问题,我们也会$unwind这个内容,为每个“反馈”成员和“每个键”生成一个有效的新文档。

接下来的阶段都是用$group完成的,顺序是:

  1. 根据提供的“用户名”的forms,对每个分数的“不同”反馈键进行分组和计数。

  2. 按“键”分组,按“用户名”分隔文档,将“分数”及其不同计数添加到数组中。

  3. 按照“用户名”分组,也可以为每个包含“分数”数组的“键”创build一个数组条目。


处理光标时,你不是真的“聚合”

对此的替代方法是考虑到你只是要求一个“单一的用户名”,这个数据是在一个单一的文件中获得的。 因此,你真正想要在服务器上做的唯一事情就是执行“连接”的$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: ek , v: ev }) == JSON.stringify(b) ) != -1 ) { a[a.findIndex(e => JSON.stringify({ k: ek , v: ev }) == JSON.stringify(b) )].count += 1; } else { a = a.concat([{ k: bk, v: bv, count: 1 }]); } return a; },[]).reduce((a,b) => { if ( a.findIndex(e => e.type == bk) != -1 ) { a[a.findIndex(e => e.type == bk)].scores.push({ score: bv, count: b.count }) } else { a = a.concat([{ type: bk, scores: [{ score: bv, count: b.count }] }]); } return a; },[]); return doc; }); res.json(users) } ) 

事实上,如果它是一个“单一”用户,那么可能res.json(users[0]) ,因为.aggregate()的结果总是一个数组,无论返回多less结果。

这实际上只是在"feedback"数组上使用.map().reduce() JavaScript函数来重塑和返回“不同的计数”。 应用的方法也是一样的,但如果这确实是一个单一的文档响应,或者甚至是一个小的响应,而没有实际的需要“汇总文档”,那么它是更清洁和可能“更快”的处理方式。

在理论上,我们可以编写一个“非常复杂”的聚合stream水线版本,对单个文档执行完全相同的步骤,如代码所示。 然而,它是“非常复杂”,依靠现代的方法,因为从服务器传输的数据真的没有什么显着的“减less”。

所以如果你需要这个“跨文档”,那么在第一个列表中使用完整的聚合pipe道。 但是,如果您一次只需要一个“单个用户”,或者一个小select上的每个用户的“不同的结果”,那么客户端处理代码就是要走的路。

两者都产生相同的输出,正如我所提到的,这比你要去的方向更友好,而且在需要的时候更容易处理更多的代码:

 { "_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 } ] } ] }