skip to Main Content

I’m trying to optimize my code that I’m currently running on a node server.
The part of the code that I’m trying to optimize is code related with filtering something called "content" part of it’s schema is

const contentSchema = new Schema({
title: String , 
skills :[
{
skillId:ObjectId , 
skillLevel:Int
}]
})

I’m trying to filter it by the difficulty. each content’s difficulty is based on the skill with the highest level. so if the skill with the highest level is 5 then the content difficulty is 5.
How I’m currently doing it.

const content = await Content.find({
...someOtherFilters
}).lean()
content = content.map((content)=> {
const difficulty = content.skills.reduce((acc , curr)=> {
if (!acc || curr.skillLevel > acc) return curr.skillLevel
return acc 
} , 0)
return {
...content, 
difficulty
}
}).filter(({difficulty})=> {
return difficulty === desiredDifficulty
})

How can I convert this into some mongodb query that I can then use like this

const contentCount = await Cotnent.countDocuments(contentQuery)

3

Answers


  1. Mapping and filtering can be done using aggregate.

    By using $max you can find which element in the skills array has the highest skillLevel and use $arrayElemAt to pluck it from the array. You can then use $set to set the difficulty to the value of that skillLevel.

    With this new difficulty property you can use $match to find documents that match your desiredDifficulty criteria.

    Something like this I think might help:

    const content = await Content.aggregate([
      {
        $addFields: {
          difficulty: {
            $arrayElemAt: [
              "$skills",
              {
                $indexOfArray: [
                  "$skills.skillLevel",
                  {
                    $max: "$skills.skillLevel"
                  }
                ]
              }
            ]
          }
        }
      },
      {
        $set: {
          "difficulty": "$difficulty.skillLevel"
        }
      },
      {
        $match: {
          difficulty: desiredDifficulty
        }
      }
    ])
    

    See HERE for a working example.

    Login or Signup to reply.
  2. The answer by @jQuenny is correct and can be simplified to :

    db.collection.aggregate([
      {$match: {$expr: {$eq: [{$max: "$skills.skillLevel"}, desiredDifficulty]}}}
    ])
    

    See how it works on the playground example

    Login or Signup to reply.
  3. Instead of calculating the maximum difficulty on the fly, it would be best to store it as a property on the document, e.g. maxDifficulty. It should be updated whenever a new skill is added to the document. You will have very simple queries afterwards and you can support them by creating an index on the new property, e.g.:

    {
      maxDifficulty: 4
    } 
    

    For updating existing documents, you can use an aggregation pipeline similar to the following (mongoplayground – thank you @jQueeny for the documents in your playground):

    [
      {
        $set: {
          maxDifficulty: {
            $first: {
              $sortArray: {
                input: "$skills.skillLevel",
                sortBy: -1
              }
            }
          }
        }
      },
      {
        $merge: {
          // Define merge as required
        }
      } 
    ]
    

    Please note that documents that do not contain any skills will not receive a maxDifficulty property. You can add an $ifNull operator when setting the field to set the maxDifficulty to 0 if needed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search