Sum time series with different time stamps MongoDB - mongodb

I have a mongoDB database with multiple time series data and each time stamp is a separate document with some additional meta data from sensors. I want to sum the two time series in an aggregation but I am heavily struggling with that and can't find any examples.
Assume we have sensor A and B and the time stamps from the different sensors don't align. See an example of the data below. Next I want to sum the "volume" metric of the two time series. So for the example below sensor A has two time stamps en sensor B 3. So the sum of A and B should have 5 time stamps such that the sum reflects all the changes in the total volume (see also the schematic example below).
Anyone knows how to solve this in a mongoDB aggregation query? I can only use the mongoDB query language and not use NodeJS.
Sensor A
{
"_id":5d67d9ee074e99274eef30d5
"sensor": A
"volume":12.4
"temperatue": 20
"timestamp":2019-08-29 15:58:06.093
"__v":0
}
{
"_id":5d67da66074e99274eef30ea
"sensor": A
"volume":12.3
"temperatue": 21
"timestamp":2019-08-29 16:48:06.078
"__v":0
}
..etc
Sensor B
{
"_id":5d67d9ee074e99274eef30d5
"sensor": B
"volume":32.4
"temperatue": 20
"timestamp":2019-08-29 15:55:06.093
"__v":0
}
{
"_id":5d67da66074e99274eef30ea
"sensor": B
"volume":21.2
"temperatue": 21
"timestamp":2019-08-29 16:49:06.178
"__v":0
}
{
"_id":5d67da66074e99274eef30ea
"sensor": B
"volume":22.3
"temperatue": 22
"timestamp":2019-08-29 17:04:06.078
"__v":0
}
..etc
Here also a sketch of the result I would like to have.

Try this one:
db.collection.aggregate([
// Determine start and end-time
{ $sort: { timestamp: -1 } },
{ $group: { _id: "$sensor", data: { $push: "$$ROOT" } } },
{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
$mergeObjects: [
"$$this",
{
timestamp_end: {
$ifNull: [ { $last: "$$value.timestamp" }, "$$NOW" ]
}
}
]
}
]
]
}
}
}
}
},
{ $unwind: "$data" },
// find data per interval
{ $sort: { "data.timestamp": 1 } },
{
$group: {
_id: null,
data: { $push: "$data" },
timestamp: { $addToSet: "$data.timestamp" }
}
},
{
$set: {
sum_data: {
$map: {
input: "$timestamp",
as: "t",
in: {
$filter: {
input: "$data",
cond: {
$and: [
{ $lte: [ "$$this.timestamp", "$$t" ] },
{ $gt: [ "$$this.timestamp_end", "$$t" ] }
]
}
}
}
}
}
}
},
// sum up temperatures
{
$set: {
volume: {
$map: {
input: "$sum_data",
in: { $sum: "$$this.volume" }
}
},
result: { $range: [ 0, { $size: "$timestamp" } ] }
}
},
// Join arrays to final result
{
$project: {
result: {
$map: {
input: "$result",
as: "i",
in: {
timestamp: { $arrayElemAt: [ "$timestamp", "$$i" ] },
volume: { $arrayElemAt: [ "$volume", "$$i" ] }
}
}
}
}
}
])
Mongo Playground

Related

want to convert "00:10:00" to a single integer in mongodb

There are many documents in the collection which contains this field timeTaken: "00:10:00",
I want to sum up from all the documents and have to give a single integer in mongodb robo3T.
That is for the following documents:
[
{ timeTaken: "00:10:00" },
{ timeTaken: "01:10:00" },
{ timeTaken: "02:20:50" }
]
I want the result to be:
{ timeTaken: "03:40:50" }
Our strategy will be to split the string into minutes, seconds and hours, convert them to numbers, sum them up and then reconstruct the structure.
For this you will need access to operators like $toString and $toInt which means you can only do this for version 4.0+, for older Mongo versions you will have to read the documents and do this in code.
I've split the following query into multiple stages so it's clearer what I'm doing but this could be re-written into just 2 stages, the $group stage and a final $project stage to restructure the data.
db.collection.aggregate([
{
"$addFields": {
dataParts: {
$map: {
input: {
$split: [
"$data",
":"
]
},
as: "num",
in: {
"$toInt": "$$num"
}
}
},
}
},
{
$group: {
_id: null,
seconds: {
$sum: {
"$arrayElemAt": [
"$dataParts",
2
]
}
},
minutes: {
$sum: {
"$arrayElemAt": [
"$dataParts",
1
]
}
},
hours: {
$sum: {
"$arrayElemAt": [
"$dataParts",
0
]
}
},
}
},
{
"$addFields": {
finalSeconds: {
$mod: [
"$seconds",
60
]
},
}
},
{
$addFields: {
minutes: {
$sum: [
"$minutes",
{
"$divide": [
{
"$subtract": [
"$seconds",
"$finalSeconds"
]
},
60
]
}
]
},
}
},
{
$addFields: {
finalMinutes: {
$mod: [
"$minutes",
60
]
},
finalHours: {
$sum: [
"$hours",
{
$mod: [
{
$max: [
{
"$subtract": [
"$minutes",
60
]
},
0
]
},
60
]
}
]
}
}
},
{
$project: {
final: {
$concat: [
{
"$toString": "$finalHours"
},
":",
{
"$toString": "$finalMinutes"
},
":",
{
"$toString": "$finalSeconds"
},
]
}
}
}
])
Mongo Playground

How can I select all data based on date range mongoDB?

I have a lists of records like below
[
{
"product": "p1",
"salesdate": "2020-02-01",
"amount": 100
},
{
"product": "p2",
"salesdate": "2020-02-04",
"amount": 200
},
]
On 2nd feb and 3rd feb i don't have data. But I need to add this in my result. My expected result is
[
{
"amount": 100,
"salesdate": "2020-02-01"
},
{
"amount": 0,
"salesdate": "2020-02-02"
},
{
"amount": 0,
"salesdate": "2020-02-03"
}
{
"amount": 200,
"salesdate": "2020-02-04"
}
]
Can I achieve this using mongoDB?
https://mongoplayground.net/p/EiAJdY9jRHn
You can use $reduce for it. Whenever one has to work with data/time values, then I recommend the moment.js library. You don't have to use it, but it makes your life easier.
db.collection.aggregate([
// Put all data into one document
{ $group: { _id: null, data: { $push: "$$ROOT" } } },
// Add missing days
{
$addFields: {
data: {
$reduce: {
// Define the range of date
input: { $range: [0, moment().get('day')] },
initialValue: [],
in: {
$let: {
vars: {
ts: {
$add: [moment().startOf('month').toDate(), { $multiply: ["$$this", 1000 * 60 * 60 * 24] }]
}
},
in: {
$concatArrays: [
"$$value",
[{
$ifNull: [
{ $first: { $filter: { input: "$data", cond: { $eq: ["$$this.salesdate", "$$ts"] } } } },
// Default value for missing days
{ salesdate: "$$ts", amount: 0 }
]
}]
]
}
}
}
}
}
}
},
{ $unwind: "$data" },
{ $replaceRoot: { newRoot: "$data" } }
// If requried add further $group stages
])
Note, this code returns values from first day of current months to current day (not 2020 as in your sample data). You may adapt the ranges - your requirements are not clear from the question.
Mongo Playground

Dynamic key in MongoDB

Im trying to create a dynamic group by (with sum agg) in MongoDB. But don't know how to right syntax that.
Lets imaging 2 documents:
{
"_id": {"$oid":"5f69f6a360c8479d0908a649"},
"key":"key1",
"data":{
"key1":"value1",
"key2":"value2",
"key3":"value3",
"key4":"value4"
},
"count":10
}
{
"_id": {"$oid":"5f69f6a360c8479d0908a649"},
"key":"key2",
"data":{
"key1":"value5",
"key2":"value6",
"key3":"value7",
"key4":"value8"
},
"count":15
}
With the key attribute, I want to control, which is the groupby attribute.
A pseudo query could look like:
[{
$group: {
_id: {
'$key': data[$key]
},
sum: {
'$sum': '$count'
}
}
}]
Output should look like:
value1 : 10
value6 : 15
Somebody knows how to do that?
I don't understand the purpose of $sum and $group, there are no arrays in your documents.
This aggregation pipeline give desired result:
db.collection.aggregate([
{ $set: { data: { $objectToArray: "$data" } } },
{ $set: { data: { $filter: { input: "$data", cond: { $eq: ["$$this.k", "$key"] } } } } },
{ $set: { data: { k: { $arrayElemAt: ["$data.v", 0] }, v: "$count" } } },
{ $set: { data: { $arrayToObject: "$data" } } },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$$ROOT", "$data"] } } },
{ $unset: ["key", "count", "data"] }
])
You can try,
$reduce input data as array using $objectToArray, check condition if key matches with data key then return key as value and value as count field
convert that returned key and value object array to exact object using $arrayToObject
replace field using $replaceWith
db.collection.aggregate([
{
$replaceWith: {
$arrayToObject: [
[
{
$reduce: {
input: { $objectToArray: "$data" },
initialValue: {},
in: {
$cond: [
{ $eq: ["$$this.k", "$key"] },
{
k: "$$this.v",
v: "$count"
},
"$$value"
]
}
}
}
]
]
}
}
])
Playground

How to find prev/next document after sort in MongoDB

I want to find prev/next blog documents whose publish date is closest to the input document.
Below is the document structure.
Collection Examples (blog)
{
blogCode: "B0001",
publishDate: "2020-09-21"
},
{
blogCode: "B0002",
publishDate: "2020-09-22"
},
{
blogCode: "B0003",
publishDate: "2020-09-13"
},
{
blogCode: "B0004",
publishDate: "2020-09-24"
},
{
blogCode: "B0005",
publishDate: "2020-09-05"
}
If the input is blogCode = B0003
Expected output
{
blogCode: "B0005",
publishDate: "2020-09-05"
},
{
blogCode: "B0001",
publishDate: "2020-09-21"
}
How could I get the output result? In sql, it seems using ROW_NUMBER can solve my problem, however I can't find a solution to achieve the feature in MongoDB. The alternate solution may be reference to this answer (But, it seems inefficient). Maybe using mapReduce is another better solutions? I'm confused at the moment, please give me some help.
You can go like following.
We need to compare existing date with given date. So I used $facet to categorize both dates
The original data should be one Eg : B0003. So that I just get the first element of the origin[] array to compare with rest[] array
used $unwind to flat the rest[]
Substract to get the different between both dates
Again used $facet to find previous and next dates.
Then combined both to get your expected result
NOTE : The final array may have 0<elements<=2. The expected result given by you will not find out whether its a prev or next date if there is a one element. So my suggestion is add another field to say which date it is as the mongo playground shows
[{
$facet: {
origin: [{
$match: { blogCode: 'B0001' }
}],
rest: [{
$match: {
$expr: {
$ne: ['$blogCode','B0001']
}
}
}]
}
}, {
$project: {
origin: {
$arrayElemAt: ['$origin',0]
},
rest: 1
}
}, {
$unwind: {path: '$rest'}
}, {
$project: {
diff: {
$subtract: [{ $toDate: '$rest.publishDate' },{ $toDate: '$origin.publishDate'}]
},
rest: 1,
origin: 1
}
}, {
$facet: {
prev: [{
$sort: {diff: -1}
},
{
$match: {
diff: {$lt: 0 }
}
},
{
$limit: 1
},
{
$addFields:{"rest.type":"PREV"}
}
],
next: [{
$sort: { diff: 1 }
},
{
$match: {
diff: { $gt: 0 }
}
},
{
$limit: 1
},
{
$addFields:{"rest.type":"NEXT"}
}
]
}
}, {
$project: {
combined: {
$concatArrays: ["$prev", "$next"]
}
}
}, {
$unwind: {
path: "$combined"
}
}, {
$replaceRoot: {
newRoot: "$combined.rest"
}
}]
Working Mongo playground
Inspire for the solution of varman proposed. I also find another way to solve my problem by using includeArrayIndex.
[
{
$sort: {
"publishDate": 1
},
},
{
$group: {
_id: 1,
root: {
$push: "$$ROOT"
}
},
},
{
$unwind: {
path: "$root",
includeArrayIndex: "rownum"
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$root",
{
rownum: "$rownum"
}
]
}
}
},
{
$facet: {
currRow: [
{
$match: {
blogCode: "B0004"
},
},
{
$project: {
rownum: 1
}
}
],
root: [
{
$match: {
blogCode: {
$exists: true
}
}
},
]
}
},
{
$project: {
currRow: {
$arrayElemAt: [
"$currRow",
0
]
},
root: 1
}
},
{
$project: {
rownum: {
prev: {
$add: [
"$currRow.rownum",
-1
]
},
next: {
$add: [
"$currRow.rownum",
1
]
}
},
root: 1
}
},
{
$unwind: "$root"
},
{
$facet: {
prev: [
{
$match: {
$expr: {
$eq: [
"$root.rownum",
"$rownum.prev"
]
}
}
},
{
$replaceRoot: {
newRoot: "$root"
}
}
],
next: [
{
$match: {
$expr: {
$eq: [
"$root.rownum",
"$rownum.next"
]
}
}
},
{
$replaceRoot: {
newRoot: "$root"
}
}
],
}
},
{
$project: {
prev: {
$arrayElemAt: [
"$prev",
0
]
},
next: {
$arrayElemAt: [
"$next",
0
]
},
}
},
]
Working Mongo playground

Multiple sums with different calculations mongodb

maybe someone can help me. I have the following table in mongodb and I need to perform the following calculation:
Odds:
High
Average
Low
For each probability, a multiplier must be applied
Example:
High probability: Value * 0.87
Average probability: Value * 0.5
Low Probability: Value * 0.06
I made the following query in the db mongo, but I can apply only one multiplier. I was unable to differentiate each probability to multiply by the above values.
db.teste.aggregate(
{
$match: {
$and: [
{
"converted_fields.Probabilidade de fechamento": {
$ne: null
},
"current.value": {
$ne: 0
},
"current.add_time": {
$gte: ISODate("2020-07-01")
},
}
]
}
},
{
$project: {
"_id": "$_id",
"___group": {
"probabilidade": "$converted_fields.Probabilidade de fechamento"
},
"current___value": "$current.value"
}
},
{
$group: {
"_id": "$___group",
"count": {
$sum: "$current___value"
}
}
},
{
$project: {
"_id": 0,
"probabilidade": "$_id.probabilidade",
"valor": {
$multiply: ["$count", 0.5]
}
}
}
)
Result:
{
Alta - 379,5
Média - 1647,9
Baixa - 3763,32
}
how do I separate a different multiplier for each probability?
The aggregation might look something like this:
db.teste.aggregate([
{
$match: {
$and: [
{
"converted_fields.Probabilidade de fechamento": {
$ne: null
},
"current.value": {
$ne: 0
},
"current.add_time": {
$gte: ISODate("2020-07-01")
},
}
]
}
},
{
$group: {
_id: "$converted_fields.Probabilidade de fechamento",
count: { $sum: "$current.value"}
}
},
{
$project:
{
_id: 1,
valor:
{
$switch:
{
branches: [
{
case: { $eq: [ "$_id", "Alta"] },
then: { $multiply: ["$count", 0.87] }
},
{
case: { $eq: [ "$_id", "Médica"] },
then: { $multiply: ["$count", 0.5] }
},
{
case: { $eq: [ "$_id", "Baixa"] },
then: { $multiply: ["$count", 0.06] }
}
],
default: 0
}
}
}
},
{
$group: {
_id: null,
probabilidades: {
$push: {
k: "$_id",
v: "$valor"
}
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$probabilidades"
}
}
}
])
The first $match stage is still as you had it. In my solution the first $group stage will return documents of this form:
{
_id: 'Alta',
count: 100
}
In the following $project stage, I use the $switch operator in order to determine what to multiply count by in order to get the correct valor. Using the sample document I showed before, this stage will return documents that look like this:
{
_id: 'Alta',
valor: 87
}
Next is another $group stage, where I group all of the probability documents together, and push them into an array. The document from this stage might look like this:
{
_id: null,
probabilidades: [
{ 'k': 'Alta', 'v': 87 },
{ 'k': 'Baixa', 'v': 6 }
]
}
In the final stage, $replaceRoot, I use $arrayToObject to turn the probabilidades array into your desired output.