仅检索MongoDB集合中对象数组中的查询元素

Seb*_*btm 356 projection mongodb mongodb-query aggregation-framework

假设我的收藏中有以下文件:

{  
   "_id":ObjectId("562e7c594c12942f08fe4192"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"blue"
      },
      {  
         "shape":"circle",
         "color":"red"
      }
   ]
},
{  
   "_id":ObjectId("562e7c594c12942f08fe4193"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"black"
      },
      {  
         "shape":"circle",
         "color":"green"
      }
   ]
}
Run Code Online (Sandbox Code Playgroud)

查询:

db.test.find({"shapes.color": "red"}, {"shapes.color": 1})
Run Code Online (Sandbox Code Playgroud)

要么

db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})
Run Code Online (Sandbox Code Playgroud)

返回匹配的文档(文档1),但始终包含所有数组项shapes:

{ "shapes": 
  [
    {"shape": "square", "color": "blue"},
    {"shape": "circle", "color": "red"}
  ] 
}
Run Code Online (Sandbox Code Playgroud)

但是,我想仅使用包含以下内容的数组来获取文档(文档1)color=red:

{ "shapes": 
  [
    {"shape": "circle", "color": "red"}
  ] 
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?

Joh*_*yHK 386

MongoDB 2.2的新$elemMatch投影操作符提供了另一种方法来更改返回的文档以仅包含第一个匹配的shapes元素:

db.test.find(
    {"shapes.color": "red"}, 
    {_id: 0, shapes: {$elemMatch: {color: "red"}}});
Run Code Online (Sandbox Code Playgroud)

返回:

{"shapes" : [{"shape": "circle", "color": "red"}]}
Run Code Online (Sandbox Code Playgroud)

在2.2中,您也可以使用$ projection operator,其中$投影对象字段名称表示字段中查询的第一个匹配数组元素的索引.以下返回与上面相同的结果:

db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});
Run Code Online (Sandbox Code Playgroud)

MongoDB 3.2更新

从3.2版本开始,您可以使用新的$filter聚合运算符在投影期间过滤数组,这样可以包含所有匹配,而不仅仅是第一个匹配.

db.test.aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
    {$match: {'shapes.color': 'red'}},
    {$project: {
        shapes: {$filter: {
            input: '$shapes',
            as: 'shape',
            cond: {$eq: ['$$shape.color', 'red']}
        }},
        _id: 0
    }}
])
Run Code Online (Sandbox Code Playgroud)

结果:

[ 
    {
        "shapes" : [ 
            {
                "shape" : "circle",
                "color" : "red"
            }
        ]
    }
]
Run Code Online (Sandbox Code Playgroud)

  • 任何解决方案,如果我希望它返回匹配它的每个元素而不是第一个? (13认同)
  • 这也有效:`db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});` (2认同)

Ste*_*nie 95

MongoDB 2.2+中的新聚合框架提供了Map/Reduce的替代方案.该$unwind操作可用于分离的shapes阵列到的文件流可以匹配:

db.test.aggregate(
  // Start with a $match pipeline which can take advantage of an index and limit documents processed
  { $match : {
     "shapes.color": "red"
  }},
  { $unwind : "$shapes" },
  { $match : {
     "shapes.color": "red"
  }}
)
Run Code Online (Sandbox Code Playgroud)

结果是:

{
    "result" : [
        {
            "_id" : ObjectId("504425059b7c9fa7ec92beec"),
            "shapes" : {
                "shape" : "circle",
                "color" : "red"
            }
        }
    ],
    "ok" : 1
}
Run Code Online (Sandbox Code Playgroud)

  • @JohnnyHK:在这种情况下,`$ elemMatch`是另一种选择.我实际上是通过[Google群组问题](https://groups.google.com/forum/?fromgroups#!topic/mongodb-user/gASUwWbxN3w)来到这里的,其中$ elemMatch无效,因为它只返回每个文件的第一场比赛 (6认同)
  • @JohnnyHK:不用担心,这个问题现在有三个有用的答案;-) (3认同)
  • @calmbird我更新了示例以包含初始$ match阶段.如果您对更有效的功能建议感兴趣,我会观察/ upvote [SERVER-6612:支持在$ elemMatch投影说明符的投影中投影多个数组值](https://jira.mongodb.org/browse/SERVER -6612)在MongoDB问题跟踪器中. (2认同)

Nie*_*est 29

注意:这个答案提供了一个解决方案,这是有关在那个时候,引入了之前的MongoDB 2.2的新功能和最多.如果您使用的是更新版本的MongoDB,请参阅其他答案.

字段选择器参数仅限于完整属性.它不能用于选择数组的一部分,只能用于整个数组.我尝试使用$ position运算符,但这不起作用.

最简单的方法是只过滤客户端中的形状.

如果您确实需要直接从MongoDB输出正确的输出,可以使用map-reduce来过滤形状.

function map() {
  filteredShapes = [];

  this.shapes.forEach(function (s) {
    if (s.color === "red") {
      filteredShapes.push(s);
    }
  });

  emit(this._id, { shapes: filteredShapes });
}

function reduce(key, values) {
  return values[0];
}

res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } })

db[res.result].find()
Run Code Online (Sandbox Code Playgroud)


anv*_*rik 28

另一种有趣的方法是使用$ redact,这是MongoDB 2.6的新聚合功能之一.如果您使用的是2.6,则不需要$ unwind,如果您有大型数组,可能会导致性能问题.

db.test.aggregate([
    { $match: { 
         shapes: { $elemMatch: {color: "red"} } 
    }},
    { $redact : {
         $cond: {
             if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]},
             then: "$$DESCEND",
             else: "$$PRUNE"
         }
    }}]);
Run Code Online (Sandbox Code Playgroud)

$redact "根据文件本身存储的信息限制文件的内容".所以它只会在文档内部运行.它基本上扫描你的文档顶部到底部,并检查它是否与你的if条件匹配$cond,如果有匹配,它将保留content($$DESCEND)或remove($$PRUNE).

在上面的示例中,首先$match返回整个shapes数组,$ redact将其删除到预期结果.

请注意,这{$not:"$color"}是必要的,因为它也将扫描顶层文档,如果在顶层$redact找不到color字段,则返回false可能会删除我们不想要的整个文档.

  • 辉煌!我不明白$ eq如何在这里工作.我最初把它关了,这对我不起作用.不知何故,它在形状数组中查找匹配,但查询从不指定要查找的数组.例如,如果文档具有形状,例如大小; $ eq会在两个数组中查找匹配吗?$ redact只是在文档中查找与"if"条件匹配的任何内容吗? (2认同)

小智 21

更好的是你可以使用匹配的数组元素进行查询$slice是否有助于返回数组中的重要对象.

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})
Run Code Online (Sandbox Code Playgroud)

$slice当你知道元素的索引时很有用,但有时候你想要哪个数组元素符合你的标准.您可以使用$运算符返回匹配元素.


Vir*_*tel 16

 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})
Run Code Online (Sandbox Code Playgroud)

产出

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}
Run Code Online (Sandbox Code Playgroud)


Vic*_*cky 11

在mongodb中查找的语法是

    db.<collection name>.find(query, projection);
Run Code Online (Sandbox Code Playgroud)

和你写的第二个查询,即

    db.test.find(
    {shapes: {"$elemMatch": {color: "red"}}}, 
    {"shapes.color":1})
Run Code Online (Sandbox Code Playgroud)

在这里你已经$elemMatch在查询部分使用了运算符,而如果你在投影部分使用这个运算符,那么你将得到所需的结果.您可以将查询写下来

     db.users.find(
     {"shapes.color":"red"},
     {_id:0, shapes: {$elemMatch : {color: "red"}}})
Run Code Online (Sandbox Code Playgroud)

这将为您提供所需的结果.

  • @ErikOlson您的建议在上述情况下是正确的,我们需要找到所有红色文档并仅在其上应用投影.但是,如果有人要求找出所有具有蓝色的文档,但它应该只返回那些形状数组中具有红色的元素.在这种情况下,上述查询也可以由其他人引用. (2认同)

Edd*_*ddy 7

感谢JohnnyHK.

在这里,我只想添加一些更复杂的用法.

// Document 
{ 
"_id" : 1
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 

{ 
"_id" : 2
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 


// The Query   
db.contents.find({
    "_id" : ObjectId(1),
    "shapes.color":"red"
},{
    "_id": 0,
    "shapes" :{
       "$elemMatch":{
           "color" : "red"
       } 
    }
}) 


//And the Result

{"shapes":[
    {
       "shape" : "square",
       "color" : "red"
    }
]}
Run Code Online (Sandbox Code Playgroud)


Vai*_*til 6

您只需要运行查询

db.test.find(
{"shapes.color": "red"}, 
{shapes: {$elemMatch: {color: "red"}}});
Run Code Online (Sandbox Code Playgroud)

此查询的输出是

{
    "_id" : ObjectId("562e7c594c12942f08fe4192"),
    "shapes" : [ 
        {"shape" : "circle", "color" : "red"}
    ]
}
Run Code Online (Sandbox Code Playgroud)

如你所料,它将从匹配颜色的数组中提供确切的字段:'red'.


sha*_*oss 5

除此之外, $project其他明智的匹配元素将与文档中的其他元素组合在一起会更合适。

db.test.aggregate(
  { "$unwind" : "$shapes" },
  { "$match" : { "shapes.color": "red" } },
  { 
    "$project": {
      "_id":1,
      "item":1
    }
  }
)
Run Code Online (Sandbox Code Playgroud)


ash*_*ber 5

同样你可以找到多个

db.getCollection('localData').aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
  {$match: {'shapes.color': {$in : ['red','yellow'] } }},
  {$project: {
     shapes: {$filter: {
        input: '$shapes',
        as: 'shape',
        cond: {$in: ['$$shape.color', ['red', 'yellow']]}
     }}
  }}
])
Run Code Online (Sandbox Code Playgroud)

  • 这个答案确实是首选的 4.x 方式: `$match` 来减少空间,然后 `$filter` 来保留你想要的内容,覆盖输入字段(在字段 `shapes` 上使用 `$filter` 的输出来`$project` 回到 `shapes`。风格注释:最好不要使用字段名称作为 `as` 参数,因为这可能会导致稍后与 `$$shape` 和 `$shape` 发生混淆。我更喜欢 `zz ` 作为 `as` 字段,因为它确实很突出。 (2认同)