MongoDB aggregation without duplication - mongodb

I have the following records:
{ "_id" : 1, "c" : 120, "b" : [ { "f1" : 10 }, { "f1" : 10 } ] }
{ "_id" :2, "c" : 5, "b" : [ { "f1" : 10 }, { "f1" : 10 } ] }
I need the output this way:
{ "_id" : 1, 'total':140}
{ "_id" :2, 'total':25 }
where total = sum of value in 'c' with sum of values in f1 for same record.
When i unwind the field 'b' it creates two documents with same id and hence data is duplicated and when i sum it up, i get:
db.test2.aggregate([
{'$unwind':'$b'},
{'$project':{'total':{'$add':['$c','$b.f1']}}},
{'$group':{'_id':'$_id', 'total':{'$sum':'$total'}}}
])
outputs:
{ "_id" : 1, 'total':260}
{ "_id" :2, 'total':30 }
(not what i wanted, as it has added 120 and 5 again to total due to duplication during unwinding)
So i tried:
db.test2.aggregate([
{'$unwind':'$b'},
{'$group':{'_id':'$_id', 'c':{'$push': '$c'},'f1':{'$sum':'$b.f1'}}},
{'$project':{'total':{'$add':[{'$arrayElemAt':['$c',0]},'$f1']}}}
])
outputs:
{ "_id" : 1, 'total':140}
{ "_id" :2, 'total':25 }
( what i wanted)
Is there any other way to achieve this?

You can try below query. Sum operator to first calculate sum in array followed by add to calculate total with other field.
db.test2.aggregate([{
$project: {
total: {"$add":["$c", {"$sum":"$b.f1"}]}
}
}]

An alternative:
db.test2.aggregate([{
$project: {
_id: 0,
c: "$c",
b: {
$reduce: {
input: "$b.f1",
initialValue: 0,
in: {
$add: ["$$value", "$$this"]
}
}
}
}
},
{
$project: {
_id: 0,
total: {
$sum: ["$c", "$b"]
}
}
}
])
That would create result:
{
"total" : 140
}
{
"total" : 25
}
If you need the field _id then replace the _id: 0 in both $project to _id: 1
That would create this result:
{
"_id" : 1,
"total" : 140
}
{
"_id" : 2,
"total" : 25
}

Related

MongoDB rename boundary values for output

I have this query to display people in certain age groups.
db.people.aggregate([
{
$bucket: {
groupBy: "$age",
boundaries: [ 0, 10, 20, 30, Number.POSITIVE_INFINITY ],
default: Number.NEGATIVE_INFINITY,
output: {
"count": { $sum: 1 }
}
}
}
])
enter code here
which produces following output:
{ "_id" : 0, "count" : 5 }
{ "_id" : 10, "count" : 10 }
{ "_id" : 20, "count" : 6 }
{ "_id" : 30, "count" : 9 }
I want to display it as
{ "_id" : 0, "count" : 5 }
{ "_id" : 10, "count" : 10 }
{ "_id" : 20, "count" : 6 }
{ "_id" : ">30", "count" : 9 }
How can I make it? Thanks.
There's no hacky way this make this happen, you'll have to add a $project
stage and specify the logic you want, like so:
db.people.aggregate([
{
$bucket: {
groupBy: "$age",
boundaries: [ 0, 10, 20, 30, Number.POSITIVE_INFINITY ],
default: Number.NEGATIVE_INFINITY,
output: {
"count": { $sum: 1 }
}
}
},
{
$project: {
_id: {
$cond: [{$eq: ['$_id', 30]}, '>30', '$_id']
},
count: 1
}
}
])

How to subtract time series element to get differance to the date before?

I am trying to build a dashboard chart in Mongo-Atlas.
The Table should should show the date on x-axis, the _id on y-axis.
The Values should be the count difference to the date before.
I have a collection with data points such as:
_id: "someName"
timestamp: 2019-09-05T06:24:24.689+00:00
count: 50
_id: "someName"
timestamp: 2019-09-04T06:24:24.689+00:00
count: 40
...
The goal is to get the difference of the count to the data point before. Having the same name.
_id: "someName"
timestamp: 2019-09-05T06:24:24.689+00:00
count: 50
difference: 10
_id: "someName"
timestamp: 2019-09-04T06:24:24.689+00:00
count: 40
difference: 17
...
That way I could make a table listing the differences
so far I created a aggregation pipeline
[
{$sort: {
"timestamp": -1
}},
{$group: {
_id: "$_id",
count: {
$push: { count: "$count", timestamp: "$timestamp" }
}
}},
{$project: {
_id: "$_id",
count: "$count",
countBefore: { $slice: [ "$count", 1, { $size: "$count" } ] }
}}
]
I was hoping to substract count and countBefore such that i get an array with the datapoints an the difference...
So I tried to follow with:
{$project: {
countDifference: {
$map: {
input: "$countBefore",
as: "before",
in: {
$subtract: ["$$before.count", "$count.count"]
/*"$count.count" seems to be the problem, since an integer works*/
}
}
}
}
}
Mongo Atlas only shows "An unknown error occurred"
I would be glad for some advice :)
The following query can get us the expected output:
db.collection.aggregate([
{
$sort:{
"timestamp":1
}
},
{
$group:{
"_id":"$id",
"counts":{
$push:"$count"
}
}
},
{
$project:{
"differences":{
$reduce:{
"input":"$counts",
"initialValue":{
"values":[],
"lastValue":0
},
"in":{
"values":{
$concatArrays:[
"$$value.values",
[
{
$subtract:["$$this","$$value.lastValue"]
}
]
]
},
"lastValue":"$$this"
}
}
}
}
},
{
$project:{
"_id":0,
"id":"$_id",
"plots":"$differences.values"
}
}
]).pretty()
Data Set:
{
"_id" : ObjectId("5d724550ef5e6630fde5b71e"),
"id" : "someName",
"timestamp" : "2019-09-05T06:24:24.689+00:00",
"count" : 50
}
{
"_id" : ObjectId("5d724550ef5e6630fde5b71f"),
"id" : "someName",
"timestamp" : "2019-09-04T06:24:24.689+00:00",
"count" : 40
}
{
"_id" : ObjectId("5d724796ef5e6630fde5b720"),
"id" : "someName",
"timestamp" : "2019-09-06T06:24:24.689+00:00",
"count" : 61
}
{
"_id" : ObjectId("5d724796ef5e6630fde5b721"),
"id" : "someName",
"timestamp" : "2019-09-07T06:24:24.689+00:00",
"count" : 72
}
{
"_id" : ObjectId("5d724796ef5e6630fde5b722"),
"id" : "someName",
"timestamp" : "2019-09-08T06:24:24.689+00:00",
"count" : 93
}
{
"_id" : ObjectId("5d724796ef5e6630fde5b723"),
"id" : "someName",
"timestamp" : "2019-09-09T06:24:24.689+00:00",
"count" : 100
}
Output:
{ "id" : "someName", "plots" : [ 40, 10, 11, 11, 21, 7 ] }
Explanation: We are pushing count for the same id into counts array and then applying $reduce operation on it to prepare a set of new values in which current value would hold difference between the current and previous value of counts array. For the very first value, the previous value is taken as zero.

Mongo $filter (aggregation) get only one element of array

I do some filter with mongodb but it return all of the data in my array. But i only want to get the specific element from that array. I cant find it in the document.
db.sales.aggregate([
{
$project: {
items: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: [ "$$item.price", 100 ] }
}
}
}
}
])
Run above command I will this is result
{
"_id" : 0,
"items" : [
{ "item_id" : 2, "quantity" : 1, "price" : 240 }
]
}
Question is I only want to get the price
{
"_id" : 0,
"items" : [
{ "price" : 240 }
]
}
or even
{
"price" : 240
}
How to do it?
You actually need $map to "alter" the array elements returned, as $filter only "selects" the array elements that "match". Try to run the below code.
ds.sales.aggregate([
{
$project: {
items: {
$map: {
input: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: [ "$$item.price", 100 ] }
}
},
"as": "a",
"in": {
"price": "$$a.price"
}
}
}
}
}], function (err, list) {
...
I don't know your whole data looks like, if your data looks like this
{
"_id" : 0,
"items" : [
{
"item_id" : 1,
"quantity" : 5,
"price" : 80
},
{
"item_id" : 2,
"quantity" : 1,
"price" : 240
},
{
"item_id" : 3,
"quantity" : 4,
"price" : 320
}
]
}
Just try this
> db.sales.aggregate([
{'$unwind': '$items'},
{'$project': {'price': '$items.price'}},
{'$match' : {'price': {'$gte': 100 }}}
])
{ "_id" : 0, "price" : 240 }
{ "_id" : 0, "price" : 320 }
$unwind
{'items': [{'item_id': 1}, {'item_id': 2}]}
after $unwind
{'items': {'item_id': 1}}
{'items': {'item_id': 2}}
$project
This can choose which field you want ( or just remove which field you don't want) and rename a field to what you want.
{'items': {'item_id': 1}}
after $project
{'renamefor__item_id': 1}
$match
Just see the previous link for more detail. My English is not very good:(

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!

Aggregating an array of values in MongoDB

I have an array of values I want to group by the id of, and get the sum of all the values. Currently I'm trying:
db.entries.aggregate(
[
{ $match: {"user": "John" } },
{ "$unwind": '$games'},
{ $group: {
"_id": "$games.id",
"score": { "$sum": "$games.score"} }},
])
The data looks like:
{
"user":"john",
"games":[
{
"id":123,
"score":123
}
]
},
{
"user":"john",
"games":[
{
"id":123,
"score":123
}
]
},
{
"user":"john",
"games":[
{
"id":256,
"score":256
}
]
}
In this example, for "John" I would like to get the total of all the unique game scores. I should get (123 + 256) in this example.
However, right now I'm getting the sum of individual games, not all of them as a total.
I have here:
> db.entries.find();
{ "_id" : ObjectId("56a9610cca390d7e14a54486"), "user" : "john", "games" : [ { "id" : 123, "score" : 123 } ] }
{ "_id" : ObjectId("56a9610cca390d7e14a54487"), "user" : "john", "games" : [ { "id" : 123, "score" : 123 } ] }
{ "_id" : ObjectId("56a9610cca390d7e14a54488"), "user" : "john", "games" : [ { "id" : 256, "score" : 256 } ] }
> db.entries.aggregate([ {$match: {user: "john"}}, {$unwind: "$games"}, {$group: {_id: "$games.id", score: {$sum: "$games.score"}}} ]);
{ "_id" : 256, "score" : 256 }
{ "_id" : 123, "score" : 246 }
What’s the problem?