I have a collection of documents such as:
{
_id: 5,
responses: [
{
staff: false,
timestamp: 1000
},
{
staff: true,
timestamp: 1500
}
]
}
I have a function (using $function
) used to apply some custom logic with the responses
array:
const diffs=[];
let current;
for (let i = 0; i < responses.length; i++) {
if (i+1>=responses.length) break;
if (!current && !responses[i].staff) current = responses[i];
if (!current) continue;
const next = responses[i+1];
if (!next.staff) continue;
diffs.push(next.timestamp - current.timestamp);
current = undefined;
}
return diffs;
which basically returns array of numbers like [500, 1000, 10]
etc. It can also potentially return an empty array ([]
).
I want to basically combine all arrays into one (say one document returns [5, 10]
and the next one returns [1, 2]
, the result would be [5, 10, 1, 2]
— order doesn’t matter) and then calculate the average using $avg
.
I was reading MongoDB docs and found $concatArrays
, so to my understanding the process should be:
- Calculate diffs for each document, which will end up with an array like
[[1, 2], [3, 4], [5, 6, 7], [], ...]
- Use
$concatArrays
on the value from step 1 - Use
$avg
on the array from step 2
How should I go about making step 1? The only part I’m not sure about is how to hold a variable in the first grouping stage with the result returned from $function
. I understand I need to do something like this:
aggregate([
{$group: {diffs: {$function: {...}}}}
])
However, I get the error MongoServerError: unknown group operator '$function'
.
2
Answers
I ended up getting it working the way I wanted. Sorry if there was some confusion over the data to anyone reading the original question -- the basic gist of the problem was, for each document the function calculates an array which are all numbers (like
[1, 2, 3]
), and I wanted to make one big array with all the numbers combined from all the documents, then calculate an average with it.I got it working by first calculating the array for each document (
diffs
), unwinding the array, using$push
to push each element of the array into a new one, and then finally using$avg
on the created array. Hopefully this helps someone with a similar problem down the line.Documentation says:
A starting point to achieve the same natively with an aggregation pipeline could be this one. I did not spend much time in testing, but this would be the direction.
https://mongoplayground.net/p/Ie6IL8atLX-
A different approach is to use
input: { $range: [ 0, {$subtract: [{ $size: "$responses" }, 1]} ] },
and then access the elements with{ $arrayElemAt: [ "$responses", "$$this" ] }
, maybe see this example which is a similar use-case.