MongoDB:选择所有字段+所有匹配的子集合元素

时间:2015-07-21 17:31:09

标签: node.js mongodb select field elements

这是我的猫鼬系列中的一个文件:

{ _id: 55ae7be99e772a7c025a0a7b,
   id: 'foo',
   isBoolean: true,
   kind: 'bar',
   values: 
     [ 
       { y: 0,
         x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit),
         _id: 55ae7ae05596dd740eb8a204 },
       { y: 0,
         x: Wed Aug 26 2015 11:12:57 GMT+0200 (Mitteleuropäische Sommerzeit),
         _id: 55ae7ae05596dd740eb8a203 }, 
       { y: 1,
         x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit);
       _id: 55ae7be91fa1511c1795c5ae } 
     ]
  }

所以,我需要查找具有特定值的所有文档。之后,我需要返回包含所有字段并找到值元素的文档。

Wenn我试试

.find({'values.x': mTime1})
      .select({
          '_id'        : 1 ,
          'id'         : 1 ,
          'kind'       : 1 ,
          'isBoolean'  : 1 ,
          'values'     : {$elemMatch: {x: time1}}
          })

我只收到第一个找到的值:

 { ...
     values: 
      [ { exceeds: null,
         y: 0,
         x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit),
         _id: 55ae7d86870b92b8056bed4c } ]
    }

下一个版本

.aggregate({"$unwind" : "$values"}, {"$match" : {"values.x": time1}},
             {"$group" : {
                    '_id'        : '$_id',
                    'values'     : {$addToSet: "$values"}
             });

返回除其他字段以外的所有匹配值...

我的目标是:

{ _id: 55ae7be99e772a7c025a0a7b,
   id: 'foo',
   isBoolean: true,
   kind: 'bar',
   values: 
     [ 
       { y: 0,
         x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit),
         _id: 55ae7ae05596dd740eb8a204 },
       { y: 1,
         x: Wed Aug 26 2015 11:12:56 GMT+0200 (Mitteleuropäische Sommerzeit);
       _id: 55ae7be91fa1511c1795c5ae } 
     ]
  }

你有什么想法,如何用猫鼬实现这一目标? :)

更新: 感谢tsturzl,我使用下一个函数解决了它(不更改模型):

 self.aggregate(
                  {'$unwind' : '$values'}, 
                  {'$match' : { 'values.x': mTime1} },
                  {'$group' : {
                    '_id'        : '$_id',
                    'values'     : {$push: '$values'}
                  }}
              )
      .exec(
          function(err, results) {
            if(err) return done(err);

            var values = {}; // hashMap to group values

            results.forEach(function(item) {
              if(item.values) // prevent empty results
                values[item._id] = item.values;
            });

            self.find({_id:{$in: _.keys(values)}})
                .exec(function(err, items) {
                    if(err) return done(err);

                    var results = items.map(function(item) {
                        item.values = values[item._id];
                        return item;
                    });

                    done(err, results); // callback results
                });
        });

1 个答案:

答案 0 :(得分:1)

在投影中使用elemMatch的问题是它访问单个项目。与数组arr[1]类似,elemMatch获取数组中项的索引,然后将该数组项投射到该索引处。因此,您只能使用此方法检索一个子文档。

您可以使用类似于此

的聚合
[
    {$match: {'values.x': mTime1}}, //match before to reduce size of unwound 
    {$unwind: '$values'},
    {$match: {'values.x': mTime1}},
    {$group: {
        _id: '$_id',
        id: {$first: '$id'},
        kind: {$first: '$kind'},
        isBoolean: {$first: '$isBoolean'},
        values: {$push: '$values'}
    }
]

我已经测试过这个在子文档数组上本地工作正常。

您的方法可能最适合重组。您应该重新构建数据,以便您的值具有_id自己的集合和引用。在这个原因中,我会将引用存储在值集合中。

从此集合中删除values字段

{ _id: 55ae7be99e772a7c025a0a7b,
   id: 'foo',
   isBoolean: true,
   kind: 'bar'
}

值模型:

{
   y: Number,
   x: Date,
   parentId: {type: ObjectId, ref: "myColl"} //make sure you require ObjectId and rename the reference
}

然后你可以做这样的事情

ValuesModel.find({
    x: mTime1
}).exec(function(err, results) {
    var ids = {}; //hashMap to group values
    var idsArr = []; //array of ids

    results.forEach(function(item) {
        if(!ids.hasOwnProperty(items.parentId.toString())) {
            ids[items.parentId.toString()] = [];
            idArr.push(item.parentId);
        }
        ids[items._id.toString()].push(item);
    });

    myColl.find({_id:{$in: idsArr}})
        .exec(function(err, items) {
            var results = items.map(function(item) {
                item.values = ids[item._id.toString()];
                return item;
            });

            done(results); //callback results
        });
});

这将获取您查询的所有值,然后将它们分组到hashMap中并将所有parentId推送到数组。然后我查询该parentIds数组。我使用hashMap,通过hashMap中的id引用它,并在父文档中为.values创建一个新字段。这将阻止您必须使用不可扩展的聚合,并允许您轻松查询值表。如果只想找到一个值,可以使用mongoose populate方法。这种方法的缺点是你需要在代码中做更多的工作,并且你有2次往返。但是,这应该比聚合更有效。

如果您经常查询值

,这可以用来创建一个可重用的方法来简化代码
function queryValues(query, done) {
    ValuesModel.find(query).exec(function(err, results) {
        if(err) return done(err);

        var ids = {}; //hashMap to group values
        var idsArr = []; //array of ids
        results.forEach(function(item) {
            if(!ids.hasOwnProperty(items.parentId.toString())) {
                ids[items.parentId.toString()] = [];
                idArr.push(item.parentId);
            }
            ids[items._id.toString()].push(item);
        });

        myColl.find({_id:{$in: idsArr}})
            .exec(function(err, items) {
                if(err) return done(err);

                var results = items.map(function(item) {
                    item.values = ids[item._id.toString()];
                    return item;
                });

                done(null, results); //callback results
            });
    });
}

然后你可以调用queryValues({x: mTime1}, function(err, results){...});,你可以传递你想要的任何查询,该函数将处理填充父文档而不需要获取重复数据以获得最大效率。

我可能还建议您在模型定义中将此方法定义为模式静态方法,这样您就可以将这些代码丢弃,而不必担心。请参阅:http://mongoosejs.com/docs/api.html#schema_Schema-static