0

Essentially, I have:

  • a bands collection
  • each band may or may not have an array of players
  • each player has an array of roles (string)

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.

3 Answers 3

2

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.

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

1 Comment

This runs much faster than the solution I posted, thank you!
1

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

3 Comments

I wouldn't double unwind whole collection. Add a copy of $match as a first stage to unwind only documents that has at least 1 singer.
I have assumed there is always at least one singer in the band :) , but indeed the filter solution seems to perform better ...
I really liked the output of this option as "{ _id: ..., cnt: ...}", though it seems to be too heavy for a larger collection: "MongoServerError: Exceeded memory limit for $group, but didn't allow external sort. Pass allowDiskUse:true to opt in."
1

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
        ]
      }
    }
  }
])

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.