如何做嵌套数组文件的mongoose聚合

我有一个Mongodb集合, 轮询与以下架构

{ "options" : [ { "_id" : Object Id, "option" : String, "votes" : [ Object Id ] // object ids of users who voted },..... ] } 

假设我有我想要发送这个信息的节点js中的用户的userId 。 我的任务是

(1)在上面的json对象(我​​使用mongoose)中包含一个额外的字段。

“myVote”:option._id

我需要findoption._id为其中

选项[someIndex] .votes包含userId

(2)改变每个选项中现有的“票数”字段以表示特定选项的票数,如示例中所见

例:

 { "options" : [ { "_id" : 1, "option" : "A", "votes" : [ 1,2,3 ] }, { "_id" : 2, "option" : "B", "votes" : [ 5 ] }, { "_id" : 3, "option" : "C", "votes" : [ ] } ] } 

所以,如果我用户id = 5的用户想看到民意调查,那么我需要发送以下信息:

预期结果 :

 { "my_vote" : 2, // user with id 5 voted on option with id 2 "options" : [ { "_id" : 1, "option" : "A", "votes" : 3 //num of votes on option "A" }, { "_id" : 2, "option" : "B", "votes" : 1 //num of votes on option "B" }, { "_id" : 3, "option" : "C", "votes" : 0 //num of votes on option "C" } ] } 

既然这个问题实际上是你提出的,在目前的接受答案中并没有真正提供,还有一些不必要的东西,还有另一种方法:

 var userId = 5; // A variable to work into the submitted pipeline db.sample.aggregate([ { "$unwind": "$options" }, { "$group": { "_id": "$_id", "my_vote": { "$min": { "$cond": [ { "$setIsSubset": [ [userId], "$options.votes" ] }, "$options._id", false ] }}, "options": { "$push": { "_id": "$options._id", "option": "$options.option", "votes": { "$size": "$options.votes" } }} }} ]) 

哪个当然会给你每个文件的输出是这样的:

 { "_id" : ObjectId("5573a0a8b67e246aba2b4b6e"), "my_vote" : 2, "options" : [ { "_id" : 1, "option" : "A", "votes" : 3 }, { "_id" : 2, "option" : "B", "votes" : 1 }, { "_id" : 3, "option" : "C", "votes" : 0 } ] } 

所以你在这里做的是使用$unwind来拆分数组进行检查。 以下$group阶段(以及唯一需要的其他阶段)使用$min$push操作符来重新构build。

在每个操作中, $cond操作通过$setIsSubsettesting数组内容,并返回匹配的_id值或false 。 当重build内部数组元素时,在$push参数中指定所有元素,而不仅仅是顶层文档,并使用$size运算符来计算数组中的元素。

你也提到一个链接到另一个关于处理$unwind空数组的问题。 这里的$size操作符会做正确的事情,所以在这种情况下不需要$unwind并且在数组为空的情况下投射一个“虚拟”值。


值得一提的是,除非您实际上在文档之间进行“汇总”,否则通常会build议您在客户端代码而不是汇总框架中执行此操作。 使用$unwind有效地为每个文档中包含的数组的每个元素在聚合pipe道中创build一个新文档,这会产生大量开销。

对于仅在不同文档上执行的操作,客户端代码对于单独处理每个文档更有效。


如果你真的必须坚持服务器处理是这样做的,那么这可能是最有效的使用$map

 db.sample.aggregate([ { "$project": { "my_vote": { "$setDifference": [ { "$map": { "input": "$options", "as": "o", "in": { "$cond": [ { "$setIsSubset": [ [userId], "$$o.votes" ] }, "$$o._id", false ]} }}, [false] ] }, "options": { "$map": { "input": "$options", "as": "o", "in": { "_id": "$$o._id", "option": "$$o.option", "votes": { "$size": "$$o.votes" } } }} }} ]) 

所以这只是“计划”每个文件的重复结果。 my_vote是不一样的,因为它是一个单一的元素数组(或可能有多个匹配),聚合框架没有运算符减less到一个非数组元素没有进一步的开销:

 { "_id" : ObjectId("5573a0a8b67e246aba2b4b6e"), "options" : [ { "_id" : 1, "option" : "A", "votes" : 3 }, { "_id" : 2, "option" : "B", "votes" : 1 }, { "_id" : 3, "option" : "C", "votes" : 0 } ], "my_vote" : [ 2 ] } 

看看这个问题 。

这不是要求同样的事情,但没有办法做你没有多重查询无论如何。 我会修改你直接返回的JSON ,因为你只是显示已经包含在查询结果中的额外信息。

  1. 保存你正在查询的userID
  2. 把你的查询结果(对象中的选项数组),search数组中每个元素的votes
  3. 当你find合适的投票时,附上_id (如果你没有find投票,也许会加上'n / a')。

写一个2和3的函数,然后你可以传递一个userID ,并返回一个myVote的新对象。

我不认为这样做会比在Mongoose中做另一个查询慢。