2016-04-07 19 views
0

文書の埋め込み配列から一致するエントリだけを返し、これを実践的に見つけることができない集約パイプラインを作成する方法をいくつか試しました。MongoDbの集計結果でelemMatchを満たすサブ文書のみを返す

私の非常に不器用でエラーを起こしやすいアプローチを避けるMongoDBの機能はありますか?

「ワークショップ」コレクション内の文書は、次のようになります...

{ 
    "_id": ObjectId("57064a294a54b66c1f961aca"), 
    "type": "normal", 
    "version": "v1.4.5", 
    "invitations": [], 
    "groups": [ 
    { 
     "_id": ObjectId("57064a294a54b66c1f961acb"), 
     "role": "facilitator" 
    }, 
    { 
     "_id": ObjectId("57064a294a54b66c1f961acc"), 
     "role": "contributor" 
    }, 
    { 
     "_id": ObjectId("57064a294a54b66c1f961acd"), 
     "role": "broadcaster" 
    }, 
    { 
     "_id": ObjectId("57064a294a54b66c1f961acf"), 
     "role": "facilitator" 
    } 
    ] 
} 

ときにグループメンバーがワークショップに与えられた役割を割り当てられるようにグループの配列の各エントリが一意のIDを提供しますその塩漬けされたIDを持つURLをヒットします。

ObjectId("57064a294a54b66c1f961acb")のようなグループ配列のエントリに一致する_idがある場合、私は集約パイプラインからこのような単一のレコードを返す必要があります - 基本的に埋め込みグループ配列から一致するエントリを返します。この例では

{ 
     "_id": ObjectId("57064a294a54b66c1f961acb"), 
     "role": "facilitator", 
     "workshopId": ObjectId("57064a294a54b66c1f961aca") 
    }, 

、workshopIdは、親文書を識別するために余分なフィールドとして追加されているが、残りの部分は、整合_idを有する元のグループエントリからALLフィールドであるべきです。

私が採用しているアプローチはこれを達成することはできますが、問題は多く、おそらく効率的ではありません(フィルタ句を繰り返します)。

return workshopCollection.aggregate([ 
    {$match:{groups:{$elemMatch:{_id:groupId}}}}, 
    {$unwind:"$groups"}, 
    {$match:{"groups._id":groupId}}, 
    {$project:{ 
     _id:"$groups._id", 
     role:"$groups.role", 
     workshopId:"$_id", 
    }}, 
]).toArray(); 

さらに悪いことに、それが明示的にエントリから名前のフィールドが含まれているので、それがレコードに追加されている任意の将来のフィールドを省略します。私はまた、配列エントリのフィールドが何であるか分からない限り、このルックアップ操作を '招待状'や他の組み込み名前付き配列の場合に一般化することはできません。

私は、パイプラインの$プロジェクト段階で$または$ elemMatch演算子を使用するのが正しいアプローチであるのか疑問に思っていますが、これまで無視されていたか、パイプライン実行時にオペレータの有効性エラーが発生しました。ドキュメントの配列からだけ一致するエントリを返すために -

QUESTION

はこのかなり主流の問題で私を助ける別の集計演算子または別のアプローチはありますか?

+0

いいえ、あなたの質問と用法は、配列要素の* "複数の条件に一致する複数の条件" *がある場合にのみ '$ elemMatch'を必要とするため、ややこしいことです。それ以外の場合(2番目の '$ match'文のように" dot notation "を使用します) –

+0

あなたの集計クエリは私にはうまく見えます。また、フィールドを知らなくても、すべてのタイプの埋め込みオブジェクト配列で機能する汎用の集計クエリを作成することはできません。それはそれをより複雑にし、エラーを起こしやすくします。 –

+0

結論として、配列メンバをトップレベルのドキュメントのように見せたいのであれば、 '$ unwind'を使い、最後に' $ project'を使う必要があります。また、カーソルの結果で受け取った基本文書を処理して出力し、送信先に出力を変換するだけで、受け入れて生きることができます。返される各ドキュメントの各配列メンバーを反復する単純なケース。 –

答えて

0

以下の実装は、任意のクエリを処理し、結果を「トップレベルのドキュメント」として提供し、パイプラインでの重複フィルタリングを回避します。

function retrieveArrayEntry(collection, arrayName, itemMatch){ 
    var match = {}; 
    match[arrayName]={$elemMatch:itemMatch}; 
    var project = {}; 
    project[arrayName+".$"] = true; 
    return collection.findOne(
     match, 
     project 
    ).then(function(doc){ 
     if(doc !== null){ 
      var result = doc[arrayName][0]; 
      result._docId = doc._id; 
      return result; 
     } 
     else{ 
      return null; 
     } 
    }); 
} 

それは...そうのように呼び出すことができ

retrieveArrayEntry(workshopCollection, "groups", {_id:ObjectId("57064a294a54b66c1f961acb")}) 

しかし、それはするのではなく、収集findOne(...)メソッドに依存して集計(...)そうに制限されます第1の一致するアレイエントリを第1の一致するドキュメントから提供する。配列一致句を参照する射影は、findXXX()メソッドと同じ方法で集計(...)によって明らかに不可能です。

さらに一般的な(ただし、混乱させて非効率的な)実装では、一致する複数のドキュメントとサブドキュメントを取得できます。これは、MongoDbがunpackMatchメソッドを使ってDocumentとSubdocumentのマッチングの構文一貫性を保つのが難しいことを回避します。 ...(Within a mongodb $match, how to test for field MATCHING , rather than field EQUALLINGで説明したように) '一致' の基準のために必要な構文に転送

{greetings:{_id:ObjectId("437908743")}} 

...ます...次の実装を導く

{"greetings._id":ObjectId("437908743")} 

...

function unpackMatch(pathPrefix, match){ 
    var unpacked = {}; 
    Object.keys(match).map(function(key){ 
     unpacked[pathPrefix + "." + key] = match[key]; 
    }) 
    return unpacked; 
} 

function retrieveArrayEntries(collection, arrayName, itemMatch){ 

    var matchDocs = {}, 
     projectItems = {}, 
     unwindItems = {}, 
     matchUnwoundByMap = {}; 

    matchDocs.$match={}; 
    matchDocs.$match[arrayName]={$elemMatch:itemMatch}; 

    projectItems.$project = {}; 
    projectItems.$project[arrayName]=true; 

    unwindItems.$unwind = "$" + arrayName; 

    matchUnwoundByMap.$match = unpackMatch(arrayName, itemMatch); 

    return collection.aggregate([matchDocs, projectItems, unwindItems, matchUnwoundByMap]).toArray().then(function(docs){ 
     return docs.map(function(doc){ 
      var result = doc[arrayName]; 
      result._docId = doc._id; 
      return result; 
     }); 
    }); 
} 
関連する問題