I have a collection of phrases
with the following structure:
- phrase – string
- phraseLength (the length of the phrase string)
- author – string
I would like to group phrases by author. And for each author, the maximum length of all that author’s phrases shouldn’t exceed 200 characters.
In other words, I am trying to figure out how to create an aggregate query that will:
- loop through each phrase
- If current author’s total running phrases length is less than 200 characters, then push it to the author’s phrases array
So for example with the following documents (I added incorrect phrase length for each document for ease of understanding):
[
{
phrase: 'This is phrase 1 of author 1',
phraseLength: 50,
author: 'Author 1',
},
{
phrase: 'This is phrase 1 of author 1',
phraseLength: 150,
author: 'Author 1',
},
{
phrase: 'This is phrase 1 of author 1',
phraseLength: 10,
author: 'Author 1',
},
{
phrase: 'This is phrase 1 of author 2',
phraseLength: 20,
author: 'Author 2',
},
{
phrase: 'This is phrase 2 of author 2',
phraseLength: 180,
author: 'Author 2',
},
{
phrase: 'This is phrase 3 of author 2',
phraseLength: 50,
author: 'Author 2',
},
]
This is the desired output:
[
{
_id: 'Author 1',
phrases: [
{
phrase: 'This is phrase 1 of author 1',
phraseLength: 50,
author: 'Author 1',
},
{
phrase: 'This is phrase 1 of author 1',
phraseLength: 150,
author: 'Author 1',
},
]
},
{
_id: 'Author 2',
phrases: [
{
phrase: 'This is phrase 1 of author 2',
phraseLength: 20,
author: 'Author 2',
},
{
phrase: 'This is phrase 2 of author 2',
phraseLength: 180,
author: 'Author 2',
},
]
},
]
I know how to group all phrases by author like this:
db.phrases.aggregate([
{
$group: {
_id: '$author',
phrases: {
$push: { phrase: '$phrase', phraseLength: '$phraseLength', author: '$author' }
}
}
},
])
And I have tried the following query to accomplish the desired outcome but it didn’t work.
db.phrases.aggregate([
{
$group: {
_id: "$author",
runningTotalPhraseLength: { $sum: '$phraseLength' },
$cond: {
if: {
$lte: [{ $size: '$runningTotalPhraseLength' }, 200]
},
then: {
phrases: {
$push: { phrase: '$phrase', phraseLength: '$phraseLength', author: '$author' }
}
},
else: {},
},
}
},
])
Any help is very much appreciated! 🙂
2
Answers
I think it is not possible to define the conditional accumulate push logic in the
$group
stage.But you need another
$set
stage with$reduce
operator.$group
$set
– Create atemp
field.2.1.
$reduce
– Iterate each element inphrases
array and return a new object.2.1.1.
input
–phrases
array.2.1.2.
initialValue
– Initialize an object withlength
andphrases
fields.2.1.3.
in
–2.1.3.1.
$cond
– Compare if the accumulatedvalue.length
plus thelength
for current iterate object is lesser or equal to 200.If true, then set the object with
length
:$$value.length + $$this.length
andphrases
with add the iterated phrase object to the accumulated$$value.phrases
.Else, remain the same value for the accumulated
$$value
.$set
– Replacephrases
array withtemp.phrases
.$unset
– Removetemp
field.Demo @ Mongo Playground
Using $setWindowFields to compute cumulative length and then conditional grouping,
Demo