MongoDB:查找并修改结果,具体取决于字段数组中的某个值

时间:2015-11-20 12:47:02

标签: mongodb

MongoDB中是否有一个简单的解决方案可以找到一些与查询匹配的对象,然后修改结果而不修改持久数据,具体取决于数组中是否包含某个值?

让我用一个例子解释我:

students = [
  { 
    name: "Alice", 
    age: 25, 
    courses: [ { name: "Databases", credits: 6 },{ name: "Java", credits: 4 }] 
  }, 
  { 
    name: "Bob",  
    age: 22, 
    courses: [ { name: "Java", credits: 4 } ] 
  }, 
  { 
    name: "Carol", 
    age: 19, 
    courses: [ { name: "Databases", credits: 6 } ] 
  }, 
  { 
    name: "Dave", age: 18
  }
]

现在,我想查询所有学生。结果应该返回除“'”课程以外的所有数据。相反,我想输出一个标志'参与者'表明该人是否参加了数据库课程:

result = [
  { name: "Alice", age: 25, participant: 1 }, 
  { name: "Bob", age: 22, participant: 0 },
  { name: "Carol", age: 19, participant: 1 }, 
  { name: "Dave", age: 18, participant: 0}
]

不更改数据库中的任何内容。

我已经找到了使用聚合的解决方案。但它非常复杂和不方便,所以,我想知道这个问题是否有一个更方便的解决方案

我目前的解决方案如下所示:

db.students.aggregate([
  {$project: {"courses": {$ifNull: ["$courses", [{name: 0}]]}, name: 1, _id: 1, age: 1}}, 
  {$unwind: "$courses"}, 
  {$project: {name: 1, age: 1, participant: {$cond: [{$eq: ["$courses.name", "DB"]}, 1, 0]}}}, 
  {$group: {_id: {_id: "$_id", age: 1, name: "$name"}, participant: {$sum: "$participant"}}}, 
  {$project: {_id: 0, _id: "$_id._id", age: "$_id.age", name: "$_id.name", participant: 1}}
]);

我不喜欢这个解决方案的一点是我必须指定输出字段三次。此外,这个管道很长。

2 个答案:

答案 0 :(得分:3)

运行以下聚合管道以获得所需的结果:

db.students.aggregate([
    {
        "$project": {
            "name": 1,
            "age": 1,
            "participant": {
                "$size": {
                    "$ifNull" : [ 
                        {
                            "$setIntersection" : [
                                {
                                    "$map": {
                                        "input": "$courses",
                                        "as": "el",
                                        "in": {
                                            "$eq": [ "$$el.name", "Databases" ]
                                        }
                                    }
                                },
                                [true]
                            ]
                        },
                        []
                    ]
                }                
            }
        }
    }
])

<强>输出

{
    "result" : [ 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd216"),
            "name" : "Alice",
            "age" : 25,
            "participant" : 1
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd217"),
            "name" : "Bob",
            "age" : 22,
            "participant" : 0
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd218"),
            "name" : "Carol",
            "age" : 19,
            "participant" : 1
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd219"),
            "name" : "Dave",
            "age" : 18,
            "participant" : 0
        }
    ],
    "ok" : 1
}

上述管道仅使用一个步骤$project,其中新字段participant是通过一系列嵌套运算符创建的。

操作的关键是深度嵌套的 $map 运算符,它本质上创建了一个新的数组字段,该字段通过子表达式中的每个元素的评估逻辑来保存值。阵列。让我们仅通过仅使用 $map 部分执行管道来演示此操作:

db.students.aggregate([
    {
        "$project": {
            "name": 1,
            "age": 1,
            "participant": {
                "$map": {
                    "input": "$courses",
                    "as": "el",
                    "in": {
                        "$eq": [ "$$el.name", "Databases" ]
                    }
                }               
            }
        }
    }
])

<强>输出

{
    "result" : [ 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd216"),
            "name" : "Alice",
            "age" : 25,
            "participant" : [ 
                true, 
                false
            ]
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd217"),
            "name" : "Bob",
            "age" : 22,
            "participant" : [ 
                false
            ]
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd218"),
            "name" : "Carol",
            "age" : 19,
            "participant" : [ 
                true
            ]
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd219"),
            "name" : "Dave",
            "age" : 18,
            "participant" : null
        }
    ],
    "ok" : 1
}

通过引入 $setIntersection 运算符进一步探测数组,该运算符返回一个包含所有输入集中出现的元素的集合。因此,在上面你需要获得一个结果数组,该数组真实地表示文档用户参与了数据库课程,否则它将返回一个空数组或空数组。让我们看看添加该运算符如何影响先前的结果:

db.students.aggregate([
    {
        "$project": {
            "name": 1,
            "age": 1,
            "participant": {
                "$setIntersection" : [
                    {
                        "$map": {
                            "input": "$courses",
                            "as": "el",
                            "in": {
                                "$eq": [ "$$el.name", "Databases" ]
                            }
                        }
                    },
                    [true]
                ]                
            }
        }
    }
])

<强>输出

{
    "result" : [ 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd216"),
            "name" : "Alice",
            "age" : 25,
            "participant" : [ 
                true
            ]
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd217"),
            "name" : "Bob",
            "age" : 22,
            "participant" : []
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd218"),
            "name" : "Carol",
            "age" : 19,
            "participant" : [ 
                true
            ]
        }, 
        {
            "_id" : ObjectId("564f1bb67d3c273d063cd219"),
            "name" : "Dave",
            "age" : 18,
            "participant" : null
        }
    ],
    "ok" : 1
}

要处理空值,请应用 $ifNull 运算符,相当于SQL中的coalesce命令,用空数组替换空值:

db.students.aggregate([
    {
        "$project": {
            "name": 1,
            "age": 1,
            "participant": {
                "$ifNull" : [ 
                    {
                        "$setIntersection" : [
                            {
                                "$map": {
                                    "input": "$courses",
                                    "as": "el",
                                    "in": {
                                        "$eq": [ "$$el.name", "Databases" ]
                                    }
                                }
                            },
                            [true]
                        ]
                    },
                    []
                ]                
            }
        }
    }
])

然后,您可以使用 $ifNull 运算符包装 $size 运算符,以返回参与者数组中的元素数量,以及产生如上所述的最终输出。

答案 1 :(得分:0)

根据您对少量对象的说法,如何简单地提取数据库名称并使用JavaScript地图进行转换?您在传输方面没有节省太多,而且代码将比管道更具可读性。