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}}
]);
我不喜欢这个解决方案的一点是我必须指定输出字段三次。此外,这个管道很长。
答案 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地图进行转换?您在传输方面没有节省太多,而且代码将比管道更具可读性。