MongoDB aggregations. Get value differences - mongodb

I've been struggling with mongo trying to find a solution to show the differences between values.
I have values like this:
[
{val: 1},
{val: 4},
{val: 7},
{val: 8},
{val: 11}
]
And I want to receive something like this:
[
{diff: 3},
{diff: 3},
{diff: 1},
{diff: 3}
]
Every value is evaluated by taking the next one (4) and subtracting the previous one (1). After all this, we receive 3 in output, which is located in the second list as the first item.
Is it possible to achieve it using MongoDB aggregations?

You need to group them into array, calculate diff and flatten again.
Pseudocode
//We $group here all values
var _data = [{val: 1}, {val: 4}, ..., {val: 11}];
//With $range operator we get nÂș of items
// We ensure even items, since odd items will return null as last item
var idx = [0, 1, 2, ..., n];
//Here we store diff items with $map operator
var data = [];
//$map
for (var i in idx) {
data[i] = _data[i+1] - _data[i];
}
//$unwind
{data:[0]}, {data[1]}, {data[2]}, ...
//$replaceRoot
{
data:{ {
diff : 3 --> diff : 3
} }
}
Add these steps into your pipeline:
db.collection.aggregate([
{
$group: {
_id: null,
data: { $push: "$$ROOT" }
}
},
{
$addFields: {
data: {
$map: {
input: {
$range: [
0,
{
$subtract: [
{ $size: "$data" },
{ $mod: [ { $size: "$data" }, 2 ] }
]
},
1
]
},
as: "idx",
in: {
diff: {
$subtract: [
{
$arrayElemAt: [
"$data.val",
{
$add: [ "$$idx", 1 ]
}
]
},
{
$arrayElemAt: [ "$data.val", "$$idx" ]
}
]
}
}
}
}
}
},
{
$unwind: "$data"
},
{
$replaceRoot: {
newRoot: "$data"
}
}
])
MongoPlayground

Related

How can I exclude results that contain a specific element from grouped results?

A: It should be output how many _ids are included by date grouped by date.
B: The number of elements in details in A.
If it has element, count 1. not 0. If the document is as follows, the value counted after excluding from A becomes B
{
_id: ObjectId
details: array //no elements
createdAt: Date
}
C: The count of B becomes C, except when there are specific details.slaesManagerIds among B.
details.salesManagerIds is provided as an array.
For examples,
[ObjecttId("612f57184205db63a3396a9e"), ObjectId("612cb021278f621a222087d7")]
I made query as follows.
https://mongoplayground.net/p/6sBxAmO_31y
It goes well until B. How can I write a query to get C ?
If you write and execute a query that can obtain C through the link above, you should get the following result.
[
{
"A": 2,
"B": 1,
"C": 1,
"_id": "2018-05-19"
},
{
"A": 3,
"B": 3,
"C": 1,
"_id": "2018-05-18"
}
]
use $filter
db.collection.aggregate([
{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$createdAt"
}
},
A: {
$sum: 1
},
B: {
$sum: {
$cond: [
{
$and: [
{
$isArray: "$details"
},
{
$gt: [
{
$size: "$details"
},
0
]
}
]
},
1,
0
]
}
},
C: {
$sum: {
$cond: [
{
$and: [
{
$isArray: "$details"
},
{
$gt: [
{
$size: "$details"
},
0
]
},
{
$gt: [
{
$size: {
$filter: {
input: "$details",
as: "d",
cond: {
$and: [
{
$not: [
{
$in: [
"$$d.salesManagerId",
[
ObjectId("612f57184205db63a3396a9e"),
ObjectId("612cb021278f621a222087d7")
]
]
}
]
}
]
}
}
}
},
0
]
}
]
},
1,
0
]
}
}
}
},
{
$sort: {
_id: -1
}
}
])
mongoplayground

mongodb average arrays across many documents

Using mongodb, I have a collection of documents where each document has a fixed length vector of floating point values such as below:
items = [
{"id": "1", "vec": [1, 2, 0]},
{"id": "2", "vec": [6, 4, 1]},
{"id": "3", "vec": [3, 2, 2]},
]
I would like to take the row wise average of these vectors. In this example I would expect the result to return
[ (1 + 6 + 3) / 3, (2 + 4 + 2) / 3, (0 + 1 + 2) / 3 ]
This answer is very close to what I am looking for, but as far as I can tell it will only work on vectors of size 2. mongoDB - average on array values
An answer has been provided that is not very performant for large arrays. For context I am using ~700 dimension vectors.
This should work: https://mongoplayground.net/p/PKXqmmW31nW
[
{
$group: {
_id: null,
a: {
$push: {
$arrayElemAt: ["$vec", 0]
}
},
b: {
$push: {
$arrayElemAt: ["$vec", 1]
}
},
c: {
$push: {
$arrayElemAt: ["$vec", 2]
}
}
}
},
{
$project: {
a: {
$avg: "$a"
},
b: {
$avg: "$b"
},
c: {
$avg: "$c"
}
}
}
]
Which outputs:
[
{
"_id": null,
"a": 3.3333333333333335,
"b": 2.6666666666666665,
"c": 1
}
]
Here's a more efficient without $avg operator. I'll leave other answer up for reference.
https://mongoplayground.net/p/rVERc8YjKZv
db.collection.aggregate([
{
$group: {
_id: null,
a: {
$sum: {
$arrayElemAt: ["$vec", 0]
}
},
b: {
$sum: {
$arrayElemAt: ["$vec", 1]
}
},
c: {
$sum: {
$arrayElemAt: ["$vec", 2]
}
},
totalDocuments: {
$sum: 1
}
}
},
{
$project: {
a: {
$divide: ["$a", "$totalDocuments"]
},
b: {
$divide: ["$b", "$totalDocuments"]
},
c: {
$divide: ["$c", "$totalDocuments"]
}
}
}
])
You can use $unwind to get values into separate documents, the key is to keep the index of the values. Then you can use $group by the index and calculate the average using the $avg operator.
db.collection.aggregate([
{
$unwind: {
path: "$vec",
includeArrayIndex: "i" // unwind and keep index
}
},
{
$group: {
_id: "$i", // group by index
avg: { $avg: "$vec" }
}
}, // at this stage, you already get all the values you need, in separate documents. The following stages will put all the values in an array
{
$sort: { _id: 1 }
},
{
$group: {
_id: null,
avg: { $push: "$avg" }
}
}
])
Mongo Playground

How to group mongo result for specific keys?

I've a collection that has this documents:
{name: "x", code: 1},
{name: "x", code: 2},
{name: "x", code: 3},
{name: "x", code: 1}
How can i group and count the keys that are equal to 1 and what is not equal to 1?
I manage to group by the code but the result comes separately for 1, 2 and 3
aggregate([
{'$match': {'name': "x"}},
{'$group': {'_id': '$code', 'total': {'$sum': 1}}},
{'$sort': {'_id': 1}}
])
You can conditionally sum the counter.
aggregate([
{
$group: {
_id: null,
key1: { // Count of all documents with keys(code) that are equal to 1
'$sum': {
'$cond': {
if: { '$eq': [1, '$code'] },
then: 1,
else: 0
}
}
},
otherKeys: { // Count of all other documents with keys not equal to 1
'$sum': {
'$cond': {
if: { '$eq': [1, '$code'] },
then: 0,
else: 1
}
}
}
},
}
])
This would output a result document like this:
{ "_id" : null, "key1" : *, "otherKeys" : * }
Where key1 is the count of all documents with keys equal to 1, while otherKeys is the count of all documents with keys not equal to 1.
Aggregation with $facet allows you to query on same set of documents in multiple facets. You can the desired result with this query:
db.codes.aggregate( [
{ $project: { code: { $eq: [ '$code', 1 ] } } },
{ $facet: {
code_is_1: [
{ $match: { code: true } },
{ $count: "Code equals 1" },
],
code_not_1: [
{ $match: { code: false } },
{ $count: "Code not equals 1" },
],
} },
{ $project: { Counts: { $concatArrays: [ "$code_is_1", "$code_not_1" ] } } },
] )
The result will look like this with the test documents you posted: { "Counts" : [ { "Code equals 1" : 2 }, { "Code not equals 1" : 2 } ] }

Averaging across list indicies during aggregation pipeline?

I currently have a MongoDB aggregation pipeline that ends with the following type of synthetic documents
[
{
'_id': '2019-09-10',
'grouped_foos':
[
{... 'foo': [1, 78, 100]},
{... 'foo': [8, 66, 98]},
{... 'foo': [99, 5, 33]},
{... 'foo': [120, 32, 2]}
]
},
{
'_id': '2019-09-09',
'grouped_foos':
[
{... 'foo': [10, 27]},
{... 'foo': [19, 66]}
]
},
{
'_id': '2019-09-08',
'grouped_foos':
[
{... 'foo': [1]}
]
}
]
I would like to continue this pipeline and average the indices of the foo lists together to form documents that look like
[
{
'_id': '2019-09-10',
'avg_foo': [57, 45.25, 58.25]
},
{
'_id': '2019-09-09',
'avg_foo': [14.5, 46.5]
},
{
'_id': '2019-09-08',
'avg_foo': [1]
}
]
Is this type of averaging possible during aggregation? Do I potentially need to $unwind the lists with indexing and assign new _id for uniqueness to make documents that look like
[
{
'_id': UUID,
'old_id': '2019-09-10',
'foo': 1,
'index': 0
},
{
'_id': UUID,
'old_id': '2019-09-10',
'foo': 78,
'index': 1
},
........
]
Basically you can try with $unwind but easier and faster approach would be to use $reduce to $map and $sum all the rows from grouped_foos. Then you'll be able to run another $map and use $divide to get the average.
db.collection.aggregate([
{
$project: {
size: { $size: "$grouped_foos" },
foo_sum: {
$reduce: {
input: "$grouped_foos",
initialValue: [],
in: {
$map: {
input: { $range: [ 0, { $size: "$$this.foo" }, 1 ] },
as: "index",
in: {
$add: [
{ $arrayElemAt: [ "$$this.foo", "$$index" ] },
{ $ifNull: [ { $arrayElemAt: [ "$$value", "$$index" ] }, 0 ] }
]
}
}
}
}
}
}
},
{
$project: {
_id: 1,
avg_foo: {
$map: {
input: "$foo_sum",
in: {
$divide: [ "$$this", "$size" ]
}
}
}
}
}
])
Mongo Playground

Mongodb aggregation - count arrays with elements having integer value greater than

I need to write a MongoDB aggregation pipeline to count the objects having arrays containing two type of values:
>=10
>=20
This is my dataset:
[
{ values: [ 1, 2, 3] },
{ values: [12, 1, 3] },
{ values: [1, 21, 3] },
{ values: [1, 2, 29] },
{ values: [22, 9, 2] }
]
This would be the expected output
{
has10s: 4,
has20s: 3
}
Mongo's $in (aggregation) seems to be the tool for the job, except I can't get it to work.
This is my (non working) pipeline:
db.mytable.aggregate([
{
$project: {
"has10s" : {
"$in": [ { "$gte" : [10, "$$CURRENT"]}, "$values"]}
},
"has20s" : {
"$in": [ { "$gte" : [20, "$$CURRENT"]}, "$values"]}
}
},
{ $group: { ... sum ... } }
])
The output of $in seems to be always true. Can anyone help?
You can try something like this:
db.collection.aggregate([{
$project: {
_id: 0,
has10: {
$size: {
$filter: {
input: "$values",
as: "item",
cond: { $gte: [ "$$item", 10 ] }
}
}
},
has20: {
$size: {
$filter: {
input: "$values",
as: "item",
cond: { $gte: [ "$$item", 20 ] }
}
}
}
}
},
{
$group: {
_id: 1,
has10: { $sum: "$has10" },
has20: { $sum: "$has20" }
}
}
])
Using $project with $filter to get the actual elements and then via $size to get the array length.
See it working here