2014-05-12 66 views
1

我有一个大文档的集合(每个大约200〜500 kb之间)。MongoDB - 返回大父亲的子文档

每个文档都包含一个子文档数组。在每个subdocujent,有一个数组我需要通过搜索。

我需要构建一个接口,使我能够获取单个子文档。

考虑到我无法重构文档模型并且不知道目标是在哪个父文档中生存的事实,那么实现这一目标的最明智和最快的方法是什么?

我想给你一个我曾经尝试过的例子,但我甚至在“如何搜索我需要的每个子文档数组”的基本概念上挣扎,所以请原谅这样的缺点。

父文档看起来是这样的:

{ 
"name":"Foobar", 
"subs":[ 
    {   
     "imageName":"name", 
     "foreignNames":[ 
      { 
       // This is the field I need to search through 
      } 
     ] 
    } 
] 
} 
+0

你可以添加一个(短路)演示文档显示对象的架构。 'db.collection.findOne()' – Simulant

回答

2

既然有返回的文件,只是所选择的“子文档”的细节之间存在明显的差异,你有嵌套的数组,你最好的办法是使用aggregate()方法代替:

所以如果考虑下面的文档作为样本:

{ 
    "_id" : ObjectId("5380709ab5caa8c27c8a1392"), 
    "name" : "Foobar", 
    "subs" : [ 
     { 
      "imageName" : "name", 
      "foreignNames" : [ 
       { 
        "tagname" : "value" 
       }, 
       { 
        "tagname" : "notvalue" 
       } 
      ] 
     } 
    ] 
} 

然后总的说法是:

db.collection.aggregate([ 
    // Actually match the documents containing the matched value 
    { "$match": { 
     "subs.foreignNames.tagname": "value" 
    }}, 

    // Unwind both of your arrays 
    { "$unwind": "$subs" }, 
    { "$unwind": "$subs.foreignNames" }, 

    // Now filter only the matching array element 
    { "$match": { 
     "subs.foreignNames.tagname": "value" 
    }}, 

    // Group back one level of data   
    { "$group": { 
     "_id": { 
      "_id": "$_id", 
      "name": "$name", 
      "imageName": "$subs.imageName" 
     }, 
     "foreignNames": { "$push": "$subs.foreignNames" } 
    }}, 

    // Group back to the original level 
    { "$group": { 
     "_id": "$_id._id", 
     "name": { "$first": "$_id.name" }, 
     "subs": { 
      "$push": { 
       "imageName": "$_id.imageName", 
       "foreignNames": "$foreignNames" 
      } 
     } 
    }} 
]) 

而结果将是:

{ 
    "_id" : ObjectId("5380709ab5caa8c27c8a1392"), 
    "name" : "Foobar", 
    "subs" : [ 
     { 
      "imageName" : "name", 
      "foreignNames" : [ 
       { 
        "tagname" : "value" 
       } 
      ] 
     } 
    ] 
} 

的优势在那里的是,如果你可能会拥有一个以上的比赛,甚至在一个以上的水平,所以说,在“潜艇的其他项目“,那么这将实际上将它们放在一起,同时过滤不匹配的结果。

如果你实际上并不需要这一点,只需要那些特定的“文件”或文档作为一个整体只在特定的领域,那么你就可以缩短,在该$group`阶段,你只需要$project结果:

db.newdoc.aggregate([ 
    { "$match": { 
     "subs.foreignNames.tagname": "value" 
    }}, 
    { "$unwind": "$subs" }, 
    { "$unwind": "$subs.foreignNames" }, 
    { "$match": { 
     "subs.foreignNames.tagname": "value" 
    }}, 
    { "$project": { 
     "_id": 0, 
     "matched": "$subs.foreignNames" 
    }} 
]) 

作为一个例子,但将返回:

{ "matched" : { "tagname" : "value" } } 

所以这是相当多的处理事情的方式。

注意:你问就在,因为很多人,为什么$match语句管线期间进行两次,它是那种在评论中解释,但这里的点。

即使在10000个文档的集合中只有1个文档实际上具有匹配条件的内部数组文档,在执行此数组展开之前执行此操作也是有意义的$match

这很简单,因为即使您打算将此后面的结果过滤为1个结果,您不希望执行的操作是$unwind所有10000个文档,其数组可能包含100,000个或更多条目,然后搜索找到那个1.你想把它缩小到最小的集合并丢弃任何永远不会包含你想要的子文档的文档。

此外,如已经提到,一个聚合管道的初始阶段使用$match只有机会,你为了提高查询性能选择索引。一旦开始解构/重构文档,索引不再可用。

所以指数第一,即:

db.collection.ensureIndex({ "subs.foreignNames.tagname": 1 }) 
+0

我是否正确地认为这只计算完整的父文档而不是仅返回该字段?所以我可以在前端做到这一点? – user2422960

+0

@ user2422960必须在这里错过您的评论。这不是意图。原帖中出现了一些小问题,但我已经解决了这些问题,并在此处添加了更好的解释。 –

1

这将搜索所有文件在Array foreignNames与价值value标签tagname(你要检查标签替换此)。

db.collection.find({"subs.foreignNames.tagname":"value"}) 

您可以使用以下命令为此搜索添加索引。有关索引(和限制)的更多信息,请参阅the documentation

db.collection.ensureIndex({"subs.foreignNames.tag":1}) 
+0

非常感谢!这样做的请求需要很长时间才能完成。我知道我可以创建索引,但是我既没有能够找到一个可以理解的指令,也不知道索引是否是提高性能的唯一方法。任何帮助,将不胜感激:) – user2422960

+0

@ user2422960:请参阅我的更新以创建索引。 – Simulant