ChatGPT解决这个技术问题 Extra ChatGPT

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

假设您在我的收藏中有以下文档:

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

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

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

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

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

我怎样才能做到这一点?


F
Faraz

MongoDB 2.2 的新 $elemMatch 投影运算符提供了另一种方法来更改返回的文档以仅包含 first 匹配的 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"
            }
        ]
    }
]

如果我希望它返回匹配它的每个元素而不是第一个元素,有什么解决方案吗?
恐怕我正在使用 Mongo 3.0.X :-(
@charliebrownie 然后使用使用 aggregate 的其他答案之一。
这也有效:db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});
这是一个错误:$$shape.color? $filter 条件下的双 $$。
S
Stennie

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
}

@JohnnyHK:在这种情况下,$elemMatch 是另一种选择。我实际上是通过 Google Group question 到达这里的,其中 $elemMatch 不起作用,因为它只返回每个文档的第一个匹配项。
谢谢,我不知道这个限制,所以很高兴知道。很抱歉删除您正在回复的我的评论,我决定发布另一个答案,并且不想让人们感到困惑。
@JohnnyHK:不用担心,这个问题现在有三个有用的答案;-)
对于其他搜索者,除此之外,我还尝试添加 { $project : { shapes : 1 } } - 如果包含的文档很大并且您只想查看 shapes 键值,这似乎有效并且会很有帮助。
@calmbird 我更新了示例以包含初始 $match 阶段。如果您对更有效的功能建议感兴趣,我会在 MongoDB 问题跟踪器中观看/支持 SERVER-6612: Support projecting multiple array values in a projection like the $elemMatch projection specifier
N
Niels van der Rest

警告:在 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()

a
anvarik

另一种有趣的方法是使用 $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 “根据文档本身存储的信息限制文档的内容”。所以它只会在文档内部运行。它基本上从上到下扫描您的文档,并检查它是否与您在 $cond 中的 if 条件匹配,如果匹配,它将保留内容($$DESCEND)或删除($$PRUNE) .

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

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


完美的答案。正如你提到的 $unwind 会消耗大量的内存。所以这样比较会更好。
我有个疑问。在示例中,“shapes”是一个数组。 “$redact”会扫描“shapes”数组中的所有对象吗?这在性能方面会有什么好处?
不是全部,而是你第一场比赛的结果。这就是您将 $match 作为第一个聚合阶段的原因
okkk .. 如果在“颜色”字段上创建索引,即使那样它也会扫描“形状”数组中的所有对象???这可能是匹配数组中多个对象的有效方法???
杰出的!我不明白 $eq 在这里是如何工作的。我最初把它关掉了,这对我不起作用。不知何故,它会在形状数组中查找匹配项,但查询从不指定要查找的数组。例如,如果文档具有形状,例如大小; $eq 会在两个数组中查找匹配项吗? $redact 是否只是在文档中查找与“if”条件匹配的任何内容?
E
Egor Neliuba

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

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

当您知道元素的索引时,$slice 会很有帮助,但有时您需要与您的条件匹配的数组元素。您可以使用 $ 运算符返回匹配元素。


它是返回所有包含 shapes.color : blue 的文档还是只返回第一个?
V
Viral Patel
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

输出

{

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

感谢您的查询,但即使条件与数组中的多个元素匹配,它也只是返回第一个,有什么建议吗?
J
Jinxcat

在 mongodb 中查找的语法是

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

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


这对我有用。但是,查询参数(find 方法的第一个参数)中的 "shapes.color":"red" 似乎不是必需的。您可以将其替换为 {} 并获得相同的结果。
@ErikOlson您的建议在上述情况下是正确的,我们需要找到所有带有红色的文档并仅在它们上应用投影。但是,假设有人需要找出所有具有蓝色的文档,但它应该只返回该形状数组中具有红色的那些元素。在这种情况下,上面的查询也可以被其他人引用..
这似乎是最简单的,但我无法让它发挥作用。它只返回第一个匹配的子文档。
@MahmoodHussain 这个答案已经快 7 岁了,所以可能是版本问题。你能检查最新的文档吗?我将尝试在最新版本上运行类似并分享我的发现。你能解释一下你到底想要达到什么目的吗?
@Vicky Patient.find( { user: req.user._id, _id: req.params.patientId, "tests.test": req.params.testId, }, { "tests.$": 1, name: 1, } ) .populate({ path: "tests", populate: { path: "test", model: "Test", }, }) .exec((err, patient) => { if (err || !patient) { return res.status(404).send({ error: { message: err } }); } return res.send({ patient }); }); 但随后填充抛出错误
C
Community

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

a
azhar

你只需要运行查询

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

这个查询的输出是

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

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


t
turivishal

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

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

你能描述一下这是通过输入和输出集完成的吗?
a
ashishSober

同样,您可以找到多个

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

这个答案确实是首选的 4.x 方式:$match 减少空间,然后 $filter 保留您想要的内容,覆盖输入字段(在字段 shapes 上使用 $filter 的输出到 $project回到 shapes。样式注意:最好不要将字段名称用作 as 参数,因为这可能会导致以后与 $$shape$shape 混淆。我更喜欢 zz 作为 as 字段因为它真的很突出。
S
Suraj Rao
db.test.find( {"shapes.color": "red"}, {_id: 0})

欢迎来到堆栈溢出!感谢您提供代码片段,它可能会提供一些有限的即时帮助。通过描述为什么这是解决问题的好方法,正确的解释将极大地改进其 long-term value,并使其对有其他类似问题的未来读者更有用。请编辑您的答案以添加一些解释,包括您所做的假设。
K
KARTHIKEYAN.A

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

A
Ali

尽管这个问题是在 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
                }
        ]
}

R
Rafiq

如果要同时进行过滤,设置和查找。

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,
          }
        );