Search code examples
arraysmongodbmongodb-queryaggregate

How do I make sure that an array field always comes out even if its empty in MongoDB?


This is an example of a document I have in my teams collection:

_id: ObjectId('...')
name: Team A
players: [
    {
        name: Michael
        position: Goalkeeper
    },
    {
        name: Frank
        position: Defender
    }
]

In my application I need to get out the list of players byt filtered on one position. So for example to get Goalkeepers I say:

{
    '$match': {
        '_id': ObjectId('...'),
        'name': 'Team A'
    }
}, {
    '$unwind': {
        'path': '$players'
    }
}, {
    '$match': {
        'players.position': 'Goalkeeper'
    }
}, {
    '$group': {
        '_id': null,
        'players': {
            '$addToSet': '$players'
        }
    }
}, {
    '$project': {
        '_id': 0
    }
}

So this aggregation ends up returning the following:

{
    players: [
        {
            name: Michael
            position: Goalkeeper
        }
    ]
}

Which is perfect, exactly what I want. However if I want to get a list of Strikers instead, from the same document, I simply get an empty array back like this:

[]

How do I make it return the following, when - and only when - there is no position match:

{
    players: []
}

Solution

  • If you want to match only one team, simply use $filter:

    db.collection.aggregate([
      {$match: {_id: ObjectId("63920f965d15e98e3d7c450c")}},
      {$project: {
          players: {
            $filter: {
              input: "$players",
              cond: {$eq: ["$$this.position", "Striker"]}
            }
          },
          _id: 0
      }}
    ])
    

    See how it works on the playground example

    But if you want to match more than one group, you can use $reduce to flatten the array:

    db.collection.aggregate([
      {$project: {
          players: {$filter: {
              input: "$players",
              cond: {$eq: ["$$this.position", "Striker"]}
          }},
          _id: 0
      }},
      {$group: {
          _id: 0, players: {$push: "$players"}
      }},
      {$project: {
          _id: 0,
          players: {
            $reduce: {
              input: "$players",
              initialValue: [],
              in: {$concatArrays: ["$$value", "$$this"]}
            }
          }
      }}
    ])
    

    See how it works on the playground example