Unexpected result when trying to filter fields in mongo db - mongodb

I have a document structured as follows:
{
_id: "someid",
games: [{
slug: "world-of-warcraft",
class: 'shaman'
}, {
slug: 'starcraft-ii',
class: 'zerg'
}],
roles: {
'starcraft-ii': ['player'],
'world-of-warcraft': ['player']
}
}
I am trying to filter it so that only starcraft-ii within the games array will show up for all players in the role of player in starcraft-ii. I do the following:
function getUsersInGame(slug) {
return Users.find({
'games.slug': slug,
[`roles.${slug}`]: 'player'
}, {
fields: {
'games.$': 1,
'roles': 1
}
});
}
However, this does not match within the games array, and instead returns a 1-element array with world-of-warcraft instead.
What is the appropriate way to filter this array in mongo?

Use $elemMatch in your fields, since the $ will return the first element of the array. So your query should look like this:
function getUsersInGame(slug) {
return Users.find(
{
'"roles.'+ slug + '"': { $in : ['player']}
},
{
'games': {
$elemMatch: {'slug': slug}
},
'roles': 1
});
Please note the difference from the docs:
"The $ operator projects the array elements based on some condition from the query statement.
The $elemMatch projection operator takes an explicit condition argument. This allows you to project based on a condition not in the query..."

Related

Find a value in multiple nested fields with the same name in MongoDB

In some documents I have a property with a complex structure like:
{
content: {
foo: {
id: 1,
name: 'First',
active: true
},
bar: {
id: 2,
name: 'Second',
active: false
},
baz: {
id: 3,
name: 'Third',
active: true
},
}
I'm trying to make a query that can find all documents with a given value in the field name across the different second level objects foo, bar, baz
I guess that a solution could be:
db.getCollection('mycollection').find({ $or: [
{'content.foo.name': 'First'},
{'content.bar.name': 'First'},
{'content.baz.name': 'First'}
]})
But a I want to do it dynamic, with no need to specify key names of nested fields, nether repeat the value to find in every line.
If some Regexp on field name were available , a solution could be:
db.getCollection('mycollection').find({'content.*.name': 'First'}) // Match
db.getCollection('mycollection').find({'content.*.name': 'Third'}) // Match
db.getCollection('mycollection').find({'content.*.name': 'Fourth'}) // Doesn't match
Is there any way to do it?
I would say this is a bad schema if you don't know your keys in advance. Personally I'd recommend to change this to an array structure.
Regardless what you can do is use the aggregation $objectToArray operator, then query that newly created object. Mind you this approach requires a collection scan each time you execute a query.
db.collection.aggregate([
{
$addFields: {
arr: {
"$objectToArray": "$content"
}
}
},
{
$match: {
"arr.v.name": "First"
}
},
{
$project: {
arr: 0
}
}
])
Mongo Playground
Another hacky approach you can take is potentially creating a wildcard text index and then you could execute a $text query to search for the name, obviously this comes with the text index/query limitations and might not be right for your usecase.

Can't remove object in array using Mongoose

This has been extensively covered here, but none of the solutions seems to be working for me. I'm attempting to remove an object from an array using that object's id. Currently, my Schema is:
const scheduleSchema = new Schema({
//unrelated
_id: ObjectId
shifts: [
{
_id: Types.ObjectId,
name: String,
shift_start: Date,
shift_end: Date,
},
],
});
I've tried almost every variation of something like this:
.findOneAndUpdate(
{ _id: req.params.id },
{
$pull: {
shifts: { _id: new Types.ObjectId(req.params.id) },
},
}
);
Database:
Database Format
Within these variations, the usual response I've gotten has been either an empty array or null.
I was able slightly find a way around this and accomplish the deletion by utilizing the main _id of the Schema (instead of the nested one:
.findOneAndUpdate(
{ _id: <main _id> },
{ $pull: { shifts: { _id: new Types.ObjectId(<nested _id>) } } },
{ new: true }
);
But I was hoping to figure out a way to do this by just using the nested _id. Any suggestions?
The problem you are having currently is you are using the same _id.
Using mongo, update method allows three objects: query, update and options.
query object is the object into collection which will be updated.
update is the action to do into the object (add, change value...).
options different options to add.
Then, assuming you have this collection:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
},
{
"_id": 3
}
]
}
]
If you try to look for a document which _id is 2, obviously response will be empty (example).
Then, if none document has been found, none document will be updated.
What happens if we look for a document using shifts._id:2?
This tells mongo "search a document where shifts field has an object with _id equals to 2". This query works ok (example) but be careful, this returns the WHOLE document, not only the array which match the _id.
This not return:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
}
]
}
]
Using this query mongo returns the ENTIRE document where exists a field called shifts that contains an object with an _id with value 2. This also include the whole array.
So, with tat, you know why find object works. Now adding this to an update query you can create the query:
This one to remove all shifts._id which are equal to 2.
db.collection.update({
"shifts._id": 2
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example
Or this one to remove shifts._id if parent _id is equal to 1
db.collection.update({
"_id": 1
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example

Inserting data to nested array in mongodb

I have a document which looks like this
{
_id:1,
list_id:23,
name:'list01'
cards:[
{
id:3,
name:'card01'
categories:[{
id:10,
category:'section01',
tags:[{id:11,name:'tag01',is_selected: true}]
}]
}
]
}
I need to insert/push some data to tags array in a selected category for a given list_id but I'm getting an error saying
MongoError: Too many positional (i.e. '$') elements found in path
'cards.$.categories.$.tags'
This is the query that I have tried out. What's wrong with this query any idea on how to achieve this?
db.collection(TABLE)
.updateOne(
{ list_id: 23, 'cards.categories.category': 'section01'},
{ $push: { 'cards.$.categories.$.tags': { name: 'tag02', id: uuidv4(), is_selected: true } } }
);
You can not use multiple $ positional, for your case you can use single positional and arrayFilters,
The filtered positional operator $[<identifier>] identifies the array elements that match the arrayFilters conditions for an update operation,
db.collection(TABLE).updateOne({
list_id: 23,
"cards.categories.category": "section01"
},
{
$push: {
"cards.$.categories.$[elem].tags": {
name: "tag02",
id: uuidv4(),
is_selected: true
}
}
},
{
arrayFilters: [
{ "elem.category": "section01" }
]
})
Playground
In short, it is not possible.
Nested Arrays
The positional $ operator cannot be used for queries which traverse
more than one array, such as queries that traverse arrays nested
within other arrays, because the replacement for the $ placeholder is
a single value
https://docs.mongodb.com/manual/reference/operator/update/positional/
However, you may want to try $[]
Nested Arrays The filtered positional operator $[] can be
used for queries which traverse more than one array and nested arrays.
For an example, see Update Nested Arrays in Conjunction with $[].
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#position-nested-arrays-filtered
You can use $[identifier]
db.collection.update({
"list_id": 23,
"cards.categories.category": "section01"
},
{
$push: {
"cards.$.categories.$[elem].tags": {
name: "tag02",
id: uuidv4(),
is_selected: true
}
}
},
{
arrayFilters: [
{
"elem.category": "section01"
}
],
multi: true
})
try it here

MongoDB - Query nested objects in nested array with array of strings filter

So basically I need to filter my data with my own filter, which is array of strings, but problem is, that that exact field is inside nested object in array in DB. so, part of my Schema looks like this:
members: [
{
_id: { type: Schema.Types.ObjectId, ref: "Users" },
profilePicture: { type: String, required: true },
profile: {
firstName: { type: String },
lastName: { type: String },
occupation: { type: String },
gender: { type: String }
}
}
]
and my filter looks like this
gender: ["male","female"]
expected result with this filter is to get a team which has both male users and female users, if it has only male, or only female, it should not give me that team. but everything i've tried was giving me everything what included males and females even tho there were only male members.
what i've tried:
db.teams.find(members: { $elemMatch: { "profile.gender": { $in: gender } } })
This works only when there is one gender specified in the filter, and well, i know it must not work on what i am trying to achieve, but i dont know how to achieve it. any help will be appreciated
Edit: I've tried to do it in this way
db.teams.find({
$and: [
{ members: { $elemMatch: { "profile.gender": gender[0] } } },
{ members: { $elemMatch: { "profile.gender": gender[1] } } }
]
})
and this gives me result only when both filters are specified, however, if there is only one filter(either "male", or "female") it is giving me nothing.
Use $and operator instead of $in.
db.teams.find(members: {$elemMatch: {$and: [{'profile.gender': 'male'}, {'profile.gender': 'female'}]}})
This query works no matter how many elements you want to compare
db.teams.find({$and: [{'members.profile.gender': 'male'}, {'members.profile.gender': 'female'}]})
You need to dynamically generate the query before passing it to find, if you want to cover more than one case.
You can do this with the $all operator that finds docs where an array field contains contains all specified elements:
var gender = ['male', 'female'];
db.teams.find({'members.profile.gender': {$all: gender}});

mongodb query to verify embedded array sequence numbers

given a document structure as shown, where the trades array can have thousands of items... how on earth could one do a query that would verify that the sequence always has 'startTradeId' one number higher than the previous items 'endTradeId', all the way through the array? is this even possible?
{
"name": "STOCK",
"trades": [{
"endTradeId": 41306,
"startTradeId": 41302,
...
},
{
"endTradeId": 41301,
"startTradeId": 41297,
...
},
{
"endTradeId": 41296,
"startTradeId": 41240,
...
},
...
]
}
You can use $where operator like below :
db.your_collection.find( { $where : function(){ return "this.trades.startTradeId > this.trades.endTradeId" }});