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

{  
   "_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})

Or

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

返回匹配的文档(文档1),但总是使用形状中的ALL数组项:

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

但是,我想只获得包含color=red的数组的文档(文档1):

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

我该怎么做呢?


当前回答

MongoDB 2.2新的$elemMatch投影操作符提供了另一种方法来修改返回的文档,使其只包含第一个匹配的形状元素:

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

返回:

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

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

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

其他回答

与$project一起,其他明智的匹配元素将与文档中的其他元素组合在一起。

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

注意:这个答案提供的解决方案在当时是相关的,在MongoDB 2.2及更高版本的新特性引入之前。如果您使用的是最新版本的MongoDB,请参阅其他答案。

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

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

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

使用聚合函数和$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")
        }
    ]
}

更好的是,您可以使用$slice在匹配的数组元素中查询,这有助于返回数组中的重要对象。

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

当您知道元素的索引时,$slice是有用的,但有时您需要 匹配条件的数组元素。您可以返回匹配的元素 使用$操作符。

如果你想做筛选,设置和查找同时进行。

let post = await Post.findOneAndUpdate(
          {
            _id: req.params.id,
            tasks: {
              $elemMatch: {
                id: req.params.jobId,
                date,
              },
            },
          },
          {
            $set: {
              'jobs.$[i].performer': performer,
              'jobs.$[i].status': status,
              'jobs.$[i].type': type,
            },
          },
          {
            arrayFilters: [
              {
                'i.id': req.params.jobId,
              },
            ],
            new: true,
          }
        );