How to make and requests in mongodb queries - mongodb

I've worked on this for about an hour now and I can't figure anything out that works so sorry if this is obvious.
I want my query to only return results where every result matches, but right now it returns a result if at least one match is found.
My document looks like this...
{
country: 'Great Britain',
data: [
{
school: 'King Alberts',
staff: [
{
name: 'John',
age: 37,
position: 'Head Teacher'
},
{
name: 'Grace',
age: 63,
position: 'Retired'
},
{
name: 'Bob',
age: 25,
position: 'Receptionist'
}
]
},
{
school: 'St Johns',
staff: [
{
name: 'Alex',
age: 22,
position: 'Head of Drama'
},
{
name: 'Grace',
age: 51,
position: 'English Teacher'
},
{
name: 'Jack',
age: 33,
position: 'Receptionist'
}
]
},
// ... more schools
]
}
The query I'm currently making looks like...
{ 'data.staff.name': { $in: names } }
and the 'names' array that is being provided looks like ['Bob', 'Grace', 'John', 'Will', 'Tom']. Currently both schools are being returned when I make this query, I think it's because the 'names' array contains 'Grace' which is a name present at both schools and so the document it matching. Does anyone know if there's a query I could make so mongodb only returns the school object if every name in the 'names' array is a member of staff at the school?

You need to use the aggregation pipeline for this, after matching the document we'll just filter out the none matching arrays, like so:
db.collection.aggregate([
{
$match: {
"data.staff.name": {
$in: names
}
}
},
{
$addFields: {
data: {
$filter: {
input: "$data",
cond: {
$eq: [
{
$size: {
"$setIntersection": [
"$$this.staff.name",
names
]
}
},
{
$size: "$$this.staff"
}
]
}
}
}
}
}
])
Mongo Playground

Related

Get only matched array object along with parent fields

I also checked the following question and tried various other things but
couldn't get it working
Retrieve only the queried element in an object array in MongoDB collection
I have the following document sample
{
_id: ObjectId("634b08f7eb5cb6af473e3ab2"),
name: 'India',
iso_code: 'IN',
states: [
{
name: 'Karnataka',
cities: [
{
name: 'Hubli Tabibland',
pincode: 580020,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Hubli Vinobanagar',
pincode: 580020,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Hubli Bengeri',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Kusugal',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
}
]
}
]
}
I need only the following
{
_id: ObjectId("634b08f7eb5cb6af473e3ab2"),
name: 'India',
iso_code: 'IN',
states: [
{
name: 'Karnataka',
cities: [
{
name: 'Kusugal',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
}
]
}
]
}
Following is the query that I have tried so far but it returns all the cities
db.countries.find(
{
'states.cities': {
$elemMatch: {
'name' : 'Kusugal'
}
}
},
{
'_id': 1,
'name': 1,
'states.name': 1,
'states.cities.$' : 1
}
);
I was able to achieve it with the help of aggregation.
db.countries.aggregate([
{ $match: { "states.cities.name": /Kusugal/ } },
{ $unwind: "$states" },
{ $unwind: "$states.cities" },
{ $match: { "states.cities.name": /Kusugal/ } }
]);
1st line $match will query the records with cities with only Kusugal
2nd & 3rd line $unwind will create a separate specific collection of documents from the filtered records
3rd line $match will filter these records again based on the condition
In simple aggregation processes commands and sends to next command and returns as an single result.

Mongoose - projection with $elemMatch on nested fields

I'm relatively new to MongoDB/Mongoose and I've only performed simple queries. Now I'm having some trouble trying to filter my database in a slightly more complex way. I already did some research to tackle my previous issues, but now I can't move forward. Here's what happening:
This is my schema:
const userSchema = new mongoose.Schema({
email: String,
password: String,
movies: [
{
title: String,
movieId: Number,
view_count: Number,
rating: Number,
review: String,
},
],
lists: {
watched_movies: [
{
title: String,
director: String,
genres: [{ type: String }],
runtime: Number,
date: Date,
},
],
},
});
I want to make a GET request that matches simultaneously "lists.watched_movies": { _id: req.params.entryId } and also "movies.title": req.body.title for a given email, so that the outcome of the findOne query would be just those elements and not the whole document. What I'm trying to accomplish is something like that:
{
email: "some.email#gmail.com",
movies: [
{
title: "Mongoose Strikes Back",
movieId: 123,
view_count: 1,
rating: 3,
review: "Very confusing movie!"
}
],
lists: {
watched_movies: [
{
_id: 4321
title: "Mongoose Strikes Back",
director: "Mongo",
genres: ["Drama"],
runtime: 150,
date: "2021-11-22"
}
]
}
}
My first attempt to tackle it, however, wasn't successful. Here's what I tried:
router.route("/:entryId").get((req, res) => {
User.findOne(
{ email: "some.email#gmail.com" },
{
"lists.watched_movies": { $elemMatch: { _id: req.params.entryId } },
movies: { $elemMatch: { title: req.body.title } },
},
(err, entry) => {
if (!err) {
res.send(entry);
console.log(entry);
} else {
console.log(err);
}
}
);
});
It says that Cannot use $elemMatch projection on a nested field. I thought that maybe I can solve it by changing my schema, but I'd like to avoid it if possible.
For your scenario, you can use $filter to filter document(s) in nested array field.
db.collection.find({
email: "some.email#gmail.com"
},
{
"lists.watched_movies": {
"$filter": {
"input": "$lists.watched_movies",
"cond": {
"$eq": [
"$$this._id",
4321// req.params.entryId
]
}
}
},
movies: {
$elemMatch: {
title: "Mongoose Strikes Back"// req.body.title
}
}
})
Sample Mongo Playground

MongoDB query - unwind and match preserving null OR different value/ add a new field based on a condition

If a have a following structure :
{
_id: 1,
name: 'a',
info: []
},
{
_id: 2,
name: 'b',
info: [
{
infoID: 100,
infoData: 'my info'
}
]
},
{
_id: 3,
name: 'c',
info: [
{
infoID: 200,
infoData: 'some info 200'
},
{
infoID: 300,
infoData: 'some info 300'
}
]
}
I need to query in such a way to obtain the documents where infoID is 100 showing the infoData, or nothing if info is empty, or contains subdocuments with infoID different from 100.
That is, I would want the following output:
{
_id: 1,
name: 'a',
infoData100: null
},
{
_id: 2,
name: 'b',
infoData100: 'my info'
},
{
_id: 3,
name: 'c',
infoData100: null
}
If I $unwind by info and $match by infoID: 100, I lose records 1 and 3.
Thanks for your responses.
Try below query :
Query :
db.collection.aggregate([
/** Adding a new field or you can use $project instead of addFields */
{
$addFields: {
infoData100: {
$cond: [
{
$in: [100, "$info.infoID"] // Check if any of objects 'info.infoID' has value 100
},
{
// If any of those has get that object & get infoData & assign it to 'infoData100' field
$let: {
vars: {
data: {
$arrayElemAt: [
{
$filter: {
input: "$info",
cond: { $eq: ["$$this.infoID", 100] }
}
},
0
]
}
},
in: "$$data.infoData"
}
},
null // If none has return NULL
]
}
}
}
]);
Test : MongoDB-Playground

MongoDB query on objects in an array based on condition

I have a players inventory that looks like this.
let inventory = [ { name: 'Wood', amount: 6 }, { name: 'Stone', amount: 2 } ]
This is the players resources.
I also have a list of craftable items.
{"name":"CraftingTable","craftingReagents":[{"name":"Stone","amount":"2"}]}
{"name":"CraftingTable2","craftingReagents":[{"name":"Wood","amount":"4"}]}
{"name":"CraftingTable3","craftingReagents":[{"name":"Wood","amount":"5"},{"name":"Stone","amount":"2"}]}
The items schema is as such
let itemSchema = new mongoose.Schema(
{
name:String,
craftingReagents: [
{
name: String,
amount: Number
}
]
}
);
I want a query that will return all craftable objects where the players inventory has sufficient resources to do so.
For example with 6 Wood and 2 Stone the query should return all 3 crafting tables, as the player has sufficient resources to craft all three
This is what I have so far, and I am very lost. Please help!
itemModel.find({
craftingReagents: {
$all: [{
$elemMatch: {
name: {
$in: [
'Wood'
]
},
amount: { $lte: 6 }
}
},
{
$elemMatch: {
name: {
$in: [
'Stone'
]
},
amount: { $lte: 2 }
}
}
]
}
});
Heres the thing. The players inventory can change to an infinite number of different resources. AND the craftable objects can have an infinite number of different requirments. I meerly gave an example of what the inventory, and craftable objects could look like.
How do you list documents that the player has enough resources to craft?
Try this :
itemModel.find({
$or: [{ craftingReagents: { $elemMatch: { name: 'Wood', amount: { $lte: 6 } } } },
{ craftingReagents: { $elemMatch: { name: 'Stone', amount: { $lte: 2 } } } }]
})

MongoDb find by value in array

here is the model of my collection :
classes:[{
class:String,
info:{
subjects:[String],
students:[{
name:String,
surname:String,
matriculae:Number,
path_1:String,
path_2:String
}],
classTeacher:{
name:String,
surname:String
}
}
}],
accademicYear:String}];
I'd like to retrive value 'matriculae' given the name,surname and accademicYear of a student. I cant wrap my head 'round it tho! Thanks for help.
If you mean you want the result flat format, try this:
School.aggregate([
{
$unwind: '$classes'
}, {
$project: {
accademicYear: 1,
students: "$classes.info.students"
}
}, {
$unwind: "$students"
}, {
$project: {
accademicYear: 1,
matriculae: "$students.matriculae",
name: "$students.name",
surname: "$students.surname",
}
}
])
In case of the classes is collection and accademicYear is inside of the classes collection.Plus added the match criteria.
db.getCollection('classes').aggregate([{
$project: {
accademicYear: 1,
students: "$info.students"
}
}, {
$unwind: "$students"
}, {
$project: {
accademicYear: 1,
matriculae: "$students.matriculae",
name: "$students.name",
surname: "$students.surname",
}
}, {
$match: {
name: name,
surname: surname,
accademicYear: year
}
}])