2014-05-12 67 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