Mongodb findone document in array by object id - node.js

Can I get only 1 photo by objectid? I only need to get 1 Image detail from 1 post by photo but what i get is all photo of post.
this is my db structure
and this is my code looks like:
Post.findOne({
$and: [
{ photo: { $elemMatch: { _id: id } } } ]
}).exec((err, post) => {
if (err) {
return res.status(400).json({ error: err });
}
req.post = post;
console.log(req.post);
next();
});
what i get in req.post is only [].
Thanks in advance

The $elemMatch projection operator provides a way to alter the returned documents, in here coupled with find utilizing second parameter which is projection will achieve that.
Post.find(
{},
{
_id: 0,
photo: { $elemMatch: { _id: id } }
}
);
This will provide leaned documents with the promise: .lean().exec():
Post.find(
{},
{
_id: 0,
photo: { $elemMatch: { _id: id } }
}
)
.lean()
.exec();
NOTE: $elemMatch behaviour is to return the first document where photo's id matches.

You can try with aggregate instead of findOne:
https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
Post.aggregate([
{ $match: { 'photo._id': id } },
{ $unwind: "$photo" },
{ $match: { 'photo._id': id } },
]);
Maybe not the best, but single photo data is achievable.

Related

How can I find specific document and update a value of specific key inside array?

I have a structure like this:
{
_id: new ObjectId("634aa49f98e3a05346dd2327"),
filmName: 'Film number 1',
episodes: [
{
episodeName: 'Testing 1',
slugEpisode: 'testing-1',
_id: new ObjectId("6351395c17f08335f1dabfc9")
},
{
episodeName: 'Testing 2',
slugEpisode: 'testing-2',
_id: new ObjectId("6351399d9a2533b9be1cbab0")
},
],
},
{
_id: new ObjectId("634aa4cc98e3a05346dd232a"),
filmName: 'Film number 2',
episodes: [
{
episodeName: 'Something 1',
slugEpisode: 'something-1',
_id: new ObjectId("6367cce66d6b85442f850b3a")
},
{
episodeName: 'Something 2',
slugEpisode: 'something-2',
_id: new ObjectId("6367cd0e6d6b85442f850b3e")
},
],
}
I received 3 fields:
_id: Film _id
episodeId: Episode _id
episodeName: The content I wish to update
I tried to find a specific Film ID to get a specific film, and from then on, I pass an Episode ID to find the exact episode in the episodes array. Then, update the episodeName of that specific episode.
Here's my code in NodeJS:
editEpisode: async (req, res) => {
const { _id } = req.params
const { episodeId, episodeName } = req.body
try {
const specificResult = await Films.findOneAndUpdate(
{ _id, 'episodes._id': episodeId },
{ episodeName }
)
console.log(specificResult)
res.json({ msg: "Success update episode name" })
} catch (err) {
return res.status(500).json({ msg: err.message })
}
},
But what console.log display to me is a whole document, and when I check in MongoDB, there was no update at all, does my way of using findOneAndUpdate incorrect?
I'm reading this document: MongooseJS - Find One and Update, they said this one gives me the option to filter and update.
The MongoDB server needs to know which array element to update. If there is just one array element to update, here's one way you could do it. (I picked a specific element. You would use your req.params and req.body.)
db.films.update({
"_id": ObjectId("634aa4cc98e3a05346dd232a"),
"episodes._id": ObjectId("6367cd0e6d6b85442f850b3e")
},
{
"$set": {
"episodes.$.episodeName": "Something Two"
}
})
Try it on mongoplayground.net.
You can use the filtered positional operator $[<identifier>] which essentially finds the element or object (in your case) with a filter condition and updates that.
Query:
const { _id } = req.params
const { episodeId, episodeName } = req.body
await Films.update({
"_id": _id
},
{
$set: {
"episodes.$[elem].episodeName": episodeName
}
},
{
arrayFilters: [
{
"elem._id": episodeId
}
]
})
Check it out here for example purpose I've put ids as numbers and episode name to update as "UpdatedValue"

mongodb using $not getting undefined, in NodeJs

I am trying to make a find command in Mongo using $not as i see in this link,
await Match.find(
{
type:"Friendly", status:"Pending", "date.fullDate":{$gt: date},
$not:{$or: [{"team1.players":{$elemMatch: {_id:playerId}}}, {"team2.players":{$elemMatch: {_id:playerId}}}]}
}).sort({'date.fullDate':1}).exec(function(err, doc) {
console.log(doc)
return res.send({data: doc});
});
However, i am getting undefined.
I am thinking the problem is with the $not because when i remove it and make the command like this it works.
await Match.find(
{
type:"Friendly", status:"Pending", "date.fullDate":{$gt: date},
$or: [{"team1.players":{$elemMatch: {_id:playerId}}}, {"team2.players":{$elemMatch: {_id:playerId}}}]
}).sort({'date.fullDate':1}).exec(function(err, doc) {
console.log(doc)
return res.send({data: doc});
});
Note: that team2 could be null in the documents.
Any ideas why i am getting this undefined.
My document look like this:
match:{
team1:{
players:[
_id:value,
otherfields...
]
},
team2:{
players:[
_id:value,
otherfields...
]
}
}
await Match.find({
type: "Friendly", status: "Pending", "date.fullDate": { $gt: date },
$or: [
{
$not: {
"team1.players": { $elemMatch: { _id: playerId } }
}
},
{
$not: {
"team2.players": { $elemMatch: { _id: playerId } }
}
}
]
}).sort({ 'date.fullDate': 1 }).exec(function (err, doc) {
console.log(doc)
return res.send({ data: doc });
});
I had to use $nor instead of $not:{$or:[]}
Similar question here.

Cannot find id and update and increment sub-document - returns null Mongoose/mongoDB

I have a problem where I cannot seem to retrieve the _id of my nested objects in my array. Specifically the foods part of my object array. I want to find the _id, of lets say risotto, and then increment the orders count dynamically (from that same object).
I'm trying to get this done dynamically as I have tried the Risotto id in the req.body._id and thats fine but i can't go forward and try to increment orders as i get null.
I keep getting null for some reason and I think its a nested document but im not sure. heres my route file and schema too.
router.patch("/update", [auth], async (req, res) => {
const orderPlus = await MenuSchema.findByIdAndUpdate({ _id: '5e3b75f2a3d43821a0fb57f0' }, { $inc: { "food.0.orders": 1 }}, {new: true} );
//want to increment orders dynamically once id is found
//not sure how as its in its own seperate index in an array object
try {
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});
Schema:
const FoodSchema = new Schema({
foodname: String,
orders: Number,
});
const MenuSchema = new Schema({
menuname: String,
menu_register: Number,
foods: [FoodSchema]
});
Heres the returned Database JSON
{
"_id": "5e3b75f2a3d43821a0fb57ee",
"menuname": "main course",
"menu_register": 49,
"foods": [
{
"_id": "5e3b75f2a3d43821a0fb57f0",
"foodname": "Risotto",
"orders": 37
},
{
"_id": "5e3b75f2a3d43821a0fb57ef",
"foodname": "Tiramisu",
"orders": 11
}
],
"__v": 0
}
the id for the menuname works in its place but i dont need that as i need to access the foods subdocs. thanks in advance.
You are sending food id (5e3b75f2a3d43821a0fb57f0) to the MenuSchema.findByIdAndUpdate update query. It should be the menu id which is 5e3b75f2a3d43821a0fb57ee
You can find a menu by it's id, and update it's one of the foods by using food _id or foodname using mongodb $ positional operator.
Update by giving menu id and food id:
router.patch("/update", [auth], async (req, res) => {
try {
const orderPlus = await MenuSchema.findByIdAndUpdate(
"5e3b75f2a3d43821a0fb57ee",
{ $inc: { "foods.$[inner].orders": 1 } },
{ arrayFilters: [{ "inner._id": "5e3b75f2a3d43821a0fb57f0" }], new: true }
);
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});
Update by giving menu id and foodname:
router.patch("/update", [auth], async (req, res) => {
try {
const orderPlus = await MenuSchema.findByIdAndUpdate(
"5e3b75f2a3d43821a0fb57ee",
{ $inc: { "foods.$[inner].orders": 1 } },
{ arrayFilters: [{ "inner.foodname": "Risotto" }], new: true }
);
res.status(200).send(orderPlus);
} catch (err) {
res.status(500).send(err);
}
});

Get result as an array instead of documents in mongodb for an attribute

I have a User collection with schema
{
name: String,
books: [
id: { type: Schema.Types.ObjectId, ref: 'Book' } ,
name: String
]
}
Is it possible to get an array of book ids instead of object?
something like:
["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]
Or
{ids: ["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]}
and not
{
_id: ObjectId("53eb79d863ff0e8229b97448"),
books:[
{"id" : ObjectId("53eb797a63ff0e8229b4aca1") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acac") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acad") }
]
}
Currently I am doing
User.findOne({}, {"books.id":1} ,function(err, result){
var bookIds = [];
result.books.forEach(function(book){
bookIds.push(book.id);
});
});
Is there any better way?
It could be easily done with Aggregation Pipeline, using $unwind and $group.
db.users.aggregate({
$unwind: '$books'
}, {
$group: {
_id: 'books',
ids: { $addToSet: '$books.id' }
}
})
the same operation using mongoose Model.aggregate() method:
User.aggregate().unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
Note that books here is not a mongoose document, but a plain js object.
You can also add $match to select some part of users collection to run this aggregation query on.
For example, you may select only one particular user:
User.aggregate().match({
_id: uid
}).unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
But if you're not interested in aggregating books from different users into single array, it's best to do it without using $group and $unwind:
User.aggregate().match({
_id: uid
}).project({
_id: 0,
ids: '$books.id'
}).exec(function(err, users) {
// use users[0].ids
})

Mongoose select $ne in array

I am wondering how you would do a query where array._id != 'someid'.
Here is an example of why I would need to do this. A user wants to update their account email address. I need these to be unique as they use this to login. When they update the account I need to make sure the new email doesn't exist on another account, but don't give an error if it exists on their account already (they didn't change their email, just something else in their profile).
Below is the code I have tried using. It doesn't give any errors, but it always returns a count of 0 so an error is never created even if it should.
Schemas.Client.count({ _id: client._id, 'customers.email': email, 'customers._id': { $ne: customerID } }, function (err, count) {
if (err) { return next(err); }
if (count) {
// it exists
}
});
I'm guessing it should use either $ne, or $not, but I can't find any examples for this online with an ObjectId.
Example client data:
{
_id: ObjectId,
customers: [{
_id: ObjectId,
email: String
}]
}
With your existing query, the customers.email and customers._id parts of your query are evaluated over all elements of customers as a group, so it won't match a doc that has any element with customerID, regardless of its email. However, you can use $elemMatch to change this behavior so the two parts operate in tandem on each element at a time:
Schemas.Client.count({
_id: client._id,
customers: { $elemMatch: { email: email, _id: { $ne: customerID } } }
}, function (err, count) {
if (err) { return next(err); }
if (count) {
// it exists
}
});
I was able to do this using aggregate.
Why this didn't work the way I had it: When looking for $ne: customerID it will never return a result because that _id does in fact exist. It can't combine cutomers.email and customers._id the way I wanted it to.
Here is how it looks:
Schemas.Client.aggregate([
{ $match: { _id: client._id } },
{ $unwind: '$customers' },
{ $match: {
'customers._id': { $ne: customerID },
'customers.email': req.body.email
}},
{ $group: {
_id: '$_id',
customers: { $push: '$customers' }
}}
], function (err, results) {
if (err) { return next(err); }
if (results.length && results[0].customers && results[0].customers.length) {
// exists
}
});
);

Resources