Eduardo Sztokbant
Eduardo Sztokbant

Reputation: 792

MongoDB: determining if there are any objects with a specific count for a particular sub-array value

Essentially, I have:

E.g.:

// a band
{
  _id: 1,
  players: [
    { 
      name: "George",
      roles: [ "GUITAR" ]
    },
    { 
      name: "John",
      roles: [ "SINGER", "GUITAR" ]
    },
    { 
      name: "Paul",
      roles: [ "SINGER", "BASS GUITAR" ]
    },
    { 
      name: "Ringo",
      roles: [ "DRUMS" ]
    },
  ]
}

I need to determine if there is any band which contains more than one member having the SINGER role.

Upvotes: 0

Views: 54

Answers (3)

Eduardo Sztokbant
Eduardo Sztokbant

Reputation: 792

This query seems to give me what I need:

db.bands.aggregate([
  {
    $addFields: {
      players: {
        $ifNull: [
          "$players",
          []
        ]
      }
    }
  },
  {
    "$match": {
      "$expr": {
        "$gt": [
          {
            "$size": {
              "$filter": {
                "input": "$players",
                "as": "player",
                "cond": {
                  "$in": [
                    "SINGER",
                    "$$player.roles"
                  ]
                }
              }
            }
          },
          1
        ]
      }
    }
  }
])

Upvotes: 1

Alex Blex
Alex Blex

Reputation: 37108

An alternative to the $unwind / $group solution would be $filter:

db.collection.aggregate([
  {
    $match: {
      "players.roles": "GUITAR"
    }
  },
  {
    "$set": {
      "member_cnt": {
        $size: {
          $filter: {
            input: "$players",
            cond: {
              $in: [
                "GUITAR",
                "$$this.roles"
              ]
            }
          }
        }
      }
    }
  },
  {
    $match: {
      "member_cnt": {
        $gt: 1
      }
    }
  },
  {
    "$project": {
      member_cnt: 0
    }
  }
])

It should be a bit faster as it doesn't have blocking $group stage.

Upvotes: 2

R2D2
R2D2

Reputation: 10737

Some easy option:

db.collection.aggregate([
 {
   $unwind: "$players"
  },
 {
   $unwind: "$players.roles"
 },
 {
   $match: {
     "players.roles": "SINGER"
  }
  },
   {
   $group: {
    _id: "$_id",
    cnt: {
    $sum: 1
    }
   }
  },
  {
    $match: {
     cnt: {
      $gt: 1
      }
    }
  }
 ])

explained:

  1. unwind 1st array
  2. unwind 2nd array
  3. Filter only the roles SINGER
  4. group by band _id and count the SINGER roles.
  5. Filter only the bands with >1 SINGER

playground

Upvotes: 1

Related Questions