仅返回文档中的键,其中查询条件为True

时间:2017-06-15 10:54:23

标签: 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 equl中的条件为122然后只有lname对象将返回rest将被忽略。

4 个答案:

答案 0 :(得分:3)

您要求的东西只有MongoDB 3.4才真正“实用”才能从服务器返回。

摘要

这里的一般情况是,逻辑条件对字段的“投影”并不简单。虽然如果MongoDB有这样的DSL用于投影会很好,但这基本上被委托给:

  • 在“从服务器返回结果”之后“操作”

  • 使用聚合管道来操作文档。

因此,在“CASE B”是“聚合管道”中,如果涉及的步骤“模仿”“查询”和“项目”的标准.find()行为,这实际上只是一个实际的练习。引入其他管道阶段之外只会引入性能问题,这大大超过了“修剪”要返回的文档的任何收益。

因此,此处的摘要为$match,然后$newRoot为“项目”,遵循模式。我认为这也是一个好的“经验法则”,在这里考虑聚合方法“只应该”应用于返回数据量大幅减少的情况。我将通过示例扩展说明“if”“trim”键的大小实际上是返回结果的兆字节范围,那么在服务器上删除它们是值得的练习”

在这种保存实际上只构成比较的“字节”的情况下,那么最合乎逻辑的过程就是简单地允许文档在光标“未修改”中返回,然后才在“后处理”中你会不会删除不符合逻辑条件的不需要的密钥。

那说,用实际的方法。

聚合案例

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来实现动态结构。核心的“键”都以数组形式表示,其中“数组”仅包含实际传递条件的那些键。

在条件过滤后完全构造我们将数组转换为文档并将投影返回到新的根。

光标处理案例

您可以轻松地在客户端代码中执行此操作。例如,在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重用

解决塞尔吉奥的问题,我想你实际上可以在$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;
})

在没有“硬编码”(有些)if语句的情况下评估DSL以实际执行操作;

然后聚合方法也将是:

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生成所需管道部分的基本条件相同,而不是硬编码。

答案 1 :(得分:0)

要查找的第二个参数指定要返回的字段(投影)

db.collection.find(query, projection)

https://docs.mongodb.com/manual/reference/method/db.collection.find/

如示例

db.bios.find( { }, { name: 1, contribs: 1 } )

答案 2 :(得分:0)

db.temp.find({
  "$elemMatch": "$or"[
    {
      'name.time': {
        $gte: 123
      }
    },
    {
      'lname.time': {
        $gte: 123
      }
    },
    {
      'sname.time': {
        $gte: 123
      }
    }
  ]
},
{
  {
    "name.time": 1,
    "lname.time": 1,
    "sname.time": 1
  }
}
})

答案 3 :(得分:0)

我使用聚合管道的方法

$project - 项目用于为文档名称创建密钥,sname和lname

初始项目查询

db.collection.aggregate([{$project: {_id:1, "tempname.name": "$name", "templname.lname":"$lname", "tempsname.sname":"$sname"}}]);

此查询的结果是

{"_id":ObjectId("5942643ea2042e12245de00c"),"tempname":{"name":{"value":"roy","time":121}},"templname":{"lname":{"value":"roy s","time":122}},"tempsname":{"sname":{"value":"roy 9","time":123}}}

再次使用$project将文档添加到数组中

db.collection.aggregate([{$project: {_id:1, "tempname.name": "$name", "templname.lname":"$lname", "tempsname.sname":"$sname"}}, 
{$project: {names: ["$tempname", "$templname", "$tempsname"]}}])

执行第二个项目后,我们的文件将是这样的

{"_id":ObjectId("5942643ea2042e12245de00c"),"names":[{"name":{"value":"roy","time":121}},{"lname":{"value":"roy s","time":122}},{"sname":{"value":"roy 9","time":123}}]}

然后使用$unwind将数组拆分为单独的文档

打破文件后,使用$match和$获得所需的结果

**

  

最终查询

**

db.collection.aggregate([
  {
    $project: {
      _id: 1,
      "tempname.name": "$name",
      "templname.lname": "$lname",
      "tempsname.sname": "$sname"
    }
  },
  {
    $project: {
      names: [
        "$tempname",
        "$templname",
        "$tempsname"
      ]
    }
  },
  {
    $unwind: "$names"
  },
  {
    $match: {
      $or: [
        {
          "names.name.time": {
            $gte: 123
          }
        },
        {
          "names.lname.time": {
            $gte: 123
          }
        },
        {
          "names.sname.time": {
            $gte: 123
          }
        }
      ]
    }
  }
])

查询的最终结果更接近您的预期结果(使用附加键)

{
        "_id" : ObjectId("5942643ea2042e12245de00c"),
        "names" : {
                "sname" : {
                        "value" : "roy 9",
                        "time" : 123
                }
        }
}