Remove element in array by value | MongoDB | NodeJS - node.js

I have a mongoDB with a collection called users. Each element has a usrName, UserID, token, and then 3 arrays which are friends, incoming and outgoing (all of them relative to 'friends'). Im looking for something like:
db.collection('users').find({user:"some userID"}).update or something similar.
I expect the search for the correct element (by the id) and then remove some element from the 'incoming' array by the value. (its a string array)
here is the DB structure

The update would look something like this:
db.collection('users').updateOne({user: "some userID"}, {$pull: { incoming: "removeThisValue" }})
$pull takes an object which describes the array field you are pulling from and the criteria you are using to remove it. Since you said it's a string array you'll be looking for equality. But just note that you could also use mongodb operators such as $gte, $lt and others if working with different values.
If you were removing multiple values from the array at once:
db.collection('users').updateOne({user: "some userID"}, {$pull: { incoming: { $in: ["removeMe", "removeMeToo"] } }})
Its also worth noting if you ever have an array of documents you could also target specific key/values. Let's say a user has an array of hobbies where each hobby has "title" and "timePerWeek" keys. We could remove the hobby with the title of "Gaming" like this:
db.collection('users').updateOne({user: "some userID"}, {$pull: { hobbies: {title: "Gaming"} }})

Related

Appending objects to array field using $push and $each returns null (Mongoose)

Background:
I have an array (A) of new objects that need to be added to an array field in Mongoose. if successful it should print to screen the newly updated object but it prints null. I checked the database and confirmed it also does not update at all. I followed the docs to use the $push and $each modifiers here:
https://www.mongodb.com/docs/manual/reference/operator/update/each/
Desired Behaviour:
I would like each object in (A) to be added to the Array field, not (A) itself. Upon success, it should print to screen the newly updated object.
Attempted Approach:
let identifier={customer:{external_id:'12345'}}
let array = [{value:30},{value:30}]
User.findOneAndUpdate(identifier,{$push:{points:{$each:array}}},{new:true})
.then((result)=>{
console.log(result)
})
Attempted Resolutions:
I tested if the issue was with the identifier parameter, but it works fine when the update parameter does not have $each (i.e. it pushes the whole array (A) into the array field)
I thought about using $addToSet like in the solution below, but as you can see in the sample code above, I want to push all objects even if they are not unique:
Mongoose - Push objects into nested array
use dot "." notation for embedded field
let filter={'customer.external_id':'12345'}
let array = [{value:30},{value:30}];
let update = { $push: { points: { $each: array } } };
User.findOneAndUpdate(filter,update,{new: true})
.then((result)=>{
console.log(result)
})
It turns out the IDs did not match. There was no issue with the code. Sorry guys.

MongoDB nested array update multiple documents [duplicate]

I am trying to update a value in the nested array but can't get it to work.
My object is like this
{
"_id": {
"$oid": "1"
},
"array1": [
{
"_id": "12",
"array2": [
{
"_id": "123",
"answeredBy": [], // need to push "success"
},
{
"_id": "124",
"answeredBy": [],
}
],
}
]
}
I need to push a value to "answeredBy" array.
In the below example, I tried pushing "success" string to the "answeredBy" array of the "123 _id" object but it does not work.
callback = function(err,value){
if(err){
res.send(err);
}else{
res.send(value);
}
};
conditions = {
"_id": 1,
"array1._id": 12,
"array2._id": 123
};
updates = {
$push: {
"array2.$.answeredBy": "success"
}
};
options = {
upsert: true
};
Model.update(conditions, updates, options, callback);
I found this link, but its answer only says I should use object like structure instead of array's. This cannot be applied in my situation. I really need my object to be nested in arrays
It would be great if you can help me out here. I've been spending hours to figure this out.
Thank you in advance!
General Scope and Explanation
There are a few things wrong with what you are doing here. Firstly your query conditions. You are referring to several _id values where you should not need to, and at least one of which is not on the top level.
In order to get into a "nested" value and also presuming that _id value is unique and would not appear in any other document, you query form should be like this:
Model.update(
{ "array1.array2._id": "123" },
{ "$push": { "array1.0.array2.$.answeredBy": "success" } },
function(err,numAffected) {
// something with the result in here
}
);
Now that would actually work, but really it is only a fluke that it does as there are very good reasons why it should not work for you.
The important reading is in the official documentation for the positional $ operator under the subject of "Nested Arrays". What this says is:
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
Specifically what that means is the element that will be matched and returned in the positional placeholder is the value of the index from the first matching array. This means in your case the matching index on the "top" level array.
So if you look at the query notation as shown, we have "hardcoded" the first ( or 0 index ) position in the top level array, and it just so happens that the matching element within "array2" is also the zero index entry.
To demonstrate this you can change the matching _id value to "124" and the result will $push an new entry onto the element with _id "123" as they are both in the zero index entry of "array1" and that is the value returned to the placeholder.
So that is the general problem with nesting arrays. You could remove one of the levels and you would still be able to $push to the correct element in your "top" array, but there would still be multiple levels.
Try to avoid nesting arrays as you will run into update problems as is shown.
The general case is to "flatten" the things you "think" are "levels" and actually make theses "attributes" on the final detail items. For example, the "flattened" form of the structure in the question should be something like:
{
"answers": [
{ "by": "success", "type2": "123", "type1": "12" }
]
}
Or even when accepting the inner array is $push only, and never updated:
{
"array": [
{ "type1": "12", "type2": "123", "answeredBy": ["success"] },
{ "type1": "12", "type2": "124", "answeredBy": [] }
]
}
Which both lend themselves to atomic updates within the scope of the positional $ operator
MongoDB 3.6 and Above
From MongoDB 3.6 there are new features available to work with nested arrays. This uses the positional filtered $[<identifier>] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement:
Model.update(
{
"_id": 1,
"array1": {
"$elemMatch": {
"_id": "12","array2._id": "123"
}
}
},
{
"$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" }
},
{
"arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }]
}
)
The "arrayFilters" as passed to the options for .update() or even
.updateOne(), .updateMany(), .findOneAndUpdate() or .bulkWrite() method specifies the conditions to match on the identifier given in the update statement. Any elements that match the condition given will be updated.
Because the structure is "nested", we actually use "multiple filters" as is specified with an "array" of filter definitions as shown. The marked "identifier" is used in matching against the positional filtered $[<identifier>] syntax actually used in the update block of the statement. In this case inner and outer are the identifiers used for each condition as specified with the nested chain.
This new expansion makes the update of nested array content possible, but it does not really help with the practicality of "querying" such data, so the same caveats apply as explained earlier.
You typically really "mean" to express as "attributes", even if your brain initially thinks "nesting", it's just usually a reaction to how you believe the "previous relational parts" come together. In reality you really need more denormalization.
Also see How to Update Multiple Array Elements in mongodb, since these new update operators actually match and update "multiple array elements" rather than just the first, which has been the previous action of positional updates.
NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.
However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.
So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.
See also positional all $[] which also updates "multiple array elements" but without applying to specified conditions and applies to all elements in the array where that is the desired action.
I know this is a very old question, but I just struggled with this problem myself, and found, what I believe to be, a better answer.
A way to solve this problem is to use Sub-Documents. This is done by nesting schemas within your schemas
MainSchema = new mongoose.Schema({
array1: [Array1Schema]
})
Array1Schema = new mongoose.Schema({
array2: [Array2Schema]
})
Array2Schema = new mongoose.Schema({
answeredBy": [...]
})
This way the object will look like the one you show, but now each array are filled with sub-documents. This makes it possible to dot your way into the sub-document you want. Instead of using a .update you then use a .find or .findOne to get the document you want to update.
Main.findOne((
{
_id: 1
}
)
.exec(
function(err, result){
result.array1.id(12).array2.id(123).answeredBy.push('success')
result.save(function(err){
console.log(result)
});
}
)
Haven't used the .push() function this way myself, so the syntax might not be right, but I have used both .set() and .remove(), and both works perfectly fine.

How to return all matches from a list in MongoDB via Node.js application? [duplicate]

I have a process that returns a list of String MongoDB ids,
[512d5793abb900bf3e20d012, 512d5793abb900bf3e20d011]
And I want to fire a single query to Mongo and get the matching documents back in the same order as the list.
What is the shell notation to do this?
After converting the strings into ObjectIds, you can use the $in operator to get the docs in the list. There isn't any query notation to get the docs back in the order of your list, but see here for some ways to handle that.
var ids = ['512d5793abb900bf3e20d012', '512d5793abb900bf3e20d011'];
var obj_ids = ids.map(function(id) { return ObjectId(id); });
db.test.find({_id: {$in: obj_ids}});
This works fine for me in Robo 3T. No need to create any object and just use the list of ids.
db.getCollection('my_collection').find({'_id':{$in:['aa37ba96']}})
// categoryId comma separated "5c875c27d131b755d7abed86,5c875b0ad131b755d7abed81" in request
var ids= req.body.categoryId.split(',');
db.test.find({ categoryId: { $in: ids } });
If your final purpose is to get the document with the order by your pre-get ids list, you can just convert the query result into mapping(id as key, doc as value) , and then traverse the ids list to get the doc.

Query documents in an array in mongodb using node.js

How to query all documents present inside an Array which itself is present in a MongoDB collection under Node.js.
For example: I have a DB with a structure:
{
"name1":[{"height":"5.5"},
{"weight":"57"}],
"name2":[{"height":"6.1"},
{"weight":"74"}]
}
What query should I make to get all the documents( i.e. height, weight) of the array "name1"
Output should be :
{
{ "height":"5.5"}
{"weight":"57"}
}
My suggestion would be to reorganise the collection so each document has a key of name and key of physicalattributes e.g.
{
'name' : 'name1',
'physAttr': ['height': heightvalue,
'weight': weightvalue]
}
then suppose you wanted to find all documents with height 5.5, the query would be trivial
db.collection.find({ 'physAttr.height': 5.5 })
As described in the question each document in the collection has a different schema from the others (different name key for every document) making query operations difficult.

need guidance on node.js multilingual presentation

I am new to node (v0.10) stack.
I am trying to achieve the following:
I have (hopefully) multilingual articles in the latest MongoDB such as:
_id
...more fields...
text: [
{lang: 'en', title: 'some title', body: 'body', slug: 'slug'},
....
]
Everytime I display an article in specific language I query as follows:
var query = Model.findOne({'text.slug': slug});
query.exec(function(err, doc){
async.each(doc.text, function(item, callback){
if (item.lang == articleLang) {
//populate the article to display
}
});
res.render('view', {post:articleToDisplay});
});
Slug is unique for each language!
The problem I have is that mongo will return the whole doc with all subdocs and not just the subdoc I searched for. Now I have to choose to iterate over all subdocs and display the appropriate one on client side or use async.each on the server to get the subdoc I need and only send to the views that one. I am doing it with async on the server. Is that OK? Also async iterates asynchronously but node still waits for the whole loop to finish and then renders the view. Am I missing anything thinking that the user is actually blocked until the async.each finishes? I am still trying to wrap my head around this asynchronous execution. Is there a way I can possibly improve how I manage this code? It seems to be quite standard procedure with subdocs!
Thanks in advance for all your help.
To achieve what you want, you need to make use of the aggregation pipeline. Using a simple findOne() would not be of help,
since you would then have to redact sub documents in your code rather than allowing mongodb to do it. find() and findOne() return the whole document when
a document matches the search criteria.
In the aggregation pipleline you could use the $unwind and $match operators to achieve this.
$unwind:
Deconstructs an array field from the input documents to output a
document for each element. Each output document is the input document
with the value of the array field replaced by the element.
First unwind the document based on the text values array.
$match:
Filters the documents to pass only the documents that match the
specified condition(s) to the next pipeline stage.
Then use the $match operator to match the appropriate documents.
db.Model.aggregate([
{$unwind:"$text"},
{$match:{"text.slug":slug,"text.lang":articleLang}}
])
Doing this would return you only one document with its text field containing only one object. such as: (Note that the text field in the output is not an array)
{ "_id" : ... ,.., "text" : { "slug" : "slug", "lang" : "en" ,...} }

Resources