एक त्वरित नोट के रूप में, आपको अपना "मान"
. बदलना होगा "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 है, तो आप इसे $मर्जऑब्जेक्ट्स
:
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" } }
]
}
}
}
}}
])
लेकिन यह कमोबेश एक ही बात है सिवाय इसके कि हम अतिरिक्त डेटा
रखते हैं
उससे थोड़ा पहले पीछे जाकर, आप हमेशा $unwind
"शहर"
जमा करना:
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
}
]
}
पहले दो रूप निश्चित रूप से करने के लिए सबसे इष्टतम चीज हैं क्योंकि वे हर समय एक ही दस्तावेज़ के "अंदर" काम कर रहे हैं।
ऑपरेटर जैसे $reduce
सरणियों पर "संचय" अभिव्यक्तियों की अनुमति दें, इसलिए हम इसका उपयोग यहां "कम" सरणी रखने के लिए कर सकते हैं जिसे हम अद्वितीय "_id"
के लिए परीक्षण करते हैं $indexOfArray
का उपयोग करके मान
यह देखने के लिए कि क्या पहले से ही एक संचित वस्तु है जो मेल खाती है। -1
. का परिणाम इसका मतलब है कि यह वहां नहीं है।
"कम किए गए सरणी" के निर्माण के लिए हम "initialValue"
. लेते हैं का []
एक खाली सरणी के रूप में और फिर इसे $concatArrays<के माध्यम से जोड़ें /कोड>
. यह सारी प्रक्रिया "टर्नरी" $cond के ज़रिए तय की जाती है
ऑपरेटर जो "if"
. पर विचार करता है शर्त और "फिर"
या तो $filter
वर्तमान $$value
. पर मौजूदा इंडेक्स _id
. को बाहर करने के लिए प्रविष्टि, निश्चित रूप से एक और "सरणी" एकवचन वस्तु का प्रतिनिधित्व करती है।
उस "ऑब्जेक्ट" के लिए हम फिर से $indexOfArrayकोड का उपयोग करते हैं। कोड>
वास्तव में मिलान सूचकांक प्राप्त करने के लिए क्योंकि हम जानते हैं कि आइटम "वहां है", और वर्तमान "visited"
को निकालने के लिए इसका उपयोग करें उस प्रविष्टि से मूल्य $arrayElemAt
के माध्यम से
और $add
इसे बढ़ाने के लिए।
"else"
. में मामले में हम बस एक "सरणी" को "ऑब्जेक्ट" के रूप में जोड़ते हैं जिसमें बस एक डिफ़ॉल्ट होता है "विज़िट"
1
. का मान . उन दोनों मामलों का उपयोग प्रभावी ढंग से आउटपुट के लिए सरणी के भीतर अद्वितीय मान जमा करता है।
बाद वाले संस्करण में, हम केवल $unwind
सरणी और क्रमिक उपयोग $group
अद्वितीय आंतरिक प्रविष्टियों पर पहले "गिनती" करने के लिए चरण, और फिर समान रूप में "सरणी का पुन:निर्माण"।
$unwind
का इस्तेमाल करना
कहीं अधिक सरल दिखता है, लेकिन चूंकि यह वास्तव में प्रत्येक सरणी प्रविष्टि के लिए दस्तावेज़ की एक प्रति लेता है, तो यह वास्तव में प्रसंस्करण के लिए काफी ओवरहेड जोड़ता है। आधुनिक संस्करणों में आम तौर पर सरणी ऑपरेटर होते हैं जिसका अर्थ है कि आपको इसका उपयोग करने की आवश्यकता नहीं है जब तक कि आपका इरादा "दस्तावेज़ों में जमा करना" न हो। इसलिए अगर आपको वास्तव में $group
की ज़रूरत है
एक सरणी के "अंदर" से एक कुंजी के मूल्य पर, तो वह वह जगह है जहां आपको वास्तव में इसका उपयोग करने की आवश्यकता होती है।
जहां तक "चर"
. का सवाल है तब हम केवल $filter
का उपयोग कर सकते हैं
मिलान करने के लिए यहां फिर से "बजट"
प्रवेश। हम इसे $map
ऑपरेटर जो सरणी सामग्री के "पुनः आकार देने" की अनुमति देता है। हम मुख्य रूप से ऐसा चाहते हैं ताकि आप "values"
. की सामग्री ले सकें (एक बार जब आप इसे सभी संख्यात्मक बना लेते हैं) और $avg का उपयोग करें
ऑपरेटर, जिसे आपूर्ति की जाती है कि "फ़ील्ड पथ संकेतन" सीधे सरणी मानों पर बनता है क्योंकि यह वास्तव में ऐसे इनपुट से परिणाम लौटा सकता है।
यह आम तौर पर एक ही पाइपलाइन चरण के भीतर एकत्रीकरण पाइपलाइन ("सेट" ऑपरेटरों को छोड़कर) के लिए सभी मुख्य "सरणी ऑपरेटरों" का दौरा करता है।
यह भी कभी न भूलें कि आप लगभग हमेशा करना चाहते हैं। $मैच
नियमित क्वेरी ऑपरेटर्स
के साथ किसी भी एकत्रीकरण पाइपलाइन के "पहले चरण" के रूप में केवल आपको आवश्यक दस्तावेज़ों का चयन करने के लिए। आदर्श रूप से एक अनुक्रमणिका का उपयोग करना।
वैकल्पिक
वैकल्पिक क्लाइंट कोड में दस्तावेज़ों के माध्यम से काम कर रहे हैं। आमतौर पर इसकी अनुशंसा नहीं की जाती है क्योंकि उपरोक्त सभी विधियों से पता चलता है कि वे वास्तव में सर्वर से लौटाई गई सामग्री को "कम" करते हैं, जैसा कि आमतौर पर "सर्वर एकत्रीकरण" का बिंदु होता है।
यह "दस्तावेज़ आधारित" प्रकृति के कारण "संभव" हो सकता है कि बड़े परिणाम सेट $अनविंड
का उपयोग करके काफी अधिक समय ले सकते हैं। और क्लाइंट प्रोसेसिंग एक विकल्प हो सकता है, लेकिन मैं इसे और अधिक संभावना मानूंगा
नीचे एक सूची है जो कर्सर स्ट्रीम में परिवर्तन को लागू करने का प्रदर्शन करती है क्योंकि परिणाम वही काम करते हुए वापस आते हैं। रूपांतरण के तीन प्रदर्शित संस्करण हैं, जो ऊपर के समान "बिल्कुल" तर्क दिखाते हैं, lodash
के साथ एक कार्यान्वयन संचय के लिए तरीके, और मानचित्र
. पर एक "प्राकृतिक" संचय कार्यान्वयन:
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()
}
})()