Can mongo has $cond in $push? - node.js

I need to push 1 item to array and save it to mongo if that item is not existed in array.
Sample: I have a record with an array
{
_id: ObjectId('xxxxx'),
userEmails: [
{
email: 'xxx#gmail.com,
addedAt: ISODate('xxx')
}
]
}
Current query:
db.users.updateOne(
{ _id: ObjectId('xxxxx') },
{
$push: {
userEmails: {
email: 'xxx#gmail.com',
addedAt: new Date(),
}
}
}
);
I expect if xxx#gmail.com is existed, it shouldn't pushed to array. I don't want array have duplicated items

Here We are specifying condition that if xxx#gmail.com is not inside userEmail then only we are pushing data.
db.users.updateOne(
{ _id: ObjectId('xxxxx'),"userEmails.email":{$ne: "xxx#gmail.com"} },
{
$push: {
userEmails: {
email: 'xxx#gmail.com',
addedAt: new Date(),
}
}
});

If you want to have a condition in $push, the best way to go will be to go with the $nin operation.
({name: {$nin: ["Shehroz", "virk"]}})
You can replace name with the user Email, so that it verifies the condition before updating

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"

remove a particular array element from mongodb on clicking the element

I'm using this code for removing a particular array element stored in MongoDB when clicked that from the app. But this code is not working.
schema structure looks like this -
const tagsSchema = new Schema({
category: {
type: String,
required: true
},
tname: { type: Array }
}, { _id: true });
Below is the code I'm using for removing array element from db -
Tags.updateOne({ tname: req.params.name }, { $pull: { _id: [req.params.id] } })
For example - "tname": "technical", "nontechnical"
Now, technical is being clicked in the app to remove, but with the code I'm using it's not getting removed.
you can use directly your element_value without [] after your array field, like below..
Tags.updateOne({tname: req.params.name}, { $pull: { your_array_field: req.params.id } } )
You have to find the specific tag by its "_id" and then remove the particular name from the "tname" array.
Tags.updateOne({ _id: req.params.id }, { $pull: { tname: req.params.name } })

Mongoose delete operation [duplicate]

Here is array structure
contact: {
phone: [
{
number: "+1786543589455",
place: "New Jersey",
createdAt: ""
}
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
Here I only know the mongo id(_id) and phone number(+1786543589455) and I need to remove that whole corresponding array element from document. i.e zero indexed element in phone array is matched with phone number and need to remove the corresponding array element.
contact: {
phone: [
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
I tried with following update method
collection.update(
{ _id: id, 'contact.phone': '+1786543589455' },
{ $unset: { 'contact.phone.$.number': '+1786543589455'} }
);
But it removes number: +1786543589455 from inner array object, not zero indexed element in phone array. Tried with pull also without a success.
How to remove the array element in mongodb?
Try the following query:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
It will find document with the given _id and remove the phone +1786543589455 from its contact.phone array.
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.
You can simply use $pull to remove a sub-document.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Collection.update({
_id: parentDocumentId
}, {
$pull: {
subDocument: {
_id: SubDocumentId
}
}
});
This will find your parent document against given ID and then will remove the element from subDocument which matched the given criteria.
Read more about pull here.
In Mongoose:
from the document:
To remove a document from a subdocument array we may pass an object
with a matching _id.
contact.phone.pull({ _id: itemId }) // remove
contact.phone.pull(itemId); // this also works
See Leonid Beschastny's answer for the correct answer.
To remove all array elements irrespective of any given id, use this:
collection.update(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from a specific document:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from all documents:
collection.updateMany(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
Given the following document in the profiles collection:
{ _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
The following operation will remove all items from the votes array that are greater than or equal to ($gte) 6:
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
After the update operation, the document only has values less than 6:
{ _id: 1, votes: [ 3, 5 ] }
If you multiple items the same value, you should use $pullAll instead of $pull.
In the question having a multiple contact numbers the same use this:
collection.update(
{ _id: id },
{ $pullAll: { 'contact.phone': { number: '+1786543589455' } } }
);
it will delete every item that matches that number. in contact phone
Try reading the manual.

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