Home > Enterprise >  MongoDB conditional $sum after using $addFields
MongoDB conditional $sum after using $addFields

Time:02-23

I am using an aggregation pipeline to aggregate stats for my game. My pipeline consists of first filtering all the games in the collection by the player's ObjectID, then summing their stats and analytics. The filtering is done by checking each array in a game's players array. The players array is an array of objects, and I check the uuid field on each object, to see if it corresponds with my target ObjectID.

Aggregating the stats works fine for simple $sum, but I am now attempting to do a more advanced sum. I want to get the average opponent rating. Each player has a team field of either 1 or 2, representing the possible teams. If the player's team is 1, I need to fetch team 2's rating, if their team is 2, I need to get team 1's rating. I designate team 1 as blue team, and team 2 and red team in my schema for simplicity. Here is an example game

{
    "type": "Regular",
    "map": "Classic",
    "winningTeam": 1,
    "gameStats": {
      "duration": 7,
      "redScore": 1,
      "blueScore": 0,
      "redRating": 1000,
      "blueRating": 1000,
},
   "players": [
      {
        "uuid": "ObjectId",
        ...
        "stats": {
          "timePlayed": 7,
          "goals": 0,
          "ownGoals": 0,
          "goalsFor": 1,
          "goalsAgainst": 0,
        },
}

And here is my pipeline

[
  {
    $addFields: {
      players: {
        $filter: {
          input: "$players",
          as: "player",
          cond: {
            $eq: [
              "$$player.uuid",
              playerObjectId
            ],
            
          },
          
        },
        
      },
      
    },
    
  },
  {
    $group: {
      _id: playerObjectId,
      oppRating: {
        $avg: {
          $avg: {
            $switch: {
              branches: [
                {
                  case: {
                    $eq: [
                      "$players.team",
                      1
                    ]
                  },
                  then: "$gameStats.blueRating"
                },
                {
                  case: {
                    $eq: [
                      "$players.team",
                      2
                    ]
                  },
                  then: "$gameStats.redRating"
                },
                
              ]
            }
          }
        }
      },
      timePlayed: {
        $sum: {
          $sum: "$players.stats.timePlayed",
          
        },  
      },      
      },
      goals: {
        $sum: {
          $sum: "$players.stats.goals",
          
        },
...
]

Now my $switch doesn't work, and I've identified the problem to be the fact that I cant access the $players field for some reason. For example when I set the condition to

$eq: [
1,
1
],

It will work, and correctly get the average. I see my issue is being able to access the $players variable that I set up in my addfields, why cant I access this variable in the $switch statement, but I can access it in all my other fields, like the $sum for timeplayed. Do I need to rethink my filter query? I understand that I could simply add a field to every playerObject that reads "opponentRating", but I would like to see if there is simply an aggregation way to do this first.

CodePudding user response:

players must be an object to considered inside the $switch block. Just need to add $unwind after the addFields, since $filter will return an array.

db.game.aggregate([
    {
        $addFields: {
            players: {
                $filter: {
                    input: "$players",
                    as: "player",
                    cond: {
                        $eq: [
                            "$$player.uuid",
                            playerObjectId
                        ],

                    },

                },

            },
        },
    },
    {
        $unwind: '$players'
    },
    {
        $group: {
            _id: playerObjectId,
            oppRating: {
                $avg: {
                    $avg: {
                        $switch: {
                            branches: [
                                {
                                    case: {
                                        $eq: [
                                            "$players.team",
                                            1
                                        ]
                                    },
                                    then: "$gameStats.blueRating"
                                },
                                {
                                    case: {
                                        $eq: [
                                            "$players.team",
                                            2
                                        ]
                                    },
                                    then: "$gameStats.redRating"
                                },

                            ]
                        }
                    }
                }
            },
            timePlayed: {
                $sum: {
                    $sum: "$players.stats.timePlayed",
                },
            },
            goals: {
                $sum: {
                    $sum: "$players.stats.goals",
                }
            }
        }
    }
])

Also, I thought some performance optimisations can be done & redundant functions could be removed on the pipeline such as

  • Instead of $filter for players, we can use $match, $unwind & $match
  • one $avg will suffice for oppRating
  • And, one $sum will suffice for timePlayed & goals

You can try the below pipeline

db.game.aggregate([
    {
        $match: {
            'players.uuid': playerObjectId,
        }
    },
    {
        $unwind: '$players'
    },
    {
        $match: {
            'players.uuid': playerObjectId,
        }
    },
    {
        $group: {
            _id: playerObjectId,
            oppRating: {
                $avg: {
                    $switch: {
                        branches: [
                            {
                                case: {
                                    $eq: [
                                        "$players.team",
                                        1
                                    ]
                                },
                                then: "$gameStats.blueRating"
                            },
                            {
                                case: {
                                    $eq: [
                                        "$players.team",
                                        2
                                    ]
                                },
                                then: "$gameStats.redRating"
                            },

                        ]
                    }
                }
            },
            timePlayed: {
                $sum: "$players.stats.timePlayed",
            },
            goals: {
                $sum: "$players.stats.goals",
            }
        }
    }
])
  • Related