MongoDB: multiple $elemMatch - mongodb

I have MongoDB documents structured like this:
{_id: ObjectId("53d760721423030c7e14266f"),
fruit: 'apple',
vitamins: [
{
_id: 1,
name: 'B7',
usefulness: 'useful',
state: 'free',
}
{
_id: 2,
name: 'A1',
usefulness: 'useful',
state: 'free',
}
{
_id: 3,
name: 'A1',
usefulness: 'useful',
state: 'non_free',
}
]
}
{_id: ObjectId("53d760721423030c7e142670"),
fruit: 'grape',
vitamins: [
{
_id: 4,
name: 'B6',
usefulness: 'useful',
state: 'free',
}
{
_id: 5,
name: 'A1',
usefulness: 'useful',
state: 'non_free',
}
{
_id: 6,
name: 'Q5',
usefulness: 'non_useful',
state: 'non_free',
}
]
}
I want to query and get all the fruits which have both {name: 'A1', state: 'non_free'} and {name: 'B7', state: 'free'}.
In the worst case I want at least to count these entries if getting them is not possible and if the equivalent code exists for pymongo, to know how to write it.
For the given example I want to retrieve only the apple (first) document.
If I use $elemMatch it works only for one condition, but not for both. E.g. if I query find({'vitamins': {'$elemMatch': {'name': 'A1', 'state': 'non_free'}, '$elemMatch': {'name': 'B7', 'state': 'free'}}}) it will retrieve all the fruits with {'name': 'B7', 'state': 'free'}.

In this case you can use the $and-operator .
Try this query:
find({
$and: [
{'vitamins': {'$elemMatch': {'name': 'A1', 'state': 'non_free'} } },
{'vitamins': {'$elemMatch': {'name': 'B7', 'state': 'free'} } }
]
});
To explain why you received only the result matching the second criteria: The objects inside each {} you pass to MongoDB are key/value pairs. Each key can only exist once per object. When you try to assign a value to the same key twice, the second assignment will override the first. In your case you assigned two different values to the key $elemMatch in the same object, so the first one was ignored. The query which actually arrived in MongoDB was just find({'vitamins': {'$elemMatch': {'name': 'B7', 'state': 'free'}}}).
Whenever you need to apply the same operator to the same key twice, you need to use $or or $and.

var fruits = db.fruits.find({
"vitamins": {
$all: [{
$elemMatch: {
"name": "A1",
"state": "non_free"
}
}, {
$elemMatch: {
"name": "B7",
"state": "free"
}
}]
}
})

let query = [];
query.push({
id: product.id,
});
query.push({ date });
for (const slot of slots) {
query.push({
slots: {
$elemMatch: {
id: slot.id,
spots: { $gte: slot.spots },
},
},
});
}
const cal = await Product.findOne({ $and: query });

Related

Find an item within an array in a collection with the highest score overall and return with that score

Schema:
const restaurantSchema = new mongoose.Schema({
address: {
building: String,
coord: [Number, Number],
street: String,
zipcode: String,
},
borough: String,
cuisine: String,
grades: [{ date: Date, grade: String, score: Number }],
name: String,
restaurant_id: String,
});
I am trying to find a restaurant with the highest grades.score and return what that score is.
What I've tried doing:
Restaurant.find({}, { "grades.score": 1, _id: 0 }).sort({ "grades.score": -1 }).limit(1).exec((err, docs) => {
if (err) {
console.log(err);
} else {
console.log(docs);
}
});
What it returns:
[
{
grades: [ [Object], [Object], [Object], [Object], [Object], [Object] ]
}
]
What I want:
[
{
score: 131
}
]
Query
you can use the $max aggregate operator that works as accumulator and on arrays like here
this is for the local(inside the document array) max-score if you want for the global(in all the collection) you can sort desc by max-score
and $limit 1
Playmongo
aggregate(
[{"$project": {"_id": 0, "max-score": {"$max": "$grades.score"}}}])

How to query to get specific objects from array of objects?

Currently I am learning mongodb. Suppose I have one collection named post in mongodb which data is :
[{
id: 123,
uId: 111,
msg: 'test 1',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false},
{name: 'attach2', url: 'https://example.com', isDeleted: true}
]
},
{
id: 456,
uId: 222,
msg: 'test 2',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: true}
]
},
{
id: 789,
uId: 333,
msg: 'test 3',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false}
]
}]
Now i want the result of all post where attachments.isDeleted is false which look like :
[{
id: 123,
uId: 111,
msg: 'test 1',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false}
]
},
{
id: 456,
uId: 222,
msg: 'test 2',
attachments: []
},
{
id: 789,
uId: 333,
msg: 'test 3',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false}
]
}]
I tried this db.post.find({attachments: {$elemMatch: {isDeleted: false}}}) but it is not working. I have taken help from [https://stackoverflow.com/questions/62953855/how-get-query-for-array-of-objects-in-mongodb]
I think this is what you are looking for. It uses the Mongodb aggregation framework. You can take a look the documentation to see details. In brief, the $project stage allow us select fields to show or to hide, and it admits calculated fields. We calculated a new attachments field using $filter stage with a the given condition (isDeleted equals to false).
db.collection.aggregate([
{
"$project": {
_id: 1,
uId: 1,
msg: 1,
attachments: {
"$filter": {
"input": "$attachments",
"as": "attachment",
"cond": {
"$eq": [
"$$attachment.isDeleted",
false
]
}
}
}
}
}
])
You can try it in: https://mongoplayground.net/p/YDvpkbZIZ2H
Note that I changed id by _id, but it was only for style purpose.
Hope I helped.

How to make and requests in mongodb queries

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

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

PyMongo: Removing a nested object without knowing the key

Let's say I have a collection that looks like this:
{
'_id': ObjectId('abc'),
'customer': 'bob',
'products': {
'1234':
{'name': 'Shirt',
'productID': 5
},
'5679': {
'name': 'Hat',
'productID': 5
}
}
'1011': {
'name': 'Jeans',
'productID': 9
}
}
}
I am looking to remove all nested objects whose 'productID' property is 5, so the collection would look like this afterwards:
{'_id': ObjectId('abc'),
'name': 'bob',
'products': {
'1011': {
'name': 'Jeans',
'productID': 9
}
}
}
I know the following information:
customer: bob
productID: 5
Is it possible to do a wildcard on 'products'? Something like this (it does not work):
db.update({'customer':'bob'}, {'$unset': {'products.*': {'productID': 9}})
If you have a choice, refactor your data to make each item a list element, e.g.
{
'customer': 'bob',
'products': [
{'code': '1234',
'name': 'Shirt',
'productID': 5
},
{'code': '5679',
'name': 'Hat',
'productID': 5
},
{'code': '1011',
'name': 'Jeans',
'productID': 9
}
]
}
Then your update becomes a piece of cake:
db.mycollection.update_one({'customer': 'bob'}, {'$pull': {'products': {'productID': 5}}})
result:
{
"customer": "bob",
"products": [
{
"code": "1011",
"name": "Jeans",
"productID": 9
}
]
}
Persisting with poor choices of schema will no yield long term rewards.