これは実際にはあなたが思っているよりもはるかに「ぎりぎりの」問題であり、すべてが本当に「名前付きキー」に沸きます。これは一般的に本当の問題であり、データは「データポイントそのような鍵の命名において。
あなたの試みで他に明らかな問題は「デカルト製品」と呼ばれます。これは$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
という名前のキーを使用して破棄します。あるいは、最終的なアプローチを行い、単純にカーソルの結果を操作して新しいコレクションに書き戻します。
予想される結果には、フィールド名が異なるドキュメントが含まれています。それらのうちのいくつかはfirstPropertyを持ち、他のものはsecondPropertyを持っています。それは本当に達成したいことですか? – mickl
期待される出力と現在の出力の両方が、実際に与えられたデータと一致しないか、提供された集約パイプラインに相関していることに注意してください。しかし、パイプラインは実際にデータがどこから来るべきかを明確にしています。あなたの予想される出力は、ネーミングの痕跡を失ったように見えますが、あなたが提示しようとしていた「列の名前としてのオブジェクトキー」の一般的な要点に従っていると思います。 –
まあ、はい。最終的なプロパティは、データ配列のさまざまなソースを表すため、異なる名前を付ける必要があります。しかし、それはまさに私がパイプラインの最終的な見方でやっていることです。 – yudarik