node.js - 如何使用嵌套数组文档进行 Mongoose 聚合

标签 node.js mongodb mongoose

我有一个 Mongodb 集合,民意调查具有以下架构

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

假设我在 Node js中有我想要向其发送此信息的用户的userId。 我的任务是

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

作为

"myVote" : option._id

我需要找到选项._id

options[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运营商进行重建。

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

您还提到了另一个关于使用 $unwind 处理空数组的问题的链接。这里的 $size 运算符会做正确的事情,因此不需要 $unwind 并在本例中数组为空的情况下投影一个“虚拟”值。

<小时/>

重要的是,除非您实际上是跨文档“聚合”,否则通常建议在客户端代码而不是聚合框架中执行此操作。使用 $unwind 可以有效地在聚合管道中为每个文档中包含的数组的每个元素创建一个新文档,这会产生大量开销。

对于仅作用于不同文档的此类操作,客户端代码单独处理每个文档会更有效。

<小时/>

如果您确实必须坚持服务器处理是执行此操作的方法,那么使用 $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 并不相同,因为它是单个元素数组(或可能的多个匹配项),聚合框架缺少运算符来减少为非数组元素而无需进一步的开销:

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

关于node.js - 如何使用嵌套数组文档进行 Mongoose 聚合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30683354/

相关文章:

node.js - Mongoose 异步/等待查找然后编辑并保存?

javascript - 在 try block 中设置间隔,但如果捕获异常则清除

node.js - Nodejs session 维护

node.js - Mongoose - 转到下一个元素

node.js - 如何在 Mongoose 中填充引用另一个模式的对象数组

node.js - Mongoose 模型访问架构属性

node.js - yarn 安装错误在 Docker 构建期间找不到 package.json

mongodb - PouchDB/CouchDB 类似 MongoDB 的替代品

mongodb - 如何使用 React 和 axios 更新 MongoDB 中文档的对象?

javascript - Json语法有什么问题