5

I want to group objects in the array by same value for specified field and produce a count.

I have the following mongodb document (non-relevant fields are not present).

{
  arrayField: [ 
    { fieldA: value1, ...otherFields }, 
    { fieldA: value2, ...otherFields },
    { fieldA: value2, ...otherFields } 
  ],
  ...otherFields
}

The following is what I want.

{
  arrayField: [ 
    { fieldA: value1, ...otherFields }, 
    { fieldA: value2, ...otherFields },
    { fieldA: value2, ...otherFields } 
  ],
  newArrayField: [ 
    { fieldA: value1, count: 1 }, 
    { fieldA: value2, count: 2 },
  ],
  ...otherFields
}

Here I grouped embedded documents by fieldA.

I know how to do it with unwind and 2 group stages the following way. (irrelevant stages are ommited)

Concrete example

// document structure
{
  _id: ObjectId(...),
  type: "test",
  results: [ 
    { choice: "a" }, 
    { choice: "b" },
    { choice: "a" } 
  ]
}
db.test.aggregate([
{ $match: {} },
{
  $unwind: {
    path: "$results",
    preserveNullAndEmptyArrays: true
  }
},
{
  $group: {
    _id: {
      _id: "$_id",
      type: "$type",
      choice: "$results.choice",
    },
    count: { $sum: 1 }
  }
},
{
  $group: {
    _id: {
      _id: "$_id._id",
      type: "$_id.type",
      result: "$results.choice",
    },
    groupedResults: { $push: { count: "$count", choice: "$_id.choice" } }
  }
}
])

2 Answers 2

2

You can use below aggregation

db.test.aggregate([
  { "$addFields": {
    "newArrayField": {
      "$map": {
        "input": { "$setUnion": ["$arrayField.fieldA"] },
        "as": "m",
        "in": {
          "fieldA": "$$m",
          "count": {
            "$size": {
              "$filter": {
                "input": "$arrayField",
                "as": "d",
                "cond": { "$eq": ["$$d.fieldA", "$$m"] }
              }
            }
          }
        }
      }
    }
  }}
])
Sign up to request clarification or add additional context in comments.

Comments

1

The below adds a new array field, which is generated by:

  1. Using $setUnion to get unique set of array items, with inner $map to extract only the choice field
  2. Using $map on the unique set of items, with inner $reduce on the original array, to sum all items where choice matches

Pipeline:

db.test.aggregate([{
  $addFields: {
    newArrayField: {
      $map: {
        input: {
          $setUnion: [{
              $map: {
                input: "$results",
                in: { choice: "$$this.choice" }
              }
            }
          ]
        },
        as: "i",
        in: {
          choice: '$$i.choice',
          count: {
            $reduce: {
              input: "$results",
              initialValue: 0,
              in: { 
                $sum: ["$$value", { $cond: [ { $eq: [ "$$this.choice", "$$i.choice" ] }, 1, 0 ] }]
              }
            }
          }
        }
      }
    }
  }
}])

The $reduce will iterate over the results array n times, where n is the number of unique values of choice, so the performance will depend on that.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.