Lorincz Alexandru
Lorincz Alexandru

Reputation: 85

Mongoose get only specific object from array or empty array if there is none

So I have a questionnaire model:

const schema = new mongoose.Schema({
  title: String,
  category: String,
  description: String,
  requirementOption: String,
  creationDate: String,
  questions: [],
  answers: []
})

As you can see the answers is an array. This array contains object that have this structure

{
  "participantEmail": "[email protected]"      
  "currentIndex": 14,
  ...
}

Now I want to get a specific questionnaire by id, but in answers array I only want specific participant email. So the answers array should have either one element or no element. But I don't want to get null result if there is no such email in the answers array.

I figure it out how to get that specific element from array with this query:

dbModel.findOne({_id: id, 'answers': {$elemMatch: {participantEmail: "[email protected]"}}}, {'answers.$': 1}).exec();

And if that email exists in the answer array I will get this:

 "data": {
    "questionnaireForParticipant": {
      "id": "5d9ca298cba039001b916c55",
      "title": null,
      "category": null,
      "creationDate": null,
      "description": null,
      "questions": null,
      "answers": [
        {
          "participantEmail": "[email protected]",
          ....
         }

    }
  }

But if that email is not in the answers array I will get only null. Also I would like to get the title and category and all of the other fields. But I can't seem to find a way to do this.

Upvotes: 2

Views: 888

Answers (3)

Lorincz Alexandru
Lorincz Alexandru

Reputation: 85

The accepted answer is correct, but if you are using mongoose like I do this is how you have to write the accepted answer query:

      dbModel.aggregate([
          { 
            $match: { "_id": mongoose.Types.ObjectId("5a934e000102030405000000") } 
          }]).addFields({
            answers: {
              $filter: {
                input: "$answers",
                  cond: { $eq: [ "$$this.participantEmail", "[email protected]" ] }
              }
            }
          }).exec();

Upvotes: 1

prasad_
prasad_

Reputation: 14287

With this sample input document:

{
  _id: 1,
  title: "t-1",
  category: "cat-abc",
  creationDate: ISODate("2020-05-05T07:01:09.853Z"),
  questions: [ ],
  answers: [
      { participantEmail: "[email protected]", currentIndex: 14 }
  ]
}

And, with this query:

EMAIL_TO_MATCH = "[email protected]"

db.questionnaire.findOne( 
  { _id: 1 }, 
  { title: 1, category: 1, answers: { $elemMatch: { participantEmail: EMAIL_TO_MATCH } } } 
)

The query returns (when the answers.participantEmail matches):

{
        "_id" : 1,
        "title" : "t-1",
        "category" : "cat-abc",
        "answers" : [
                {
                        "participantEmail" : "[email protected]",
                        "currentIndex" : 12
                }
        ]
}

And, when the answers.participantEmail doesn't match or if the amswers array is empty, the result is:

{ "_id" : 1, "title" : "t-1", "category" : "cat-abc" }


NOTE: The $elemMatch used in the above query is a projection operator.

Upvotes: 0

whoami - fakeFaceTrueSoul
whoami - fakeFaceTrueSoul

Reputation: 17915

Since you've this condition 'answers': {$elemMatch: {participantEmail: "[email protected]"}} in filter part of .findOne() - If for given _id document there are no elements in answers. participantEmail array match with input value "[email protected]" then .findOne() will return null as output. So if you wanted to return document irrespective of a matching element exists in answers array or not then try below query :

db.collection.aggregate([
  {
    $match: { "_id": ObjectId("5a934e000102030405000000") }
  },
  /** addFields will re-create an existing field or will create new field if there is no field with same name */
  {
    $addFields: {
      answers: {
        $filter: { // filter will result in either [] or array with matching elements
          input: "$answers",
          cond: { $eq: [ "$$this.participantEmail", "[email protected]" ] }
        }
      }
    }
  }
])

Test : mongoplayground

Ref : aggregation-pipeline

Note : We've used aggregation as you wanted to return either answers array with matched element or an empty array. Also you can use $project instead of $addFields to transform the output as you wanted to.

Upvotes: 1

Related Questions