我在 MongoDB 中有一组元素,如下所示:
{
"_id": ObjectId("5942643ea2042e12245de00c"),
"user": NumberInt(1),
"name": {
"value": "roy",
"time": NumberInt(121)
},
"lname": {
"value": "roy s",
"time": NumberInt(122)
},
"sname": {
"value": "roy 9",
"time": NumberInt(123)
}
}
但是当我执行下面的查询时
db.temp.find({
$or: [{
'name.time': {
$gte: 123
}
}, {
'lname.time': {
$gte: 123
}
}, {
'sname.time': {
$gte: 123
}
}]
})
它返回正确的整个文档。
有没有办法只获取条件匹配的指定对象。就像在我的文档中让 lname.time 内的条件等于 122 那么只有 lname 对象会返回,其余的将被忽略。
最佳答案
您要求的事情类型只有在 MongoDB 3.4 中才真正“实用”,以便从服务器返回它。
摘要
这里的一般情况是,通过逻辑条件对字段进行“投影”并不简单。虽然如果 MongoDB 有这样一个用于投影的 DSL 就好了,但这基本上被委托(delegate)给:
在服务器返回结果“之后”进行操作
使用聚合管道来操作文档。
因此,在“CASE B”是“聚合管道”的情况下,如果涉及的步骤“模仿”“query”和“的标准 .find()
行为,那么这实际上只是一个实际练习。项目”。除此之外引入其他管道阶段只会带来性能问题,远远超过“修剪”要返回的文档所带来的任何 yield 。
因此,这里的摘要是 $match
然后 $newRoot
到“project”,遵循模式。我认为这里考虑的一个很好的“经验法则”是,聚合方法“应该仅”应用于返回数据大小显着减少的情况。我会举例说明“如果”“修剪”键的大小实际上在返回结果的兆字节范围内,那么“在服务器上删除它们”是值得的练习”。
在这种情况下,相比之下,这种保存实际上只构成“字节”,那么最合乎逻辑的做法是简单地允许文档“未更改”地返回光标,然后才进行“后处理”您是否愿意删除不符合逻辑条件的不需要的键?
也就是说,采用实际方法。
聚合案例
db.temp.aggregate([
{ "$match": {
"$or": [
{ "name.time": { "$gte": 123 } },
{ "lname.time": { "$gte": 123 } },
{ "sname.time": { "$gte": 123 } }
]
}},
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
[
{ "k": "_id", "v": "$_id" },
{ "k": "user", "v": "$user" },
],
{ "$filter": {
"input": [
{ "$cond": [
{ "$gte": [ "$name.time", 123 ] },
{ "k": "name", "v": "$name" },
false
]},
{ "$cond": [
{ "$gte": [ "$lname.time", 123 ] },
{ "k": "lname", "v": "$lname" },
false
]},
{ "$cond": [
{ "$gte": [ "$sname.time", 123 ] },
{ "k": "sname", "v": "$sname" },
false
]}
],
"as": "el",
"cond": "$$el"
}}
]
}
}
}}
])
这是一个非常奇特的语句,它依赖于 $arrayToObject
和 $replaceRoot
来实现动态结构。本质上,“键”都是以数组形式表示的,其中“数组”只包含那些真正通过条件的键。
条件过滤后完全构造完毕,我们将数组转换为文档并将投影返回到新的 Root。
游标处理案例
实际上,您可以在客户端代码中轻松地完成此操作。例如在 JavaScript 中:
db.temp.find({
"$or": [
{ "name.time": { "$gte": 123 } },
{ "lname.time": { "$gte": 123 } },
{ "sname.time": { "$gte": 123 } }
]
}).map(doc => {
if ( doc.name.time < 123 )
delete doc.name;
if ( doc.lname.time < 123 )
delete doc.lname;
if ( doc.sname.time < 123 )
delete doc.sname;
return doc;
})
在这两种情况下,您都会得到相同的期望结果:
{
"_id" : ObjectId("5942643ea2042e12245de00c"),
"user" : 1,
"sname" : {
"value" : "roy 9",
"time" : 123
}
}
其中 sname
是文档中唯一满足条件的字段,因此也是唯一返回的字段。
动态生成和DSL重用
解决 Sergio 的问题,我想您实际上可以重新使用 $or
条件中的 DSL 在两种情况下生成:
考虑定义的变量
var orlogic = [
{
"name.time" : {
"$gte" : 123
}
},
{
"lname.time" : {
"$gte" : 123
}
},
{
"sname.time" : {
"$gte" : 123
}
}
];
然后用光标迭代:
db.temp.find({
"$or": orlogic
}).map(doc => {
orlogic.forEach(cond => {
Object.keys(cond).forEach(k => {
var split = k.split(".");
var op = Object.keys(cond[k])[0];
if ( op === "$gte" && doc[split[0]][split[1]] < cond[k][op] )
delete doc[split[0]];
else if ( op === "$lte" && doc[split[0]][split[1]] > cond[k][op] )
delete doc[split[0]];
})
});
return doc;
})
它根据 DSL 进行评估,以实际执行操作,而无需“硬编码”(某种程度上)if
语句;
那么聚合方法也将是:
var pipeline = [
{ "$match": { "$or": orlogic } },
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$concatArrays": [
[
{ "k": "_id", "v": "$_id" },
{ "k": "user", "v": "$user" }
],
{ "$filter": {
"input": orlogic.map(cond => {
var obj = {
"$cond": {
"if": { },
"then": { },
"else": false
}
};
Object.keys(cond).forEach(k => {
var split = k.split(".");
var op = Object.keys(cond[k])[0];
obj.$cond.if[op] = [ `$${k}`, cond[k][op] ];
obj.$cond.then = { "k": split[0], "v": `$${split[0]}` };
});
return obj;
}),
"as": "el",
"cond": "$$el"
}}
]
}
}
}}
];
db.test.aggregate(pipeline);
因此,我们重复使用现有的 $or
DSL 来生成所需的管道部分,而不是对其进行硬编码,基本条件相同。
关于mongodb - 仅返回查询条件为 True 的文档中的键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44565682/