Project data set into new objects - mongodb

I have a really simple question which has troubled me for some time. I have a list of objects containing an array of Measurements, where each of these contains a time and multiple values like below:
{
"_id" : ObjectId("5710ed8129c7f31530a537bc"),
"Measurements" : [
{
"_t" : "Measurement",
"_time" : ISODate("2016-04-14T12:31:52.584Z"),
"Measurement1" : 1
"Measurement2" : 2
"Measurement3" : 3
},
{
"_t" : "DataType",
"_time" : ISODate("2016-04-14T12:31:52.584Z"),
"Measurement1" : 4
"Measurement2" : 5
"Measurement3" : 6
},
{
"_t" : "DataType",
"_time" : ISODate("2016-04-14T12:31:52.584Z"),
"Measurement1" : 7
"Measurement2" : 8
"Measurement3" : 9
} ]
},
{
"_id" : ObjectId("5710ed8129c7f31530a537cc"),
"Measurements" : [
{
"_t" : "Measurement",
"_time" : ISODate("2016-04-14T12:31:52.584Z"),
"Measurement1" : 0
....
I want to create a query which projects the following data set into the one below. For example, query for Measurement1 and create an array of objects containing the time and value of Measurement1 (see below) via mongo aggregation framework.
{ "Measurement": [
{
"Time": ISODate("2016-04-14T12:31:52.584Z"),
"Value": 1
}
{
"Time": ISODate("2016-04-14T12:31:52.584Z"),
"Value": 4
}
{
"Time": ISODate("2016-04-14T12:31:52.584Z"),
"Value": 7
}
]}
Seems like a pretty standard operation, so I hope you guys can shed some light on this.

You can do this by first unwinding the Measurements array for each doc and then projecting the fields you need and then grouping them back together:
db.test.aggregate([
// Duplicate each doc, once per Measurements array element
{$unwind: '$Measurements'},
// Include and rename the desired fields
{$project: {
'Measurements.Time': '$Measurements._time',
'Measurements.Value': '$Measurements.Measurement1'
}},
// Group the docs back together to reassemble the Measurements array field
{$group: {
_id: '$_id',
Measurements: {$push: '$Measurements'}
}}
])

Related

Paginated aggregation with two fields

Trying to do an aggregate operation to find distinct property pairs in a collection of objects, and paginated, but the $skip and $limit doesn't seems to work.
I have a collection with the following object type
{
"_id" : {
"expiration" : ISODate("2021-06-30T00:00:00.000Z"),
"product" : "proda",
"site" : "warehouse1",
"type" : "AVAILABLE"
},
"quantity" : 2,
"date" : ISODate("2021-06-28T00:00:00.000Z"),
}
I'm trying to find distinct product/site pairs, but only 2 at a time with the following aggregation:
db.getCollection('OBJECT').aggregate( [
{ $group: { "_id": { product: "$_id.product", site: "$_id.site" } } },
{ $skip: 0 },
{ $limit: 2 }
])
With skip being 0 it returns 2 distinct product-site paris as expected, but when I increase the skip value to 2 or more for the next steps, the query will not return anything and I have many objects with distinct product-site pairs that should be returned.

How to take data N samples from MongoDB to form a graph representation on whole data set?

This is a sample document in our new app to store time-series data in MongoDB sub-document,
{
"_id" : ObjectId("5dcb6cacfb315e66b551a1a0"),
"youtubeId" : "bIWShN9rKQU",
"views" : [
{
"count" : 17506,
"at" : ISODate("2019-08-12T13:31:00.002Z")
},
{
"count" : 29576,
"at" : ISODate("2019-11-14T13:32:00.216Z")
},
{
"count" : 29579,
"at" : ISODate("2019-11-15T13:33:00.197Z")
},
{
"count" : 29582,
"at" : ISODate("2019-11-16T13:34:00.192Z")
},
{
"count" : 29586,
"at" : ISODate("2019-11-17T13:35:00.180Z")
},
{
"count" : 29595,
"at" : ISODate("2019-11-19T13:36:00.190Z")
},
{
"count" : 29597,
"at" : ISODate("2019-11-20T13:37:00.206Z")
},
{
"count" : 29604,
"at" : ISODate("2019-11-21T13:38:00.228Z")
},
{
"count" : 29606,
"at" : ISODate("2019-11-22T13:39:00.218Z")
},
{
"count" : 29613,
"at" : ISODate("2019-11-24T13:40:00.201Z")
},
{
"count" : 29619,
"at" : ISODate("2019-11-25T13:41:00.250Z")
},
{
"count" : 29624,
"at" : ISODate("2019-11-27T13:42:00.103Z")
},
{
"count" : 29636,
"at" : ISODate("2019-11-29T13:43:00.128Z")
}
]
}
Now, I wanted to send this data in a web service consumed by a mobile application for plotting the graph, but I wanted to get only 10 objects in views array which should be the representation of entire data set with respect to the time. But it should be 10 data irrespective of the size of the array.
How can I take 10 data from the entire data set like this by using the at timestamp field to create a representation of the whole data?
In the above example views is an object array, which has times from
2019-08-12T13:31:00.002Z to 2019-11-29T13:43:00.128Z (13 records as 1/
minute), so that 5 samples from that means one record per every 2
minutes approximately
var noOfSamples = 5
db.test.aggregate( [
{
$addFields: {
indexes: {
$range: [ 0,
{ $size: "$views" },
{ $ceil: { $divide: [ { $size: "$views" }, noOfSamples ] } }
]
}
}
},
{
$project: {
sample: {
$map: {
input: "$indexes",
as: "ix",
in: { $arrayElemAt: [ "$views", "$$ix" ] }
}
}
}
}
] )
NOTES:
The number of samples you want is 5, noOfSamples. The size of the views array is 13.
Number of elements in views is divided by noOfSamples; in this case you get a value of 2.6. The $ceil rounds it to the next rounded integer, which is 3 (lets call it "step"). The $range operator gives you an array of numbers between 0 and 12 with a step of 3 (0 is the views array's first index, 12 is the array's highest index). With the aggregation's first stage you get an array called as indexes: [ 0, 3, 6, 9, 12 ].
In the second stage of the aggregation, you get the views array elements by their indexes, using the indexes generated in the previous stage. The $map aggregation array operator maps the generated index from indexes to the views array element - so you get the five elements from views array as sample.

MongoDB $divide on aggregate output

Is there a possibility to calculate mathematical operation on already aggregated computed fields?
I have something like this:
([
{
"$unwind" : {
"path" : "$users"
}
},
{
"$match" : {
"users.r" : {
"$exists" : true
}
}
},
{
"$group" : {
"_id" : "$users.r",
"count" : {
"$sum" : 1
}
}
},
])
Which gives an output as:
{ "_id" : "A", "count" : 7 }
{ "_id" : "B", "count" : 49 }
Now I want to divide 7 by 49 or vice versa.
Is there a possibility to do that? I tried $project and $divide but had no luck.
Any help would be really appreciated.
Thank you,
From your question, it looks like you are assuming result count to be 2 only. In that case I can assume users.r can have only 2 values(apart from null).
The simplest thing I suggest is to do this arithmetic via javascript(if you're using it in mongo console) or in case of using it in progam, use the language you're using to access mongo) e.g.
var results = db.collection.aggregate([theAggregatePipelineQuery]).toArray();
print(results[0].count/results[1].count);
EDIT: I am sharing an alternative to above approach because OP commented about the constraint of not using javascript code and the need to be done only via query. Here it is
([
{ /**your existing aggregation stages that results in two rows as described in the question with a count field **/ },
{ $group: {"_id": 1, firstCount: {$first: "$count"}, lastCount: {$last: "$count"}
},
{ $project: { finalResult: { $divide: ['$firstCount','$lastCount']} } }
])
//The returned document has your answer under `finalResult` field

How to match and group in multiple cases in mongodb aggregation?

i have 4 players with there scores in different matches.
e.g
{user: score} -- json keys
{'a': 10}, {'a':12}, {'b':16}
I am trying to find out a way in which i can found sum of single player using aggregation function.
users.aggregation([{$match:{'user':'a'}},{$group:{_id: null, scores:{$sum:'$score'}])
i am repeating same thing for b also and continue
In shot i am doing same thing for different users for too many times.
What is the best way or different way or optimize way, so i can write aggregate query once for all users
You can just match out the required users with the $in clause, and then group as #Sourbh Gupta suggested.
db.users.aggregate([
{$match:{'user':{$in: ['a', 'b', 'c']}}},
{$group:{_id: '$user', scores:{$sum:'$score'}}}
])
group the data on the basis of user. i.e.
users.aggregation([{$group:{_id: "$user", scores:{$sum:'$score'}}}])
Not too sure about your document structures, but if you've got 2 diffrent fields for 2 diffrent scores you can group together and sum then and then project and sum then 2 grouped sums (if that makes sense)
So for example, I have these docuemnts:
> db.scores.find()
{ "_id" : ObjectId("5858ed67b11b12dce194eec8"), "user" : "bob", "score" : { "a" : 10 } }
{ "_id" : ObjectId("5858ed6ab11b12dce194eec9"), "user" : "bob", "score" : { "a" : 12 } }
{ "_id" : ObjectId("5858ed6eb11b12dce194eeca"), "user" : "bob", "score" : { "b" : 16 } }
Notice we have a user bob and he has 2x a scores and 1x b score.
We can now write an aggregation query to do a match for bob then sum the scores.
db.scores.aggregate([
{ $match: { user : "bob" } },
{ $group: { _id : "$user", sumA : { $sum : "$score.a" }, sumB : { $sum : "$score.b" } } },
{ $project: { user: 1, score : { $sum: [ "$sumA", "$sumB" ] } } }
]);
This will give us the following result
{ "_id" : "bob", "score" : 38 }

sort by date with aggregate request in mongodb

I would like to retrieve a list of values ​​that comes from the oldest document currently signed.But i failed to select a document absed on the date.Thanks
here is json :
"ad" : "noc3",
"createdDate" : ISODate(),
"list" : [
{
"id" : "p45",
"value" : 21,
},
{
"id" : "p6",
"value" : 20,
},
{
"id" : "4578",
"value" : 319
}
]
and here my aggregate request :
db.friends.aggregate({$match:{advertiser:"noc3", {$sort:{timestamps:-1},{$limit:1} }},{$unwind:"$list"},{$project:{_id: "$list.id", value:{$add:[0]}}});
Your aggregate query is incorrect. You add the sort and limit to the match, but that's now how you do that. You use different pipeline operators:
db.friends.aggregate( [
{ $match: { advertiser: "noc3" } },
{ $sort: { createdDate: -1 } },
{ $limit: 1 },
Your other pipeline operators are bit strange too, and your code vs query mismatches on timestamps vs createdDate. If you add the expected output, I can update the answer to include the last bits of the query too.