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

时间:2010-10-21 07:31:16

标签: mongodb mongodb-query aggregation-framework projection

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

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

查询:

db.test.find({"shapes.color": "red"}, {"shapes.color": 1})

或者

db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})

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

{ "shapes": 
  [
    {"shape": "square", "color": "blue"},
    {"shape": "circle", "color": "red"}
  ] 
}

但是,我想仅使用包含color=red的数组获取文档(文档1)

{ "shapes": 
  [
    {"shape": "circle", "color": "red"}
  ] 
}

我该怎么做?

14 个答案:

答案 0 :(得分:359)

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

db.test.find(
    {"shapes.color": "red"}, 
    {_id: 0, shapes: {$elemMatch: {color: "red"}}});

返回:

{"shapes" : [{"shape": "circle", "color": "red"}]}

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

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

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
    }}
])

结果:

[ 
    {
        "shapes" : [ 
            {
                "shape" : "circle",
                "color" : "red"
            }
        ]
    }
]

答案 1 :(得分:94)

MongoDB 2.2+中的新Aggregation Framework提供了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"
  }}
)

结果:

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

答案 2 :(得分:29)

  

警告:在引入MongoDB 2.2及更高版本的新功能之前,此答案提供了与当时相关的解决方案。如果您使用的是更新版本的MongoDB,请参阅其他答案。

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

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

如果你真的需要直接从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()

答案 3 :(得分:27)

另一种有趣的方法是使用$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"
         }
    }}]);

$redact “根据文档本身存储的信息限制文档内容”。因此它只会在文档中运行。它基本上扫描您的文档顶部到底部,并检查它是否与if中的$cond条件匹配,如果匹配,它将保留内容($$DESCEND)或除去($$PRUNE)。

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

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

答案 4 :(得分:18)

最好使用$slice查询匹配数组元素是否有助于返回数组中的重要对象。

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})
当您知道元素的索引时,

$slice会很有用,但有时您需要 无论哪个数组元素符合您的条件。您可以返回匹配元素 使用$运算符。

答案 5 :(得分:14)

 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

OUTPUTS

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}

答案 6 :(得分:11)

mongodb中find的语法是

    db.<collection name>.find(query, projection);

和您编写的第二个查询,即

    db.test.find(
    {shapes: {"$elemMatch": {color: "red"}}}, 
    {"shapes.color":1})

在此您已在查询部分中使用$elemMatch运算符,而如果在投影部分中使用此运算符,则您将获得所需的结果。您可以将查询写下来

     db.users.find(
     {"shapes.color":"red"},
     {_id:0, shapes: {$elemMatch : {color: "red"}}})

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

答案 7 :(得分: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"
    }
]}

答案 8 :(得分:6)

您只需要运行查询

db.test.find(
{"shapes.color": "red"}, 
{shapes: {$elemMatch: {color: "red"}}});

此查询的输出是

{
    "_id" : ObjectId("562e7c594c12942f08fe4192"),
    "shapes" : [ 
        {"shape" : "circle", "color" : "red"}
    ]
}

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

答案 9 :(得分:2)

与$ project一起,更合适的是其他明智的匹配元素将与文档中的其他元素一起使用。

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

答案 10 :(得分:2)

同样,您可以找到倍数

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']]}
     }}
  }}
])

答案 11 :(得分:1)

使用聚合函数和$project获取文档中的特定对象字段

db.getCollection('geolocations').aggregate([ { $project : { geolocation : 1} } ])

结果:

{
    "_id" : ObjectId("5e3ee15968879c0d5942464b"),
    "geolocation" : [ 
        {
            "_id" : ObjectId("5e3ee3ee68879c0d5942465e"),
            "latitude" : 12.9718313,
            "longitude" : 77.593551,
            "country" : "India",
            "city" : "Chennai",
            "zipcode" : "560001",
            "streetName" : "Sidney Road",
            "countryCode" : "in",
            "ip" : "116.75.115.248",
            "date" : ISODate("2020-02-08T16:38:06.584Z")
        }
    ]
}

答案 12 :(得分:1)

尽管这个问题是在9.6年前提出的,但它对无数人产生了巨大帮助,我就是其中之一。谢谢大家提出的所有疑问,提示和答案。从这里的答案之一中进行选择。我发现以下方法也可以用于投影父文档中的其他字段。这可能对某人有所帮助。

对于以下文档,需要确定雇员(emp#7839)是否设置了2020年的休假历史记录。休假历史记录作为父级Employee文档中的嵌入式文档实现。

db.employees.find( {"leave_history.calendar_year": 2020}, 
    {leave_history: {$elemMatch: {calendar_year: 2020}},empno:true,ename:true}).pretty()


{
        "_id" : ObjectId("5e907ad23997181dde06e8fc"),
        "empno" : 7839,
        "ename" : "KING",
        "mgrno" : 0,
        "hiredate" : "1990-05-09",
        "sal" : 100000,
        "deptno" : {
                "_id" : ObjectId("5e9065f53997181dde06e8f8")
        },
        "username" : "none",
        "password" : "none",
        "is_admin" : "N",
        "is_approver" : "Y",
        "is_manager" : "Y",
        "user_role" : "AP",
        "admin_approval_received" : "Y",
        "active" : "Y",
        "created_date" : "2020-04-10",
        "updated_date" : "2020-04-10",
        "application_usage_log" : [
                {
                        "logged_in_as" : "AP",
                        "log_in_date" : "2020-04-10"
                },
                {
                        "logged_in_as" : "EM",
                        "log_in_date" : ISODate("2020-04-16T07:28:11.959Z")
                }
        ],
        "leave_history" : [
                {
                        "calendar_year" : 2020,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                },
                {
                        "calendar_year" : 2021,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                }
        ]
}

答案 13 :(得分:0)

=Iif(Fields!pack.Value = 0 Or Fields!units.Value = 0,0,CDBL((Fields!AVAIL.Value/Fields!pack.Value)/Fields!units.Value))