2

Lets say I have document like below

{
"_id" : 1,
"Quality" : [ 
        "HIGH", 
        "LOW", 
        "LOW", 
        "HIGH"
    ],
    "Pages" : [ 
        10, 
        10, 
        12, 
        17
    ]
}

I need result as

{
"_id" : 1,
"HIGH" : 27
"LOW" : 22
}

I need to create two attributes HIGH and LOW as per the Quality by summing their corresponding index position values of Pages array.

I though of using $filter and $arrayElemAt, but i could not get index position while filtering on Pages attribute

high : { $sum:{ $filter: { input: "$Pages", as: "noOfPages", cond: {"$eq":[{ $arrayElemAt: ["$Quality", **Need to pass index position of $pages while filtering**]}, "HIGH"]}}}}

Thanks In Advance.

2 Answers 2

4

You can use below aggregation

db.collection.aggregate([
  { "$project": {
    "Quality": {
      "$map": {
        "input": { "$range": [0, { "$size": "$Quality" }] },
        "in": {
          "Quality": { "$arrayElemAt": ["$Quality", "$$this"] },
          "Pages": { "$arrayElemAt": ["$Pages", "$$this"] }
        }
      }
    }
  }},
  { "$project": {
    "newArrayField": {
      "$map": {
        "input": { "$setUnion": ["$Quality.Quality"] },
        "as": "m",
        "in": {
          "k": "$$m",
          "v": {
            "$filter": {
              "input": "$Quality",
              "as": "d",
              "cond": {
                "$eq": ["$$d.Quality", "$$m"]
              }
            }
          }
        }
      }
    }
  }},
  { "$project": {
    "d": {
      "$arrayToObject": {
        "$map": {
          "input": "$newArrayField",
          "in": {
            "k": "$$this.k",
            "v": { "$sum": "$$this.v.Pages" }
          }
        }
      }
    }
  }}
])

MongoPlayground

Sign up to request clarification or add additional context in comments.

1 Comment

Is there a way to use $subtract in combination with $$this to get for example the previous item from the array?
0

Can achieve this with 2 aggregation stages.

First is to map the 2 arrays into a single array.

Second is filtering the single array to include only HIGH(/LOW), and reducing the filtered array into a single sum element.

[{$project: {
  "Quality": {
    $map: {
      input: {$range: [0, {$size: "$Quality"}]},
      as: "idx",
      in: {
        "Quality": { $arrayElemAt: ["$Quality", "$$idx"] },
        "Pages": { $arrayElemAt: ["$Pages", "$$idx"] }
      }
    }
  }
}}, {$project: {
  "HIGH": {
    $reduce: {
      input: {
        $filter: {
          input: "$Quality",
          as: "x",
          cond: {$eq: ["$$x.Quality", "HIGH"]}
        }
      },
      initialValue: 0,
      in: {
        $add: ["$$value", "$$this.Pages"]
      }
    }
  },
  "LOW": {
    $reduce: {
      input: {
        $filter: {
          input: "$Quality",
          as: "x",
          cond: {$eq: ["$$x.Quality", "LOW"]}
        }
      },
      initialValue: 0,
      in: {
        $add: ["$$value", "$$this.Pages"]
      }
    }
  }
}}]

Which gives exactly what you've asked for:

{
  _id: 1
  HIGH:27
  LOW:22
}

1 Comment

I can even do that with single aggregation stage but to be better in read I have prefered to be in multiple stages. And the issue with your answer is, You have put "HIGH" and "LOW" hardcoded.

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.