首页 文章

优雅地返回在MongoDb聚合结果中满足elemMatch的Subdocuments [重复]

提问于
浏览
0

这个问题在这里已有答案:

我已经尝试了几种创建聚合管道的方法,它只返回文档嵌入式数组中的匹配条目,但没有找到任何实用的方法 .

是否有一些MongoDB功能可以避免我非常笨拙和容易出错的方法?

'workshop'系列中的文档看起来像这样......

{
  "_id": ObjectId("57064a294a54b66c1f961aca"),
  "type": "normal",
  "version": "v1.4.5",
  "invitations": [],
  "groups": [
    {
      "_id": ObjectId("57064a294a54b66c1f961acb"),
      "role": "facilitator"
    },
    {
      "_id": ObjectId("57064a294a54b66c1f961acc"),
      "role": "contributor"
    },
    {
      "_id": ObjectId("57064a294a54b66c1f961acd"),
      "role": "broadcaster"
    },
    {
      "_id": ObjectId("57064a294a54b66c1f961acf"),
      "role": "facilitator"
    }
  ]
}

groups数组中的每个条目都提供一个唯一的ID,以便当组成员在使用该salted ID访问URL时为其分配给定角色 .

给定_id匹配组数组中的条目,如 ObjectId("57064a294a54b66c1f961acb") ,我需要从聚合管道返回这样的单个记录 - 基本上只返回嵌入的组数组中的匹配条目 .

{
      "_id": ObjectId("57064a294a54b66c1f961acb"),
      "role": "facilitator",
      "workshopId": ObjectId("57064a294a54b66c1f961aca")
    },

在此示例中,workshopId已添加为标识父文档的额外字段,但其余字段应为原始组条目中具有匹配_id的所有字段 .

我采用的方法可以实现这一点,但有很多问题,可能效率低下(重复过滤子句) .

return workshopCollection.aggregate([
    {$match:{groups:{$elemMatch:{_id:groupId}}}},
    {$unwind:"$groups"},
    {$match:{"groups._id":groupId}},
    {$project:{
        _id:"$groups._id",
        role:"$groups.role",
        workshopId:"$_id",
    }},
]).toArray();

更糟糕的是,由于它明确包含条目中的命名字段,因此将省略添加到记录中的任何未来字段 . 我也无法将此查找操作概括为“邀请”或其他嵌入式命名数组的情况,除非我事先知道数组条目的字段 .

我想知道在管道的$ project阶段中使用$或$ elemMatch运算符是否是正确的方法,但到目前为止,它们要么被忽略,要么在运行管道时触发运算符有效性错误 .

是否有另一种聚合运算符或替代方法可以帮助我解决这个相当主流的问题 - 只返回文档数组中的匹配条目?

1 回答

  • 0

    下面的实现可以处理任意查询,将结果作为“顶级文档”提供,并避免在管道中进行重复过滤 .

    function retrieveArrayEntry(collection, arrayName, itemMatch){
        var match = {};
        match[arrayName]={$elemMatch:itemMatch};
        var project = {};
        project[arrayName+".$"] = true;
        return collection.findOne(
            match,
            project
        ).then(function(doc){
            if(doc !== null){
                var result = doc[arrayName][0];
                result._docId = doc._id;
                return result;
            }
            else{
                return null;
            }
        });
    }
    

    它可以这样调用......

    retrieveArrayEntry(workshopCollection, "groups", {_id:ObjectId("57064a294a54b66c1f961acb")})
    

    但是,它依赖于集合findOne(...)方法而不是聚合(...),因此将限制为从第一个匹配文档提供第一个匹配的数组条目 . 引用数组匹配子句的投影显然不可能通过聚合(...)以与通过findXXX()方法相同的方式实现 .

    更通用(但令人困惑和低效)的实现允许检索多个匹配的文档和子文档 . 它通过unpackMatch方法解决了MongoDb与Document和Subdocument匹配的语法一致性的困难,因此不正确的“相等”标准,例如: ...

    {greetings:{_id:ObjectId("437908743")}}
    

    ...转换为'match'标准所需的语法(如Within a mongodb $match, how to test for field MATCHING , rather than field EQUALLING中所述)...

    {"greetings._id":ObjectId("437908743")}
    

    导致以下实施......

    function unpackMatch(pathPrefix, match){
        var unpacked = {};
        Object.keys(match).map(function(key){
            unpacked[pathPrefix + "." + key] = match[key];
        })
        return unpacked;
    }
    
    function retrieveArrayEntries(collection, arrayName, itemMatch){
    
        var matchDocs = {},
            projectItems = {},
            unwindItems = {},
            matchUnwoundByMap = {};
    
        matchDocs.$match={};
        matchDocs.$match[arrayName]={$elemMatch:itemMatch};
    
        projectItems.$project = {};
        projectItems.$project[arrayName]=true;
    
        unwindItems.$unwind = "$" + arrayName;
    
        matchUnwoundByMap.$match = unpackMatch(arrayName, itemMatch);
    
        return collection.aggregate([matchDocs, projectItems, unwindItems, matchUnwoundByMap]).toArray().then(function(docs){
            return docs.map(function(doc){
                var result = doc[arrayName];
                result._docId = doc._id;
                return result;
            });
        });
    }
    

相关问题