MongoDB / Mongoose: is there a way to query for a value within a field that is an object that contains two arrays? - mongodb

At this moment I am using 2 queries to search for a username value inside a field that is an object that contains 2 arrays then merging them together. I can't seem to figure out how to write a query that would search through both arrays at once and return all results that match a given username in any of the arrays. I was wondering if this is even possible? Thanks for any inputs!
Game.find({
active: false,
'current_players.team1': { $elemMatch: { $in: [username] } }
})
Game.find({
active: false,
'current_players.team2': { $elemMatch: { $in: [username] } }
})
This is the schema I am working with:
var Game = mongoose.Schema({
id: ...,
...,
...,
current_players: {
team1: Array // [ 'jim', 'bob', 'sarah', null ]
team2: Array // [ 'peter', 'frank', null, 'simon']
}
})

To search both arrays in one query for this problem you can use the $or operator.
db.collection.find({
"active": false,
$or: [
{
"current_players.team1": "username"
},
{
"current_players.team2": "username"
}
]
})

Related

mongodb query map by key

I have the following structure in mongodb:
person:
{
"1" [personId] : ["some_text", "some_text"],
"2" [personId] : ["some_text", "some_text"],
"3" [personId] : ["some_text", "some_text"]
}
I would like to query a person map structure and get only values when personid (key) is between 1 to 2
I try to use $elemMatch but its not a good idea because I would like query by a dynamic range.
I use map strucute becuase I have another process that insert person data dynamically by person id.
Is there any way to filter map structure data by key?
Thanks
The code is not actually tested. But this might work:
const searchParams = [
{ $elemMatch: { fieldA: "val1", fieldB: "val2" } },
{ $elemMatch: { fieldC: "val3", fieldD: "val4" } },
]
const results = await yourDB.find({
person: {
$in: searchParam
}
})
The elements of searchParams can be dynamically generated based on your need.
searchParams.psuh({ $elemMatch: { fieldX: "val_x", fieldZ: "val_z })
You need an to run an aggregate operation where you can convert the hash map into an array of key value documents, filter the array based on the above condition and convert the filtered array back to the hash map.
Take for instance the following operation, it uses the $addFields pipeline step to do the above operations with the aid of the $objectToArray, $filter and $arrayToObject operators:
db.collection.aggregate([
{ '$addFields': {
'person': {
'$arrayToObject': {
'$filter': {
'input': { '$objectToArray': '$person' },
'cond': {
'$in': ['$$this.k', ['1', '2']]
}
}
}
}
} }
])

Monogdb $expr with multiple level of nesting

I don't know whether this is possible or not and the doc for $expr doesn't seem to mention it as well, but I'm trying to compare two fields where one is a hash type and the other is an array of hashes. I'm trying to query for records where the hash field is equal to the first hash element in the array of hashes. Here is my collection layout:
{
_id: "Some ID",
answers: [ { staff_id: 1, answer: { dob: true, type: true } } ,
qa_answer: { dob: true, type: true }
}
The query expression I've tried is:
collection.find({ '$expr': { '$eq': ['$answers.0.answer', '$qa_answer'] } })
But this doesn't seem to work. I thought it was the nested dot notation that was the problem but I've tried the following and it worked:
collection.find({ 'answers.0.answer': { "dob": true, "type": true } })
Okay I finally got it to work, it turns out the dot notation was the issue. Here's the query I ended up with:
collection.find({
'$expr': {
'$eq': [
{ '$arrayElemAt': [ '$answers.answer', 0 ] },
'$qa_answer'
]
}
})

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": "someEmail#email.email"
"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: "someEmail#email.com"}}}, {'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": "someEmail#email.com",
....
}
}
}
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.
Since you've this condition 'answers': {$elemMatch: {participantEmail: "someEmail#email.com"}} in filter part of .findOne() - If for given _id document there are no elements in answers. participantEmail array match with input value "someEmail#email.com" 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", "someEmail#email.com" ] }
}
}
}
}
])
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.
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", "someEmail#email.com" ] }
}
}
}).exec();
With this sample input document:
{
_id: 1,
title: "t-1",
category: "cat-abc",
creationDate: ISODate("2020-05-05T07:01:09.853Z"),
questions: [ ],
answers: [
{ participantEmail: "someEmail#email.email", currentIndex: 14 }
]
}
And, with this query:
EMAIL_TO_MATCH = "someEmail#email.email"
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" : "someEmail#email.email",
"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.

How can I find all records where an id is in one array, or another id is in another array?

I need to perform a query that returns all results where an id, or array of ids in an array of ids AND another id, or array of ids, is in another array of ids. Perhaps an example will better explain what I'm trying to do:
Schema:
var somethingSchema = mongoose.Schema({
space_id : String,
title : String,
created : {
type: Date,
default: Date.now
},
visibility : {
groups : [{
type : String,
ref : 'Groups'
}],
users : [{
type : String,
ref : 'User'
}]
}
});
Query:
something.find({
space_id: req.user.space_id,
$and: [
{ $or: [{ "visibility.groups": { $in: groups } }] },
{ $or: [{ "visibility.users": { $in: users } }] }
]
}, function (err, data) {
return res.json(data);
});
In this example, both groups and users are arrays of ids. The query above isn't working. It always returns an empty array. What am I doing wrong?
You should be including all clauses to OR together in a single $or array:
something.find({
space_id: req.user.space_id,
$or: [{ "visibility.groups": { $in: groups } },
{ "visibility.users": { $in: users } }]
}, function (err, data) {
return res.json(data);
});
Which translates to: find all docs with a matching space_id AND that have a visibility.groups value in groups OR a visibility.users value in users.

How to remove an array value from item in a nested document array

I want to remove "tag4" only for "user3":
{
_id: "doc"
some: "value",
users: [
{
_id: "user3",
someOther: "value",
tags: [
"tag4",
"tag2"
]
}, {
_id: "user1",
someOther: "value",
tags: [
"tag3",
"tag4"
]
}
]
},
{
...
}
Note: This collection holds items referencing many users. Users are stored in a different collection. Unique tags for each user are also stored in the users collection. If an user removes a tag (or multiple) from his account it should be deleted from all items.
I tried this query, but it removes "tag4" for all users:
{
"users._id": "user3",
"users.tags": {
$in: ["tag4"]
}
}, {
$pullAll: {
"users.$.tags": ["tag4"]
}
}, {
multi: 1
}
I tried $elemMatch (and $and) in the selector but ended up with the same result only on the first matching document or noticed some strange things happen (sometimes all tags of other users are deleted).
Any ideas how to solve this? Is there a way to "back reference" in $pull conditions?
You need to use $elemMatch in your query object so that it will only match if both the _id and tags parts match for the same element:
db.test.update({
users: {$elemMatch: {_id: "user3", tags: {$in: ["tag4"]}}}
}, {
$pullAll: {
"users.$.tags": ["tag4"]
}
}, {
multi: 1
})