mongodb - 仅返回查询条件为 True 的文档中的键

标签 mongodb mongodb-query aggregation-framework

我在 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/

相关文章:

mongodb - 子文档的 Mongo 更新

arrays - mongoDB中数组中的字符串字段值长度

node.js - MongoError : can't find any special indices: 2d (needs index), 2dsphere(需要索引)$nearSphere

javascript - MongooseJS - 限制子文档中的文档数量

将项目部署到 dokku 后删除 MongoDB 记录

mongodb - 如何从 mongodb 的文档中只删除一两个字段?

javascript - $group 嵌套数组元素

mongodb - 使用 insertMany 在 Mongodb 中插入一个文档

c - 查询mongodb C

javascript - 从值数组中计算不同的 MongoDb