【问题标题】:MongoDB: select matched elements of subcollectionMongoDB:选择子集合的匹配元素
【发布时间】:2013-03-14 16:34:55
【问题描述】:

我正在使用 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'}]}

选定的专辑曲目仅包含一个标题为“track0”的曲目元素(但应该同时存在“track0”和“track1”):

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

我做错了什么?

【问题讨论】:

  • 您没有做错任何事情,这正是这些查询的工作方式——它们只包含第一个匹配元素。我认为你需要使用聚合框架来做你想做的事。
  • 谢谢,你能给我更具体的建议吗?

标签: mongodb mongoose


【解决方案1】:

就像@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
}

【讨论】:

  • 如果您不介意如何将所选曲目限制为只有标题字段(没有其他类似的语言字段),还有一个相关问题。
  • @mnemosyn 我在我的相关问题上尝试了这个解决方案并且它有效。现在我必须回去了解它!
  • 我必须在 aggregate 调用中添加数组括号。 db.Album.aggregate([...]);
猜你喜欢
  • 1970-01-01
  • 2011-05-23
  • 2013-10-01
  • 2012-11-24
  • 2012-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多