2017-10-30 18 views
1

今日、私はmongoDBコレクションをSQLのテーブルの列となるvertica(SQLデータベース)と同期させる必要がある状況に遭遇します。 私はmongoDBアグリゲーションフレームワークを使用して、最初に、望む結果ドキュメントを照会し、操作して投影し、それをverticaに同期させます。複数のドキュメント配列を新しいドキュメントに巻き戻す

私が凝集するスキーマは次のようになります。配列の値が他の配列の値と関連していないので、私は必要なもの、

{ 
    userId: 123 
    firstProperty: { 
    firstArray: ['x','y','z'], 
    anotherAttr: 'abc' 
    }, 
    anotherProperty: { 
    secondArray: ['a','b','c'], 
    anotherAttr: 'def' 
    } 
} 

ネストされた配列の各値は、個別の結果文書になるということです。私は、次の集約パイプを使用することについては :まさに私は何

{ 
    userId: 'x1', 
    firstProperty: 'x', 
    firstPropertyAttr: 'b' 
    secondProperty: 'a', 
    secondPropertyAttr: 'b' 
} 
{ 
    userId: 'x1', 
    firstProperty: 'y', 
    firstPropertyAttr: 'b' 
    secondProperty: 'b', 
    secondPropertyAttr: 'b' 
} 
{ 
    userId: 'x1', 
    firstProperty: 'z', 
    firstPropertyAttr: 'b' 
    secondProperty: 'c', 
    secondPropertyAttr: 'b' 
} 

:代わりに、私はそのような何かを得る

{ 
    userId: 'x1', 
    firstProperty: 'x', 
    firstPropertyAttr: 'a' 
} 
{ 
    userId: 'x1', 
    firstProperty: 'y', 
    firstPropertyAttr: 'a' 
} 
{ 
    userId: 'x1', 
    firstProperty: 'z', 
    firstPropertyAttr: 'a' 
} 
{ 
    userId: 'x1', 
    secondProperty: 'a', 
    firstPropertyAttr: 'b' 
} 
{ 
    userId: 'x1', 
    secondProperty: 'b', 
    firstPropertyAttr: 'b' 
} 
{ 
    userId: 'x1', 
    secondProperty: 'c', 
    firstPropertyAttr: 'b' 
} 

:私は何を期待

db.collection('myCollection').aggregate([ 
      { 
       $match: { 
        $or: [ 
         {'firstProperty.firstArray.1': {$exists: true}}, 
         {'secondProperty.secondArray.1': {$exists: true}} 
        ] 
       } 
      }, 
      { 
       $project: { 
        userId: 1, 
        firstProperty: 1, 
        secondProperty: 1 
       } 
      }, { 
       $unwind: {path:'$firstProperty.firstAray'} 
      }, { 
       $unwind: {path:'$secondProperty.secondArray'}, 
      }, { 
       $project: { 
        userId: 1, 
        firstProperty: '$firstProperty.firstArray', 
        firstPropertyAttr: '$firstProperty.anotherAttr', 
        secondProperty: '$secondProperty.secondArray', 
        seondPropertyAttr: '$secondProperty.anotherAttr' 
       } 
      }, { 
       $out: 'another_collection' 
      } 
     ]) 

には、以下の結果であり、行方不明、どうすれば修正できますか?

+0

予想される結果には、フィールド名が異なるドキュメントが含まれています。それらのうちのいくつかはfirstPropertyを持ち、他のものはsecondPropertyを持っています。それは本当に達成したいことですか? – mickl

+0

期待される出力と現在の出力の両方が、実際に与えられたデータと一致しないか、提供された集約パイプラインに相関していることに注意してください。しかし、パイプラインは実際にデータがどこから来るべきかを明確にしています。あなたの予想される出力は、ネーミングの痕跡を失ったように見えますが、あなたが提示しようとしていた「列の名前としてのオブジェクトキー」の一般的な要点に従っていると思います。 –

+0

まあ、はい。最終的なプロパティは、データ配列のさまざまなソースを表すため、異なる名前を付ける必要があります。しかし、それはまさに私がパイプラインの最終的な見方でやっていることです。 – yudarik

答えて

0

これは実際にはあなたが思っているよりもはるかに「ぎりぎりの」問題であり、すべてが本当に「名前付きキー」に沸きます。これは一般的に本当の問題であり、データは「データポイントそのような鍵の命名において。

あなたの試みで他に明らかな問題は「デカルト製品」と呼ばれます。これは$unwindの配列の1つで、別の配列の$unwindです。その結果、「最初の」$unwindの項目が「2番目」のすべての値に対して繰り返されます。

この2番目の問題に対処するための基本的なアプローチは、単一のソースからの$unwindという順序で「配列を結合する」ことです。残りのすべてのアプローチにはこれが非常に共通しています。

これらのアプローチについては、利用可能なMongoDBのバージョンとアプリケーションの一般的な実用性が異なります。それでは、それらをステップしてみましょう:

は、指定されたキー

を削除し、ここで最も単純なアプローチは、単に出力で指定されたキーを期待しないことであり、代わりに最終出力での彼らの源を特定する"name"としてそれらをマーク。だから、私たちがしたいのは、最初の "combined"配列の構築の中にそれぞれの "期待される"キーを指定することだけです。そして、nullの値に対して、現在のドキュメントには存在しない値$filterを単純に指定するだけです。データから

db.getCollection('myCollection').aggregate([ 
    { "$match": { 
    "$or": [ 
     { "firstProperty.firstArray.0": { "$exists": true } }, 
     { "anotherProperty.secondArray.0": { "$exists": true } } 
    ] 
    }}, 
    { "$project": { 
    "_id": 0, 
    "userId": 1, 
    "combined": { 
     "$filter": { 
     "input": [ 
      { 
      "name": { "$literal": "first" }, 
      "array": "$firstProperty.firstArray", 
      "attr": "$firstProperty.anotherAttr" 
      }, 
      { 
      "name": { "$literal": "another" }, 
      "array": "$anotherProperty.secondArray", 
      "attr": "$anotherProperty.anotherAttr" 
      } 
     ], 
     "cond": { 
      "$ne": ["$$this.array", null ] 
     } 
     } 
    } 
    }}, 
    { "$unwind": "$combined" }, 
    { "$unwind": "$combined.array" }, 
    { "$project": { 
    "userId": 1, 
    "name": "$combined.name", 
    "value": "$combined.array", 
    "attr": "$combined.attr" 
    }} 
]) 

は、あなたの質問にこの生み出す含ま:

/* 1 */ 
{ 
    "userId" : 123.0, 
    "name" : "first", 
    "value" : "x", 
    "attr" : "abc" 
} 

/* 2 */ 
{ 
    "userId" : 123.0, 
    "name" : "first", 
    "value" : "y", 
    "attr" : "abc" 
} 

/* 3 */ 
{ 
    "userId" : 123.0, 
    "name" : "first", 
    "value" : "z", 
    "attr" : "abc" 
} 

/* 4 */ 
{ 
    "userId" : 123.0, 
    "name" : "another", 
    "value" : "a", 
    "attr" : "def" 
} 

/* 5 */ 
{ 
    "userId" : 123.0, 
    "name" : "another", 
    "value" : "b", 
    "attr" : "def" 
} 

/* 6 */ 
{ 
    "userId" : 123.0, 
    "name" : "another", 
    "value" : "c", 
    "attr" : "def" 
} 

オブジェクトをマージする - 実際に私たちは$objectToArrayが必要「という名前のキーを」使用するにはMongoDB 3.4.4最小

を必要とし、 $arrayToObject演算子はMongoDB 3.4.4以降でしか使用できませんでした。これらと使用我々は単に明示的は、いずれかの段階で出力するキーに名前を付けずに、ご希望の出力に処理することができ$replaceRootパイプラインステージ:その後、配列に「キー」を変換するから、かなり巨大な取得

db.getCollection('myCollection').aggregate([ 
    { "$match": { 
    "$or": [ 
     { "firstProperty.firstArray.0": { "$exists": true } }, 
     { "anotherProperty.secondArray.0": { "$exists": true } } 
    ] 
    }}, 
    { "$project": { 
    "_id": 0, 
    "userId": 1, 
    "data": { 
     "$reduce": { 
     "input": { 
      "$map": { 
      "input": { 
       "$filter": { 
       "input": { "$objectToArray": "$$ROOT" }, 
       "cond": { "$not": { "$in": [ "$$this.k", ["_id", "userId"] ] } } 
       } 
      }, 
      "as": "d", 
      "in": { 
       "$let": { 
       "vars": { 
        "inner": { 
        "$map": { 
         "input": { "$objectToArray": "$$d.v" }, 
         "as": "i", 
         "in": { 
         "k": { 
          "$cond": { 
          "if": { "$ne": [{ "$indexOfCP": ["$$i.k", "Array"] }, -1] }, 
          "then": "$$d.k", 
          "else": { "$concat": ["$$d.k", "Attr"] } 
          } 
         }, 
         "v": "$$i.v" 
         } 
        } 
        } 
       }, 
       "in": { 
        "$map": { 
        "input": { 
         "$arrayElemAt": [ 
         "$$inner.v", 
         { "$indexOfArray": ["$$inner.k", "$$d.k"] } 
         ] 
        }, 
        "as": "v", 
        "in": { 
         "$arrayToObject": [[ 
         { "k": "$$d.k", "v": "$$v" }, 
         { 
          "k": { "$concat": ["$$d.k", "Attr"] }, 
          "v": { 
          "$arrayElemAt": [ 
           "$$inner.v", 
           { "$indexOfArray": ["$$inner.k", { "$concat": ["$$d.k", "Attr"] }] } 
          ] 
          } 
         } 
         ]] 
        } 
        } 
       } 
       } 
      } 
      } 
     }, 
     "initialValue": [], 
     "in": { "$concatArrays": [ "$$value", "$$this" ] } 
     } 
    } 
    }}, 
    { "$unwind": "$data" }, 
    { "$replaceRoot": { 
    "newRoot": { 
     "$arrayToObject": { 
     "$concatArrays": [ 
      [{ "k": "userId", "v": "$userId" }], 
      { "$objectToArray": "$data" } 
     ] 
     } 
    } 
    }} 
]) 

「サブキー」を配列に格納し、その内部配列の値を出力のキーのペアにマッピングします。 $objectToArray

キー部分は、基本的にキーと「値」の「名前」を表す"k""v"の配列にあなたの「ネストされたキー」構造を「変換」するために必要とされます。これは、文書の「外側」の部分に対して1度であり、"_id""userId"などの「定数」フィールドをそのような配列構造体に除外することで、2回呼び出されます。次に、2番目の呼び出しは、それらの「配列」要素を同様の「配列」にするために、各「配列」要素で処理されます。

を使用してマッチングが行われ、値の "内部キー"と "Attr"が決定されます。次に、キーは、ここでは "v"の礼儀 $objectToArrayであるため、アクセス可能な「外部」キー値に名前が変更されます。

そして「アレイ」は「内部値」のため、我々は$map基本的形態を有する合成「アレイ」への各エントリを望む:

[ 
    { "k": "firstProperty", "v": "x" }, 
    { "k": "firstPropertyAttr", "v": "abc" } 
] 

これはそれぞれ「内側アレイに​​対して起こります"要素であり、$arrayToObjectはプロセスを逆にし、それぞれ"k""v"をオブジェクトの" key "と" value "にそれぞれ変換します。

出力がまだこの時点では「内部キー」の「配列の配列」であるので、$reduceは、その出力をラップし"data"のための単一のアレイに「参加」するために、各要素を処理中$concatArraysを適用します。

残っているのは、それぞれのソース文書から生成された配列$unwindです。$replaceRootが適用されます。これは実際には各文書出力の「ルート」に「異なるキー名」を許可する部分です。

ここで「マージ」"userId"ため表記同じ"k""v"構造のオブジェクトの配列を供給することにより行われ、$objectToArray有することが"data"の変換「concatentating」。もちろん、この「新しい配列」は$arrayToObjectを介してオブジェクトに変換され、"newRoot"の「オブジェクト」引数を式として形成します。

実際に明示的に名前を付けることができない多数の「名前付きキー」がある場合は、そのようなことを行います。そして、それは実際にあなたが望む結果を提供します:MongoDBの3.4.4またはグレーターなし

/* 1 */ 
{ 
    "userId" : 123.0, 
    "firstProperty" : "x", 
    "firstPropertyAttr" : "abc" 
} 

/* 2 */ 
{ 
    "userId" : 123.0, 
    "firstProperty" : "y", 
    "firstPropertyAttr" : "abc" 
} 

/* 3 */ 
{ 
    "userId" : 123.0, 
    "firstProperty" : "z", 
    "firstPropertyAttr" : "abc" 
} 

/* 4 */ 
{ 
    "userId" : 123.0, 
    "anotherProperty" : "a", 
    "anotherPropertyAttr" : "def" 
} 

/* 5 */ 
{ 
    "userId" : 123.0, 
    "anotherProperty" : "b", 
    "anotherPropertyAttr" : "def" 
} 

/* 6 */ 
{ 
    "userId" : 123.0, 
    "anotherProperty" : "c", 
    "anotherPropertyAttr" : "def" 
} 
キーネームド

オペレータのサポートがなければ上記のリストに示すように、それはへの集約フレームワークのために、単純に不可能です異なるキー名を持つドキュメントを出力します。それは$out経由でこれを行うには、「サーバー」を指示することはできませんけれども

だから、あなたはもちろん、単純にカーソルを繰り返すことができますし、中に行われているようなものの

var ops = []; 

db.getCollection('myCollection').find().forEach(d => { 
    ops = ops.concat(Object.keys(d).filter(k => ['_id','userId'].indexOf(k) === -1) 
    .map(k => 
     d[k][Object.keys(d[k]).find(ki => /Array$/.test(ki))] 
     .map(v => ({ 
      [k]: v, 
      [`${k}Attr`]: d[k][Object.keys(d[k]).find(ki => /Attr$/.test(ki))] 
     })) 
    ) 
    .reduce((acc,curr) => acc.concat(curr),[]) 
    .map(o => Object.assign({ userId: d.userId },o)) 
); 

    if (ops.length >= 1000) { 
    db.getCollection("another_collection").insertMany(ops); 
    ops = []; 
    } 

}) 

if (ops.length > 0) { 
    db.getCollection("another_collection").insertMany(ops); 
    ops = []; 
} 

同じ種類の新しいコレクションを書きます初期の集約ではなく、単に「外部的に」。それは本質的に生成し、そのような「インナー」配列、一致する各文書について文書の配列:

[ 
    { 
     "userId" : 123.0, 
     "firstProperty" : "x", 
     "firstPropertyAttr" : "abc" 
    }, 
    { 
     "userId" : 123.0, 
     "firstProperty" : "y", 
     "firstPropertyAttr" : "abc" 
    }, 
    { 
     "userId" : 123.0, 
     "firstProperty" : "z", 
     "firstPropertyAttr" : "abc" 
    }, 
    { 
     "userId" : 123.0, 
     "anotherProperty" : "a", 
     "anotherPropertyAttr" : "def" 
    }, 
    { 
     "userId" : 123.0, 
     "anotherProperty" : "b", 
     "anotherPropertyAttr" : "def" 
    }, 
    { 
     "userId" : 123.0, 
     "anotherProperty" : "c", 
     "anotherPropertyAttr" : "def" 
    } 
] 

は、これらのことが到達したときに1000年以上の長さが最終的に書かれている大きな配列の中に「キャッシュされた」GET .insertMany()で新しいコレクションに追加します。もちろん、サーバーとの "前後の"通信が必要ですが、以前の集約で使用できる機能がない場合は、可能な限り効率的な方法でジョブを完了させます。

結論

全体的にここでのポイントあなたが実際にそれをサポートするMongoDBのを持っていない限り、あなたは単に集約パイプラインから、出力の「異なるキー名」との文書を取得するつもりはないということです。

そのようなサポートがない場合は、最初のオプションを使用して、$outという名前のキーを使用して破棄します。あるいは、最終的なアプローチを行い、単純にカーソルの結果を操作して新しいコレクションに書き戻します。

+0

あなたが提案した最初の解決策(「名前付きキーの削除」)は、私が必要とするものを完全に行います。 私のデータは現実には少し違って見えますが、私が与えた例は単純化しています。 ありがとうございました。 – yudarik

関連する問題