Reputation: 85
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
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
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
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