簡単に言うと、 "value"
を変更する必要があります "values"
内のフィールド 現在は文字列であるため、数値になります。しかし、答えに移りましょう:
$ reduce
にアクセスできる場合
MongoDB 3.4から、実際には次のようなことができます:
db.collection.aggregate([
{ "$addFields": {
"cities": {
"$reduce": {
"input": "$cities",
"initialValue": [],
"in": {
"$cond": {
"if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
"then": {
"$concatArrays": [
{ "$filter": {
"input": "$$value",
"as": "v",
"cond": { "$ne": [ "$$this._id", "$$v._id" ] }
}},
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": {
"$add": [
{ "$arrayElemAt": [
"$$value.visited",
{ "$indexOfArray": [ "$$value._id", "$$this._id" ] }
]},
1
]
}
}]
]
},
"else": {
"$concatArrays": [
"$$value",
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": 1
}]
]
}
}
}
}
},
"variables": {
"$map": {
"input": {
"$filter": {
"input": "$variables",
"cond": { "$eq": ["$$this.name", "Budget"] }
}
},
"in": {
"_id": "$$this._id",
"name": "$$this.name",
"defaultValue": "$$this.defaultValue",
"lastValue": "$$this.lastValue",
"value": { "$avg": "$$this.values.value" }
}
}
}
}}
])
MongoDB 3.6を使用している場合は、 $ mergeObjects
:
db.collection.aggregate([
{ "$addFields": {
"cities": {
"$reduce": {
"input": "$cities",
"initialValue": [],
"in": {
"$cond": {
"if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
"then": {
"$concatArrays": [
{ "$filter": {
"input": "$$value",
"as": "v",
"cond": { "$ne": [ "$$this._id", "$$v._id" ] }
}},
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": {
"$add": [
{ "$arrayElemAt": [
"$$value.visited",
{ "$indexOfArray": [ "$$value._id", "$$this._id" ] }
]},
1
]
}
}]
]
},
"else": {
"$concatArrays": [
"$$value",
[{
"_id": "$$this._id",
"name": "$$this.name",
"visited": 1
}]
]
}
}
}
}
},
"variables": {
"$map": {
"input": {
"$filter": {
"input": "$variables",
"cond": { "$eq": ["$$this.name", "Budget"] }
}
},
"in": {
"$mergeObjects": [
"$$this",
{ "values": { "$avg": "$$this.values.value" } }
]
}
}
}
}}
])
ただし、 additionalData
を保持することを除けば、ほぼ同じです。
その少し前に戻ると、いつでも $ unwind
"cities"
蓄積する:
db.collection.aggregate([
{ "$unwind": "$cities" },
{ "$group": {
"_id": {
"_id": "$_id",
"cities": {
"_id": "$cities._id",
"name": "$cities.name"
}
},
"_class": { "$first": "$class" },
"name": { "$first": "$name" },
"startTimestamp": { "$first": "$startTimestamp" },
"endTimestamp" : { "$first": "$endTimestamp" },
"source" : { "$first": "$source" },
"variables": { "$first": "$variables" },
"visited": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"_class": { "$first": "$class" },
"name": { "$first": "$name" },
"startTimestamp": { "$first": "$startTimestamp" },
"endTimestamp" : { "$first": "$endTimestamp" },
"source" : { "$first": "$source" },
"cities": {
"$push": {
"_id": "$_id.cities._id",
"name": "$_id.cities.name",
"visited": "$visited"
}
},
"variables": { "$first": "$variables" },
}},
{ "$addFields": {
"variables": {
"$map": {
"input": {
"$filter": {
"input": "$variables",
"cond": { "$eq": ["$$this.name", "Budget"] }
}
},
"in": {
"_id": "$$this._id",
"name": "$$this.name",
"defaultValue": "$$this.defaultValue",
"lastValue": "$$this.lastValue",
"value": { "$avg": "$$this.values.value" }
}
}
}
}}
])
すべてが(ほぼ)同じものを返します:
{
"_id" : ObjectId("5afc2f06e1da131c9802071e"),
"_class" : "Traveler",
"name" : "John Due",
"startTimestamp" : 1526476550933,
"endTimestamp" : 1526476554823,
"source" : "istanbul",
"cities" : [
{
"_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
"name" : "Cairo",
"visited" : 1
},
{
"_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
"name" : "Moscow",
"visited" : 2
}
],
"variables" : [
{
"_id" : "c8103687c1c8-97d749e349d785c8-9154",
"name" : "Budget",
"defaultValue" : "",
"lastValue" : "",
"value" : 3000
}
]
}
もちろん、最初の2つの形式は、常に同じドキュメント内で機能しているだけなので、最適な方法です。
$ reduce
のような演算子
配列で「累積」式を許可するので、ここでそれを使用して、一意の "_ id"
をテストする「縮小」配列を保持できます。 $ indexOfArray
を使用した値
一致するアイテムがすでに蓄積されているかどうかを確認するため。 -1
の結果 そこにないことを意味します。
「縮小配列」を作成するために、 "initialValue"
を使用します。 []
の 空の配列として追加し、 $concatArrays<を介して追加します。 / code>
。そのプロセスはすべて、「ternary」 $condを介して決定されます。
"if"
を考慮する演算子 条件と"then"
$ filter
>
現在の$$value
現在のインデックスを除外するには_id
もちろん、単一のオブジェクトを表す別の「配列」を含むエントリ。
その「オブジェクト」には、再び $ indexOfArray コード>
アイテムが「そこにある」ことがわかっているので、実際に一致するインデックスを取得し、それを使用して現在の「visited」
を抽出します。 $ arrayElemAt
を介したそのエントリからの値
および $ add
インクリメントするためにそれに。
"else"
内 デフォルトの"visited"
を持つ「オブジェクト」として「配列」を追加するだけの場合 1
の値 。これらの両方のケースを使用すると、配列内に一意の値が効果的に蓄積されて出力されます。
後者のバージョンでは、 $ unwind
配列を使用し、連続する $ group
を使用します
最初に一意の内部エントリを「カウント」し、次に「配列を再構築」して同様の形式にするためのステージ。
$ unwind
を使用する
見た目ははるかに単純ですが、実際にはすべての配列エントリに対してドキュメントのコピーを取得するため、実際には処理にかなりのオーバーヘッドが追加されます。最近のバージョンでは、一般に配列演算子があります。これは、「ドキュメント間で蓄積する」ことを意図していない限り、これを使用する必要がないことを意味します。したがって、実際に $ group
を実行する必要がある場合
配列の「内部」からのキーの値に対して、実際にそれを使用する必要がある場所です。
「変数」
について 次に、 $ filter
を使用するだけです。
ここでも、一致する "Budget"
を取得します。 エントリ。これは、 $ map
>
配列コンテンツの「再形成」を可能にする演算子。 "values"
のコンテンツを取得できるように、主にそれが必要です。 (すべて数値にすると)、 $avgを使用します
演算子。「フィールドパス表記」は、実際にはそのような入力から結果を返すことができるため、配列値に直接形成されます。
これにより、通常、集約パイプラインのほとんどすべての主要な「配列演算子」(「集合」演算子を除く)をすべて単一のパイプラインステージ内でツアーできます。
また、常にを実行したいことを忘れないでください。 $ match
通常の
代替案
代替は、クライアントコードのドキュメントを処理しています。上記のすべてのメソッドは、一般に「サーバーの集約」のポイントであるように、サーバーから返されるコンテンツを実際に「削減」することを示しているため、通常はお勧めしません。
「ドキュメントベース」の性質により、 $ unwind
を使用すると、結果セットが大きくなるとかなり時間がかかる可能性があります。 クライアント処理はオプションかもしれませんが、私はそれがはるかに可能性が高いと思います
以下は、同じことを行って結果が返されるときにカーソルストリームに変換を適用する方法を示すリストです。変換には3つのデモバージョンがあり、上記と「まったく」同じロジック、 lodash
を使用した実装を示しています。 蓄積の方法、および Map
での「自然な」蓄積 実装:
const { MongoClient } = require('mongodb');
const { chain } = require('lodash');
const uri = 'mongodb://localhost:27017';
const opts = { useNewUrlParser: true };
const log = data => console.log(JSON.stringify(data, undefined, 2));
const transform = ({ cities, variables, ...d }) => ({
...d,
cities: cities.reduce((o,{ _id, name }) =>
(o.map(i => i._id).indexOf(_id) != -1)
? [
...o.filter(i => i._id != _id),
{ _id, name, visited: o.find(e => e._id === _id).visited + 1 }
]
: [ ...o, { _id, name, visited: 1 } ]
, []).sort((a,b) => b.visited - a.visited),
variables: variables.filter(v => v.name === "Budget")
.map(({ values, additionalData, ...v }) => ({
...v,
values: (values != undefined)
? values.reduce((o,e) => o + e.value, 0) / values.length
: 0
}))
});
const alternate = ({ cities, variables, ...d }) => ({
...d,
cities: chain(cities)
.groupBy("_id")
.toPairs()
.map(([k,v]) =>
({
...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
visited: v.length
})
)
.sort((a,b) => b.visited - a.visited)
.value(),
variables: variables.filter(v => v.name === "Budget")
.map(({ values, additionalData, ...v }) => ({
...v,
values: (values != undefined)
? values.reduce((o,e) => o + e.value, 0) / values.length
: 0
}))
});
const natural = ({ cities, variables, ...d }) => ({
...d,
cities: [
...cities
.reduce((o,{ _id, name }) => o.set(_id,
[ ...(o.has(_id) ? o.get(_id) : []), { _id, name } ]), new Map())
.entries()
]
.map(([k,v]) =>
({
...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
visited: v.length
})
)
.sort((a,b) => b.visited - a.visited),
variables: variables.filter(v => v.name === "Budget")
.map(({ values, additionalData, ...v }) => ({
...v,
values: (values != undefined)
? values.reduce((o,e) => o + e.value, 0) / values.length
: 0
}))
});
(async function() {
try {
const client = await MongoClient.connect(uri, opts);
let db = client.db('test');
let coll = db.collection('junk');
let cursor = coll.find().map(natural);
while (await cursor.hasNext()) {
let doc = await cursor.next();
log(doc);
}
client.close();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()