MongoDB:选择子集合的匹配元素

时间:2013-03-14 16:34:55

标签: mongodb mongoose

我正在使用mongoose.js对mongodb进行查询,但我认为我的问题并非针对mongoose.js。

假设我在集合中只有一条记录:

var album = new Album({
    tracks: [{
        title: 'track0',
        language: 'en',

    },{
        title: 'track1',
        language: 'en',
    },{
        title: 'track2',
        language: 'es',
    }]
})

我想选择语言字段等于'en'的所有曲目,所以我尝试了两种变体:

Album.find({'tracks.language':'en'}, {'tracks.$': 1}, function(err, albums){

与$ elemMatch投影相关联:

Album.find({}, {tracks: {$elemMatch: {'language': 'en'}}}, function(err, albums){

在任何一种情况下我都得到了相同的结果:

{tracks:[{title: 'track0', language: 'en'}]}

选择的album.tracks只包含一个标题为'track0'的轨道元素(但应该同时包含'track0','track1'):

 {tracks:[{title: 'track0', language: 'en'}, {title: 'track1', language: 'en'}]}

我做错了什么?

1 个答案:

答案 0 :(得分:12)

就像@JohnnyHK已经说过的那样,你必须使用聚合框架来实现这一点,因为$$elemMatch只返回第一个匹配。

以下是:

db.Album.aggregate(
    // This is optional. It might make your query faster if you have
    // many albums that don't have any English tracks. Take a larger 
    // collection and measure the difference. YMMV.
    { $match: {tracks: {$elemMatch: {'language': 'en'}} } },

    // This will create an 'intermediate' document for each track
    { $unwind : "$tracks" },

    // Now filter out the documents that don't contain an English track
    // Note: at this point, documents' 'tracks' element is not an array
    { $match: { "tracks.language" : "en" } },

    // Re-group so the output documents have the same structure, ie.
    // make tracks a subdocument / array again
    { $group : { _id : "$_id", tracks : { $addToSet : "$tracks" } }} 
);

您可能希望仅使用第一个表达式尝试该聚合查询,然后逐行添加表达式以查看输出的更改方式。了解$unwind如何创建稍后使用$group$addToSet重新合并的中间文档尤为重要。

结果:

> db.Album.aggregate(
     { $match: {tracks: {$elemMatch: {'language': 'en'}} } }, 
     { $unwind : "$tracks" },
     { $match: { "tracks.language" : "en" } },
     { $group : { _id : "$_id", tracks : { $addToSet : "$tracks" } }}  );
{
    "result" : [
            {
                    "_id" : ObjectId("514217b1c99766f4d210c20b"),
                    "tracks" : [
                            {
                                    "title" : "track1",
                                    "language" : "en"
                            },
                            {
                                    "title" : "track0",
                                    "language" : "en"
                            }
                    ]
            }
    ],
    "ok" : 1
}