如何做嵌套数组文件的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
操作通过$setIsSubset
testing数组内容,并返回匹配的_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 ,因为你只是显示已经包含在查询结果中的额外信息。
- 保存你正在查询的
userID
。 - 把你的查询结果(对象中的选项数组),search数组中每个元素的
votes
。 - 当你find合适的投票时,附上
_id
(如果你没有find投票,也许会加上'n / a')。
写一个2和3的函数,然后你可以传递一个userID
,并返回一个myVote
的新对象。
我不认为这样做会比在Mongoose中做另一个查询慢。