Multiply last 4 record in mongo db - mongodb

I want multiplication on value field in last 4 record in mongodb
I have the following data
/* 1 */
{
"_id" : ObjectId("5d6d38f4509717526ea8469c"),
"code" : "302",
"value" : 123,
"timestamp" : ISODate("2019-09-02T15:44:52.012Z"),
"createdDate" : ISODate("2019-09-02T15:44:52.012Z"),
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("5d6d38f4509717526ea8469e"),
"code" : "340",
"value" : 8,
"timestamp" : ISODate("2019-09-02T15:44:52.013Z"),
"createdDate" : ISODate("2019-09-02T15:44:52.013Z"),
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("5d6d38f4509717526ea8469d"),
"code" : "327",
"value" : 23,
"timestamp" : ISODate("2019-09-02T15:44:52.013Z"),
"createdDate" : ISODate("2019-09-02T15:44:52.013Z"),
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("5d6d491d509717526ea8469f"),
"code" : "301",
"value" : 3.48,
"timestamp" : ISODate("2019-09-02T16:53:49.560Z"),
"createdDate" : ISODate("2019-09-02T16:53:49.560Z"),
"__v" : 0
}
and I want to multiply the last 4 records of each code and multiply them, Something like this
last records of each code and multiply all code value and get 1 value.

The more "proper" way to do this would look like this:
db.collection.aggregate([
{ // can remove this step if same code docs are sorted in database
$sort: {
createdDate: -1
}
},
{
$group: {
_id: "$code",
root: {$last: "$ROOT"}
}
},
{
$sort: {
"root.createdDate": -1
}
},
{
$limit: 4
},
{
$group: {
_id: null,
values: {$push: "$root.value"}
}
},
{
$project: {
total:
{
$reduce: {
input: "$values",
initialValue: 1,
in: {$multiply: ["$$value", "$$this"]}
}
}
}
}
])
However on large scale the "proper" way is not really that efficient as you have to scan the entire collection every time.
If indeed that is your case i recommend either using heuristics to fetch a certain amount of documents by sorting and limiting or just batch fetching until the condition is met and then doing the calculation in code. (this is dependant on how your data distribution is)

Related

How to get the difference of two ISO String dates with an aggregation MongoDB query?

I have a collection called biosignals in my MongoDB, in which there exist entries that represent physical activity (e.g. walking).
Each such entry has a 'start_date_time' and an 'end_date_time', which are ISO strings (e.g. 2017-04-26T07:12:09.463Z).
I want to do the following query, where I group the physical activity entries by day and I calculate the total duration of activity for each day.
db.biosignals.aggregate([
{
$match: {
"name": "physical-activity"
}
},
{
$project: {
duration: {
"$subtract": [new Date("$end_date_time"), new Date("$start_date_time")]
},
date: {
$substr: [ "$start_date_time", 0, 10]
}
}
},
{
$group: {
_id: "$date",
total: { $sum: "$duration" }
}
},
{
$sort: {
_id: 1
}
}
])
However, I only get 0 as a result, as shown here:
{ "_id" : "2017-04-24", "total" : NumberLong(0) }
{ "_id" : "2017-04-25", "total" : NumberLong(0) }
{ "_id" : "2017-04-26", "total" : NumberLong(0) }
{ "_id" : "2017-04-27", "total" : NumberLong(0) }
If, instead, I hardcode the dates (e.g. start_date_time = 2017-04-26T07:12:08.463Z and end_date_time = 2017-04-26T07:12:09.463Z, that is one second difference), I get the expected result:
{ "_id" : "2017-04-24", "total" : NumberLong(16000) }
{ "_id" : "2017-04-25", "total" : NumberLong(3000) }
{ "_id" : "2017-04-26", "total" : NumberLong(7000) }
{ "_id" : "2017-04-27", "total" : NumberLong(12000) }
How could I fix that?
Thank you very much!

Multiple conditional sums in mongodb aggregation

I'm trying to return the total of requests by type based on their status:
If there is no status set, the request should be added to requested
If the status is ordered, the request should be added to ordered
If the status is arrived, the request should be added to arrived
caseRequest.aggregate([{
$group: {
_id: "$product",
suggested: {
$sum: {
$cond: [{
$ifNull: ["$status", true]
},
1, 0
]}
},
ordered: {
$sum: {
$cond: [{
$eq: ["$status", "ordered"]
},
1, 0
]
}
},
arrived: {
$sum: {
$cond: [{
$eq: ["$status", "arrived"]
},
1, 0
]
}
}
}
}
But for some reason it doesn't find any request status ordered or arrived. If in the database I have 48 requests, 45 of them without status, 2 with ordered and 1 with arrived, it returns:
[
{
_id: "xxx",
suggested: 48,
ordered: 0,
arrived: 0,
},
...
]
Try this approach,
Return the total number of requests by type based on their status
Now the simplest way to get the count of different status is to use aggregate pipeline with $group on the status field
db.stackoverflow.aggregate([{ $group: {_id: "$status", count: {$sum:1}} }])
We will be getting a result similar to this
{ "_id" : "", "count" : 2 }
{ "_id" : "arrived", "count" : 3 }
{ "_id" : "ordered", "count" : 4 }
The schema which is used to retrieve these records is very simple so that it will be easier to understand. The schema will have a parameter on the top level of the document and the value of status can be "ordered", "arrived" or empty
Schema
{ "_id" : ObjectId("5798c348d345404e7f9e0ced"), "status" : "ordered" }
The collection is populated with 9 records, with status as ordered, arrived and empty
db.stackoverflow.find()
{ "_id" : ObjectId("5798c348d345404e7f9e0ced"), "status" : "ordered" }
{ "_id" : ObjectId("5798c349d345404e7f9e0cee"), "status" : "ordered" }
{ "_id" : ObjectId("5798c34ad345404e7f9e0cef"), "status" : "ordered" }
{ "_id" : ObjectId("5798c356d345404e7f9e0cf0"), "status" : "arrived" }
{ "_id" : ObjectId("5798c357d345404e7f9e0cf1"), "status" : "arrived" }
{ "_id" : ObjectId("5798c358d345404e7f9e0cf2"), "status" : "arrived" }
{ "_id" : ObjectId("5798c35ad345404e7f9e0cf3"), "status" : "ordered" }
{ "_id" : ObjectId("5798c361d345404e7f9e0cf4"), "status" : "" }
{ "_id" : ObjectId("5798c362d345404e7f9e0cf5"), "status" : "" }
db.stackoverflow.count()
9
Hope it Helps!!

Mongo db skip-limit work before match after unwind

This is my query:
db.getCollection('grades').
aggregate([{ "$match" : { "class_id" : 28, "student_id" : 0 } },
{ "$unwind" : "$scores" },
{ "$match" : { "scores.type" : "homework" } },
{ "$skip" : 3 }, { "$limit" : 3 },
{ "$group" : { "_id" : { "id" : "$_id" }, "scores" : { "$push" : "$scores" } } },
{ "$project" : { "_id" : "$_id.id", "scores" : 1 } }])
scores - is a nested array of objects. Score object - {type: "someType", score: someScore}. This query returns one document.
The problem: array of scores has 6 objects and 4 of them have type homework.
The result, what I've received: http://prntscr.com/bq217r
The original document: http://prntscr.com/bq23bv
Why skip-limit performed before match operator? How can I fix it?
As per attached screenshot everything looks OK.
we have 4 elements 1 2 3 4, then we are skipping 3, so we get 4 at the end... and 53 is the value :-)
btw your skip/limit is after $match

mongodb aggregation find min value and other fields in nested array

Is it possible to find in a nested array the max date and show its price then show the parent field like the actual price.
The result I want it to show like this :
{
"_id" : ObjectId("5547e45c97d8b2c816c994c8"),
"actualPrice":19500,
"lastModifDate" :ISODate("2015-05-04T22:53:50.583Z"),
"price":"16000"
}
The data :
db.adds.findOne()
{
"_id" : ObjectId("5547e45c97d8b2c816c994c8"),
"addTitle" : "Clio pack luxe",
"actualPrice" : 19500,
"fistModificationDate" : ISODate("2015-05-03T22:00:00Z"),
"addID" : "1746540",
"history" : [
{
"price" : 18000,
"modifDate" : ISODate("2015-05-04T22:01:47.272Z"),
"_id" : ObjectId("5547ec4bfeb20b0414e8e51b")
},
{
"price" : 16000,
"modifDate" : ISODate("2015-05-04T22:53:50.583Z"),
"_id" : ObjectId("5547f87e83a1dae00bc033fa")
},
{
"price" : 19000,
"modifDate" : ISODate("2015-04-04T22:53:50.583Z"),
"_id" : ObjectId("5547f87e83a1dae00bc033fe")
}
],
"__v" : 1
}
my query
db.adds.aggregate(
[
{ $match:{addID:"1746540"}},
{ $unwind:"$history"},
{ $group:{
_id:0,
lastModifDate:{$max:"$historique.modifDate"}
}
}
])
I dont know how to include other fields I used $project but I get errors
thanks for helping
You could try the following aggregation pipeline which does not need to make use of the $group operator stage as the $project operator takes care of the fields projection:
db.adds.aggregate([
{
"$match": {"addID": "1746540"}
},
{
"$unwind": "$history"
},
{
"$project": {
"actualPrice": 1,
"lastModifDate": "$history.modifDate",
"price": "$history.price"
}
},
{
"$sort": { "lastModifDate": -1 }
},
{
"$limit": 1
}
])
Output
/* 1 */
{
"result" : [
{
"_id" : ObjectId("5547e45c97d8b2c816c994c8"),
"actualPrice" : 19500,
"lastModifDate" : ISODate("2015-05-04T22:53:50.583Z"),
"price" : 16000
}
],
"ok" : 1
}

Mongo DB aggregation framework calculate avg documents

I have question collection each profile can have many questions.
{"_id":"..." , "pid":"...",.....}
Using mongo DB new aggregation framework how can I calculate the avg number of questions per profile?
tried the following without success:
{ "aggregate" : "question" , "pipeline" : [ { "$group" : { "_id" : "$pid" , "qCount" : { "$sum" : 1}}} , { "$group" : { "qavg" : { "$avg" : "qCount"} , "_id" : null }}]}
Can it be done with only one group operator?
Thanks.
For this you just need to know the amount of questions, and the amount of different profiles (uniquely identified with "pid" I presume). With the aggregation framework, you need to do that in two stages:
First, you calculate the number of questions per PID
Then you calculate the average of questions per PID
You'd do that like this:
Step one:
db.profiler.aggregate( [
{ $group: { _id: '$pid', count: { '$sum': 1 } } },
] );
Which outputs (in my case, with some sample data):
{
"result" : [
{ "_id" : 2, "count" : 7 },
{ "_id" : 1, "count" : 1 },
{ "_id" : 3, "count" : 3 },
{ "_id" : 4, "count" : 5 }
],
"ok" : 1
}
I have four profiles, respectively with 7, 1, 3 or 5 questions.
Now with this result, we run another group, but in this case we don't really want to group by anything, and thus do we need to set the _id value to null as you see in the second group below:
db.profiler.aggregate( [
{ $group: { _id: '$pid', count: { '$sum': 1 } } },
{ $group: { _id: null, avg: { $avg: '$count' } } }
] );
And then this outputs:
{
"result" : [
{ "_id" : null, "avg" : 4 }
],
"ok" : 1
}
Which tells me that I have on average, 4 questions per profile.