2

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.

7
  • Did I get it right? Do you want to get all documents which have an answer by specific email OR do not have an answer at all Commented May 5, 2020 at 0:19
  • @VaheYavrumian I updated the question to make it a bit more clear Commented May 5, 2020 at 0:24
  • At first, there is only one document with specific ObjectID(_id) in entire collection, so you simply don't need other filters, you can just get that one doc with Model.findById(_id) and do other validations in your code Commented May 5, 2020 at 0:36
  • @VaheYavrumian ok, got it, but what if I want to update only a specific answer, what query should I use for that? Commented May 5, 2020 at 0:42
  • I think the best way will be getting document by any query you want, then making changes in your code and then saving doc with doc.save(), I think this will be easier than editing doc with Update query Commented May 5, 2020 at 0:54

3 Answers 3

1

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.

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

2 Comments

Thank you, that's it
@LorinczAlexandru : Just as note - Using above query you definitely get the doc out but when it comes to answers array either it can be empty [] if no match is found or will have matching object/objects like [{...}]. If you don't need that field at all in the response document then you can simply use $elemMatch in projection as stated..
1

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();

1 Comment

Yes in mongoose you would do .aggregate() on mongoose model dbModel & has to call .exec() :-)
0

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.

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.