इसके लिए एल्गोरिदम मूल रूप से दो मानों के अंतराल के बीच मूल्यों को "पुनरावृत्ति" करना है। MongoDB के पास इससे निपटने के कुछ तरीके हैं, जो हमेशा mapReduce()
और aggregate()<के लिए उपलब्ध नई सुविधाओं के साथ /कोड>
विधि।
मैं आपके चयन का विस्तार जानबूझकर एक अतिव्यापी माह दिखाने के लिए कर रहा हूं क्योंकि आपके उदाहरणों में एक नहीं था। इसके परिणामस्वरूप "HGV" मान आउटपुट के "तीन" महीनों में दिखाई देंगे।
{
"_id" : 1,
"startDate" : ISODate("2017-01-01T00:00:00Z"),
"endDate" : ISODate("2017-02-25T00:00:00Z"),
"type" : "CAR"
}
{
"_id" : 2,
"startDate" : ISODate("2017-02-17T00:00:00Z"),
"endDate" : ISODate("2017-03-22T00:00:00Z"),
"type" : "HGV"
}
{
"_id" : 3,
"startDate" : ISODate("2017-02-17T00:00:00Z"),
"endDate" : ISODate("2017-04-22T00:00:00Z"),
"type" : "HGV"
}
कुल - MongoDB 3.4 की आवश्यकता है
db.cars.aggregate([
{ "$addFields": {
"range": {
"$reduce": {
"input": { "$map": {
"input": { "$range": [
{ "$trunc": {
"$divide": [
{ "$subtract": [ "$startDate", new Date(0) ] },
1000
]
}},
{ "$trunc": {
"$divide": [
{ "$subtract": [ "$endDate", new Date(0) ] },
1000
]
}},
60 * 60 * 24
]},
"as": "el",
"in": {
"$let": {
"vars": {
"date": {
"$add": [
{ "$multiply": [ "$$el", 1000 ] },
new Date(0)
]
},
"month": {
}
},
"in": {
"$add": [
{ "$multiply": [ { "$year": "$$date" }, 100 ] },
{ "$month": "$$date" }
]
}
}
}
}},
"initialValue": [],
"in": {
"$cond": {
"if": { "$in": [ "$$this", "$$value" ] },
"then": "$$value",
"else": { "$concatArrays": [ "$$value", ["$$this"] ] }
}
}
}
}
}},
{ "$unwind": "$range" },
{ "$group": {
"_id": {
"type": "$type",
"month": "$range"
},
"count": { "$sum": 1 }
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": "$_id.type",
"monthCounts": {
"$push": { "month": "$_id.month", "count": "$count" }
}
}}
])
यह काम करने की कुंजी है $range
ऑपरेटर जो लागू करने के लिए "प्रारंभ" और "अंत" के साथ-साथ "अंतराल" के लिए मान लेता है। परिणाम "प्रारंभ" से लिए गए मानों की एक सरणी है और "अंत" तक पहुंचने तक वृद्धि हुई है।
हम इसका उपयोग startDate
. के साथ करते हैं और समाप्ति तिथि
उन मानों के बीच संभावित तिथियां उत्पन्न करने के लिए। आप देखेंगे कि $range
. के बाद से हमें यहां कुछ गणित करने की आवश्यकता है केवल 32-बिट पूर्णांक लेता है, लेकिन हम मिलीसेकंड को टाइमस्टैम्प मानों से दूर ले जा सकते हैं ताकि यह ठीक रहे।
क्योंकि हम "महीने" चाहते हैं, लागू किए गए ऑपरेशन उत्पन्न सीमा से महीने और वर्ष के मूल्यों को निकालते हैं। हम वास्तव में "दिनों" के बीच में सीमा उत्पन्न करते हैं क्योंकि "महीनों" को गणित में निपटना मुश्किल होता है। अगला $reduce
दिनांक सीमा से संचालन में केवल "विशिष्ट महीने" लगते हैं।
इसलिए पहले एकत्रीकरण पाइपलाइन चरण का परिणाम दस्तावेज़ में एक नया क्षेत्र है जो startDate
के बीच कवर किए गए सभी विशिष्ट महीनों का "सरणी" है। और समाप्ति तिथि
. यह बाकी ऑपरेशन के लिए "इटरेटर" देता है।
"इटरेटर" से मेरा मतलब उस समय से है जब हम $unwind<लागू करते हैं /कोड>
हमें अंतराल में शामिल प्रत्येक विशिष्ट महीने के लिए मूल दस्तावेज़ की एक प्रति प्राप्त होती है। इसके बाद निम्नलिखित दो $group
को अनुमति देता है
$योग
, और अगला $group
कुंजी को केवल "टाइप" बनाता है और परिणामों को के माध्यम से एक सरणी में रखता है। $पुश
।
यह उपरोक्त डेटा पर परिणाम देता है:
{
"_id" : "HGV",
"monthCounts" : [
{
"month" : 201702,
"count" : 2
},
{
"month" : 201703,
"count" : 2
},
{
"month" : 201704,
"count" : 1
}
]
}
{
"_id" : "CAR",
"monthCounts" : [
{
"month" : 201701,
"count" : 1
},
{
"month" : 201702,
"count" : 1
}
]
}
ध्यान दें कि "महीनों" का कवरेज केवल वहीं मौजूद होता है जहां वास्तविक डेटा होता है। जबकि एक सीमा पर शून्य मान उत्पन्न करना संभव है, ऐसा करने के लिए काफी तकरार की आवश्यकता होती है और यह बहुत व्यावहारिक नहीं है। यदि आप शून्य मान चाहते हैं तो परिणाम प्राप्त होने के बाद क्लाइंट में पोस्ट प्रोसेसिंग में जोड़ना बेहतर होगा।
यदि आपका दिल वास्तव में शून्य मानों पर सेट है, तो आपको $मिनट
और $max
मान, और इन्हें प्रत्येक आपूर्ति किए गए संभावित श्रेणी मान के लिए प्रतियां उत्पन्न करने के लिए पाइपलाइन को "बलपूर्वक" करने के लिए पास करें।
तो इस बार "रेंज" को सभी दस्तावेज़ों के लिए बाहरी रूप से बनाया गया है, और फिर आप $cond
संचायक में यह देखने के लिए कि क्या वर्तमान डेटा उत्पादित समूहीकृत सीमा के भीतर है। इसके अलावा चूंकि पीढ़ी "बाहरी" है, हमें वास्तव में $range
के MongoDB 3.4 ऑपरेटर की आवश्यकता नहीं है , इसलिए इसे पुराने संस्करणों पर भी लागू किया जा सकता है:
// Get min and max separately
var ranges = db.cars.aggregate(
{ "$group": {
"_id": null,
"startRange": { "$min": "$startDate" },
"endRange": { "$max": "$endDate" }
}}
).toArray()[0]
// Make the range array externally from all possible values
var range = [];
for ( var d = new Date(ranges.startRange.valueOf()); d <= ranges.endRange; d.setUTCMonth(d.getUTCMonth()+1)) {
var v = ( d.getUTCFullYear() * 100 ) + d.getUTCMonth()+1;
range.push(v);
}
// Run conditional aggregation
db.cars.aggregate([
{ "$addFields": { "range": range } },
{ "$unwind": "$range" },
{ "$group": {
"_id": {
"type": "$type",
"month": "$range"
},
"count": {
"$sum": {
"$cond": {
"if": {
"$and": [
{ "$gte": [
"$range",
{ "$add": [
{ "$multiply": [ { "$year": "$startDate" }, 100 ] },
{ "$month": "$startDate" }
]}
]},
{ "$lte": [
"$range",
{ "$add": [
{ "$multiply": [ { "$year": "$endDate" }, 100 ] },
{ "$month": "$endDate" }
]}
]}
]
},
"then": 1,
"else": 0
}
}
}
}},
{ "$sort": { "_id": 1 } },
{ "$group": {
"_id": "$_id.type",
"monthCounts": {
"$push": { "month": "$_id.month", "count": "$count" }
}
}}
])
जो सभी समूहों पर सभी संभावित महीनों के लिए लगातार शून्य भरता उत्पन्न करता है:
{
"_id" : "HGV",
"monthCounts" : [
{
"month" : 201701,
"count" : 0
},
{
"month" : 201702,
"count" : 2
},
{
"month" : 201703,
"count" : 2
},
{
"month" : 201704,
"count" : 1
}
]
}
{
"_id" : "CAR",
"monthCounts" : [
{
"month" : 201701,
"count" : 1
},
{
"month" : 201702,
"count" : 1
},
{
"month" : 201703,
"count" : 0
},
{
"month" : 201704,
"count" : 0
}
]
}
MapReduce
MongoDB के सभी संस्करण mapReduce का समर्थन करते हैं, और जैसा कि ऊपर बताया गया है, "इटरेटर" का साधारण मामला के लिए
द्वारा नियंत्रित किया जाता है मैपर में लूप। हम पहले $group
. तक जेनरेट किए गए आउटपुट को प्राप्त कर सकते हैं ऊपर से केवल ऐसा करके:
db.cars.mapReduce(
function () {
for ( var d = this.startDate; d <= this.endDate;
d.setUTCMonth(d.getUTCMonth()+1) )
{
var m = new Date(0);
m.setUTCFullYear(d.getUTCFullYear());
m.setUTCMonth(d.getUTCMonth());
emit({ id: this.type, date: m},1);
}
},
function(key,values) {
return Array.sum(values);
},
{ "out": { "inline": 1 } }
)
जो उत्पादन करता है:
{
"_id" : {
"id" : "CAR",
"date" : ISODate("2017-01-01T00:00:00Z")
},
"value" : 1
},
{
"_id" : {
"id" : "CAR",
"date" : ISODate("2017-02-01T00:00:00Z")
},
"value" : 1
},
{
"_id" : {
"id" : "HGV",
"date" : ISODate("2017-02-01T00:00:00Z")
},
"value" : 2
},
{
"_id" : {
"id" : "HGV",
"date" : ISODate("2017-03-01T00:00:00Z")
},
"value" : 2
},
{
"_id" : {
"id" : "HGV",
"date" : ISODate("2017-04-01T00:00:00Z")
},
"value" : 1
}
इसलिए इसमें सरणियों के लिए कंपाउंड करने के लिए दूसरा समूह नहीं है, लेकिन हमने समान मूल समेकित आउटपुट का उत्पादन किया।