3

I would like to find a single document matching the courseID but inside the document only objects from the materials array whose moduleNo matches the one I give. But the query I currently use seems to return the correct document but also returns all the objects in materials array. Any help would be appreciated.

My schema,

const materialSchema = new mongoose.Schema({
  courseID: String,
  materials: [
    {
      moduleNo: Number,
      moduleMaterial: String,
    },
  ],
});

My code/query,

 exports.getMaterials = (req, res) => {
  const { courseID, moduleNo } = req.query;
  Material.findOne(
    { courseID, "materials.moduleNo": moduleNo },
    (err, result) => {
      if (err) {
        console.error(err);
      } else {
        res.json(result);
      }
    }
  );
};

2 Answers 2

3

Way 1 : Use $elemMatch operator in project field

The $elemMatch operator limits the contents of an array field from the query results to contain only the first element matching the $elemMatch condition

Result : Returns only one matching array element

syntax :

db.collection.find(query,projection)

db.collection.find({
     "field": field_value                  
  },{
    "array_name":{
       $elemMatch:{"key_name": value }
    },
    field:1,
    field_2:1,
    field_3:0
})

https://mongoplayground.net/p/HKT1Gop32Pq

Way 2 : Array Field Limitations array.$ in project field *

Result : Returns only one matching array element

db.collection.find({
     "field": field_value,
     "array_name.key_name": value       
  },{
        "array_name.$":1,
        field:1,
        field_2:1,
        field_3:0
 });

https://mongoplayground.net/p/Db0azCakQg9

Update : Using MongoDB Aggregation

Result : Returns multiple matching array elements

db.collection.aggregate([
  {
    "$unwind": "$materials"
  },
  {
    "$match": {
      "courseID": "Demo",
      "materials.moduleNo": 1
    }
  }
]) 

will return output as :

[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "courseID": "Demo",
    "materials": {
      "moduleMaterial": "A",
      "moduleNo": 1
    }
  },
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "courseID": "Demo",
    "materials": {
      "moduleMaterial": "B",
      "moduleNo": 1
    }
  }
]

And If you want to format output :

db.collection.aggregate([
  {
    "$unwind": "$materials"
  },
  {
    "$match": {
      "courseID": "Demo",
      "materials.moduleNo": 1
    }
  },
  {
    "$group": {
      "_id": {
        "courseID": "$courseID",
        "moduleNo": "$materials.moduleNo"
      },
      "materials": {
        "$push": "$materials.moduleMaterial"
      }
    }
  },
  {
    "$project": {
      "_id": 0,
      "courseID": "$_id.courseID",
      "moduleNo": "$_id.moduleNo",
      "materials": "$materials"
    }
  }
])

will return result as :

[
  {
    "courseID": "Demo",
    "materials": [
      "A",
      "B"
    ],
    "moduleNo": 1
  }
]

https://mongoplayground.net/p/vdPVbce6WkX

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

2 Comments

Thanks for this but there might be more than one materials object with same moduleNo, but this only returns one object back. Any solution for that?
@JonanMathewGeorge , Thanks for update , please check updated answer.
1

Use the $elemMatch operator, something lik this:

exports.getMaterials = (req, res) => {
  const { courseID, moduleNo } = req.query;
  Material.findOne(
    { courseID },
    {"materials": { $elemMatch: {moduleNo: moduleNo}},
    (err, result) => {
      if (err) {
        console.error(err);
      } else {
        res.json(result);
      }
    }
  );
};

Update: To return all matching elements in the array, you will have to use an aggregation pipeline, having $filter stage, to filter out array elements. Like this:

exports.getMaterials = (req, res) => {
  const { courseID, moduleNo } = req.query;
  Material.aggregate(
    [
  {
    $match: {
      courseID: courseID
    }
  },
  {
    "$project": {
      courseID: 1,
      materials: {
        "$filter": {
          "input": "$materials",
          "as": "material",
          "cond": {
            "$eq": [
              "$$material.moduleNo",
              moduleNo
            ]
          }
        }
      }
    }
  }
]
  );
};

Here's the playground link.

1 Comment

This works but returns only one object but there might be more than one object with same moduleNo. Is there any solution for that.

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.