嵌套嵌入式文档中的$ elemMatch

时间:2017-05-22 22:58:08

标签: mongodb mongoose mongodb-query aggregation-framework mongoose-schema

在我的Mongoose中,我有以下架构..

const RobotSchema = new Schema({
  name: { type: String },
});

const RobotClassRoomSchema = new Schema({
  robot: { type: Schema.Types.ObjectId, ref: 'Robot' },
});

const ClassRoomSchema = new Schema({
  robots: [{ type: Schema.Types.ObjectId, ref: 'RobotClassRoom' }],
});

如何验证我的Robot中是否有59226c258a4a131c6828841eClassRoom

尝试

enter image description here

但我有机器人......

enter image description here

enter image description here

1 个答案:

答案 0 :(得分:2)

您的架构正在使用对另一个集合中的对象的引用。由于MongoDB本身实际上只是一次查询一个集合,因此引用了#34;您正在查询的集合中不存在数据。

所有该集合都知道它有一个"数组"包含ObjectId值的字段,实际上对MongoDB本身没有任何意义,唯一知道ObjectId值实际引用的位置的东西是在你的mongoose模式中定义的客户端代码" 。所以服务器当然不知道这个。

使用MongoDB

在MongoDB的现代版本(至少3.2版)中,您可以使用$lookup聚合管道运算符来执行" join" (或者说是#34;查找")在服务器上。此语法允许您定义您想要的集合"查看"对于在数组中找到的值,结果将从引用的集合中将匹配的对象检索到您的文档中。

数组的问题在于,根据您的版本,可能需要使用$unwind进行处理才能进行匹配。 MongoDB 3.2要求数组是" un-wound",但是在MongoDB 3.4中你应该能够做到这一点:

db.getCollection('classrooms').aggregate([
    { "$lookup": {
        "from": "robotclassrooms",
        "localField": "robots",
        "foreignField": "_id"
        "as": "robots"
    }},
    { "$redact": {
      "$cond": {
        "if": {
           "$anyElementTrue": {
             "$map": {
               "input": "$robots",
               "as": "r",
               "in": { "$eq": [ "$$r.robot", ObjectId("59226c258a4a131c6828841e") ] }
             }
           }
        },
        "then": "$$KEEP",
        "else": "$$PRUNE"
      }
    }}
])

使用$unwind

db.getCollection('classrooms').aggregate([
    { "$unwind": "$robots" },
    { "$lookup": {
        "from": "robotclassrooms",
        "localField": "robots",
        "foreignField": "_id"
        "as": "robots"
    }},
    { "$unwind": "$robots" },
    { "$group": {
      "_id": "_id",
      "updatedAt": { "$first": "$updatedAt" },
      "createdAt": { "$first": "$createdAt" },
      "code": { "$first": "$code" },
      "createdBy": { "$first": "$createdBy" },
      "robots": { "$push": "$robots" }
    }},
    { "$redact": {
      "$cond": {
        "if": {
           "$anyElementTrue": {
             "$map": {
               "input": "$robots",
               "as": "r",
               "in": { "$eq": [ "$$r.robot", ObjectId("59226c258a4a131c6828841e") ] }
             }
           }
        },
        "then": "$$KEEP",
        "else": "$$PRUNE"
      }
    }}
])

注意在这种情况下$unwind的双重用法,因为MongoDB 3.2不仅要求在处理之前解开数组,而且结果也是"数组",即使它可能只匹配单个值。 $lookup运算符不区分单数和多重匹配,因为始终返回数组。在任何版本中。

因此,使用$group进行后续处理以恢复文档结构。我没有在这里复制所有你的文档属性,但只是足够了,所以你得到了一般的" gist"。

使用Mongoose

实际询问"服务器"的替代流程要做这个动作就是使用" mongoose"来使代码变白。做一个"填充查询"。这是一个"客户端仿真" "加入",其中实际发生的事情是对数据库的多个请求,其结果为"合并"在代码中。

当然,作为"客户端"操作对此有一些限制。最值得注意的是,服务器将返回所有结果,无论它们是否符合您的条件,并且在"客户端代码"中评估条件。

Classroom.find()
  .populate({
    path: 'robots',
    match: { 'robot': '59226c258a4a131c6828841e' }
  }).exec(function(err,docs) {
     // docs has all classrooms but some robots arrays will be empty

  });

所以这里的问题是外翻"教室"始终返回文档,即使条件仅为"填充"那些来自" robotsclassrooms"与给定的查询条件匹配。

结果是"教室"具有"空数组的对象"不符合查询的地方。然后你需要反击,但过滤"生成的空数组的课堂文档,如下:

Classroom.find()
  .populate({
    path: 'robots',
    match: { 'robot': '59226c258a4a131c6828841e' }
  }).exec(function(err,docs) {
      docs = docs.filter(function(doc) { return doc.robots.length > 0 })
      // Now docs is only matching documents containing the requested ObjectId value
  });

结论

这些基本上是您的选择,您使用哪一个取决于您的方法,当然还有可用的服务器功能。

请注意,这些过程是"查找与数组中的元素匹配的文档",这是与#34;不同的语句;只返回数组的匹配元素"。有不同的技术可以建立在"这里的过程,但基本上涉及"过滤"数组内容也是如此。例如,在聚合框架中,您可以应用$filter作为运算符来执行该功能。

备注:

另请参阅示例中使用的其他运算符的Aggregation Operators的完整列表。