Search code examples
node.jsmongodbsortingmongooseaggregate

Sort with Mongoose by the number of respected conditions


I want to make a search function with mongoose, and I have to be able to make a research with multiple fields (with Mongoose, in NodeJS).

So, I do something like this :

const result = await myModel.find({
   $or [{condition1: "value1"}, {condition2: "value2"}, etc...]
});

But, I want to sort the result by the number of condition the object returned have. Like :

If I have 2 conditions, I want to display first the objects respecting the 2 conditions, then the objects respecting the 1st condition, and finally the objects respecting the 2nd condition.

Do you guys know how I can do this? :)

Thanks in advance !

================EDIT================

This is the new search function :

  /**
   * Search function which returns users matching jobs and skills.
   *
   * @param {Array[String]} jobs
   * @param {Array[String]} skills
   * @return {Array[UserModel]} users
   */
  async search(jobs, skills) {
    // Normalized arrays of jobs, skills and fields (to use it in a mongoose request).
    const jobSkills = [];
    const associatedSkills = [];
    const fields = [];

    for (const job of jobs) {
      jobSkills.push({
        $cond: [
          {
            $eq: ["$jobSkills", job],
          },
          2,
          0,
        ],
      });
      fields.push({
        jobSkills: job,
      });
    }

    for (const skill of skills) {
      associatedSkills.push({
        $cond: [
          {
            $eq: ["$associatedSkills", skill],
          },
          1,
          0,
        ],
      });
      fields.push({
        associatedSkills: skill,
      });
    }

    // Request to find users matching jobs and skills.
    const users = await UserModel.aggregate([
      {
        $match: {
          $or: fields,
        },
      },
      {
        $addFields: {
          sortField: {
            $sum: jobSkills.concat(associatedSkills),
          },
        },
      },
      {
        $sort: {
          sortField: -1,
        },
      },
    ]);

    return users;
  }

Aggregation Log :

Aggregate {
  _pipeline: [
    { '$match': [Object] },
    { '$addFields': [Object] },
    { '$sort': [Object] }
  ],
  _model: Model { User },
  options: {}
}

Solution

  • In general, a document either matches a query predicate or it doesn't. There isn't really a concept of one document matching "better" than another. So it looks like you'll want to generate a custom value in a new field and sort on that. This will need to be done via an aggregation.

    So after the $match, we'll want an $addFields stage that effectively duplicates the query predicates. For each one it will be wrapped in a conditional statement ($cond) where we add 1 for a match or 0 otherwise, e.g.:

              {
                $cond: [
                  {
                    $eq: [
                      "$condition1",
                      "value1"
                    ]
                  },
                  1,
                  0
                ]
              }
    

    Then there will be a $sum pulling them together to generate the final score to sort on.

    Taken together, the aggregation will look something like this:

    db.collection.aggregate([
      {
        $match: {
          $or: [
            {
              condition1: "value1"
            },
            {
              condition2: "value2"
            }
          ]
        }
      },
      {
        $addFields: {
          sortField: {
            "$sum": [
              {
                $cond: [
                  {
                    $eq: [
                      "$condition1",
                      "value1"
                    ]
                  },
                  1,
                  0
                ]
              },
              {
                $cond: [
                  {
                    $eq: [
                      "$condition2",
                      "value2"
                    ]
                  },
                  1,
                  0
                ]
              }
            ]
          }
        }
      },
      {
        $sort: {
          "sortField": -1
        }
      }
    ])
    

    Playground demonstration here