I am trying to push some data to a nested array. My data is like:
{a:[{x:[{'y':'z'}]}]}
I am trying to push values into x array. I tried this way, it works=>
{ "$push": { "a.0.x": data}}
But, is there a way where I can pass the value 0 dynamically. I tried the following, but it doesn't work.
{ $push: { "a.$.x": {
$each: [data],
$position: 1
}}}
I didn't find a mongoose way. But, here is a way I did it by passing index to the object property.
var i = "1";
var obj = { ["prop"+i+"Name"]: "response" };
console.log(obj);
Related
I just can't figure out the query and even if it's allowed to write a single query to push 4 different objects into 4 different arrays deeply nested inside the user Object.
I receive PATCH request from front-end which's body looks like this:
{
bodyweight: 80,
waist: 60,
biceps: 20,
benchpress: 50,
timestamp: 1645996168125
}
I want to create 4 Objects and push them into user's data in Mongo Atlas
{date:1645996168125, value:80} into user.stats.bodyweight <-array
{date:1645996168125, value:60} into user.stats.waist <-array
...etc
I am trying to figure out second argument for:
let user = await User.findOneAndUpdate({id:req.params.id}, ???)
But i am happy to update it with any other mongoose method if possible.
PS: I am not using _id given by mongoDB on purpose
You'll want to use the $push operator. It accepts paths as the field names, so you can specify a path to each of the arrays.
I assume the fields included in your request are fixed (the same four property names / arrays for every request)
let user = await User.findOneAndUpdate(
{ id: req.params.id },
{
$push: {
"stats.bodyweight": {
date: 1645996168125,
value: 80,
},
"stats.waist": {
date: 1645996168125,
value: 60,
},
// ...
},
}
);
If the fields are dynamic, use an object and if conditions, like this:
const update = {};
if ("bodyweight" in req.body) {
update["stats.bodyweight"] = {
date: 1645996168125,
value: 80,
};
}
// ...
let user = await User.findOneAndUpdate(
{ id: req.params.id },
{
$push: update,
}
);
The if condition is just to demonstrate the principle, you'll probably want to use stricter type checking / validation.
try this:
await User.findOneAndUpdate(
{id:req.params.id},
{$addToSet:
{"stats.bodyweight":{date:1645996168125, value:80} }
}
)
I have a mongoose schema of mixed types like so:
const user = mongoose.Schema({
...
"links" : []
...
After populating this schema, I ended up with data like so:
[
[
{
"step1": "post-url-google", // This field is unique for each entry
"step2": {
"title": "Heading 1",
"likes": 4
}
},
],
[
{
"step1": "post-url-microsoft",
"step2": {
"title": "Heading 1",
"likes": 1
}
},
{
"step1": "post-url-apple",
"step2": {
"title": "Heading 2",
"likes": 6 // I want to update this to 7
}
}
]
]
What I want to achieve is to update the "step1": "post-url-apple" field from 6 to have a likes of 7
So I tried using the User.save() function like so:
let user = await User.findOne({"_id" : "some_id"})
user.links[1].some(object => {
if (object.step1 === "post-url-apple") {
object.step2.likes = 7
(async function (){
user.save() // I also did error handling
})()
return
}
})
This method works fine and the user gets updated but it keeps throwing ParallelSaveError possibly because I am calling the save() function in parallel on the same user instance in some other parts of my code.
So I decided to use the User.findOneAndUpdate() method, but my queries keep failing when using the mongodb dot notation $[<identifier>], obviously because I don't know how to use it properly.
Like so:
let update = {
"$set" : {
"links.$[index1].$[index2].step2.likes" : 7,
}
}
let conditions = {
arrayFilters : [
{"index1" : 1},
{"index2.step1" : "post-url-apple"}
]
}
try {
let result = await Users.findOneAndUpdate({"_id" : "some_id"}, update, conditions)
console.log(result)
} catch (err) {
console.log(err)
}
For all good reasons, I'm not hiting the catch block but the update was equally not successful
How do I achieve updating the "step1": "post-url-apple" likes field to 7 using findOneAndUpdate?
Thank you.
In arrayFilters you should define the conditions to be applied to all the array elements, not the index
If you are sure, you always update the second array element (index = 1) of the outer array, then you can use the dot notation for the outer array, and for the inner array you can use the array filters to get the element that has step1 = 'post-url-apple'
your code may look something like that
let update = {
"$set" : {
'links.1.$[item].step2.likes': 7 // here we used links.1 to access the second element of the outer array
}
}
let conditions = {
arrayFilters : [
{ 'item.step1' : 'post-url-apple' } // item here is the element in the inner array that has step1 = post-url-apple
]
}
then do your update query
hope it helps
Desired Behaviour
Pull a range of objects from an array of objects and push them back to the array at a new index.
For example, pull objects from the array where their index is between 0 and 2, and push them back to the array at position 6.
For reference, in jQuery, the desired behaviour can be achieved with:
if (before_or_after === "before") {
$("li").eq(new_position).before($("li").slice(range_start, range_end + 1));
} else if (before_or_after === "after") {
$("li").eq(new_position).after($("li").slice(range_start, range_end + 1));
}
jsFiddle demonstration
Schema
{
"_id": ObjectId("*********"),
"title": "title text",
"description": "description text",
"statements": [
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
}]
}
What I've Tried
I am able to reposition a single object in an array of objects with the code below.
It uses pull to remove the object from the array and push to add it back to the array at a new position.
In order to do the same for a range of objects, I think I just need to modify the $pull and $push variables but:
I can't figure out how to use $slice in this context, either as a projection or an aggregation, in a $pull operation
Because I can't figure out the first bit, I don't know how to attempt the second bit - the $push operation
// define the topic_id to search for
var topic_id = request_body.topic_id;
// make it usable as a search query
var o_id = new ObjectID(topic_id);
// define the statement_id to search for
var statement_id = request_body.statement_id;
// define new position
var new_position = Number(request_body.new_position);
// define old position
var old_position = Number(request_body.old_position);
// define before or after (this will be relevant later)
// var before_or_after = request_body.before_or_after;
// define the filter
var filter = { _id: o_id };
// define the pull update - to remove the object from the array of objects
var pull_update = {
$pull: {
statements: { id: statement_id } // <----- how do i pull a range of objects here
}
};
// define the projection so that only the 'statements' array is returned
var options = { projection: { statements: 1 } };
try {
// perform the pull update
var topic = await collection.findOneAndUpdate(filter, pull_update, options);
// get the returned statement object so that it can be inserted at the desired index
var returned_statement = topic.value.statements[old_position];
// define the push update - to add the object back to the array at the desired position
var push_update = {
$push: {
statements: {
$each: [returned_statement],
$position: new_position
}
} // <----- how do i push the range of objects back into the array here
};
// perform the push update
var topic = await collection.findOneAndUpdate(filter, push_update);
}
Environments
##### local
$ mongod --version
db version v4.0.3
$ npm view mongodb version
3.5.9
$ node -v
v10.16.3
$ systeminfo
OS Name: Microsoft Windows 10 Home
OS Version: 10.0.18363 N/A Build 18363
##### production
$ mongod --version
db version v3.6.3
$ npm view mongodb version
3.5.9
$ node -v
v8.11.4
RedHat OpenShift Online, Linux
Edit
Gradually, figuring out parts of the problem, I think:
Using the example here, the following returns objects from array with index 0 - 2 (ie 3 objects):
db.topics.aggregate([
{ "$match": { "_id": ObjectId("********") } },
{ "$project": { "statements": { "$slice": ["$statements", 0, 3] }, _id: 0 } }
])
Not sure how to use that in a pull yet...
I also looked into using $in (even though i would prefer to just grab a range of objects than have to specify each object's id), but realised it does not preserve the order of the array values provided in the results returned:
Does MongoDB's $in clause guarantee order
Here is one solution to re-ordering results from $in in Node:
https://stackoverflow.com/a/34751295
Here an example with mongo 3.5
const mongo = require('mongodb')
;(async function (params) {
const client = await mongo.connect('mongodb://localhost:27017')
const coll = client.db('test').collection('test')
const from0to99 = Array(100).fill('0').map((_, i) => String(i))
const from5To28 = Array(24).fill('0').map((_, i) => String(i + 5))
const insert = { statements: from0to99.map(_ => ({ id: _ })) }
await coll.insertOne(insert)
const all100ElementsRead = await coll.findOneAndUpdate(
{ _id: insert._id },
{
$pull: {
statements: {
id: { $in: from5To28 }
}
}
},
{ returnOriginal: true }
)
/**
* It shows the object with the desired _id BEFORE doing the $pull
* You can process all the old elements as you wish
*/
console.log(all100ElementsRead.value.statements)
// I use the object read from the database to push back
// since I know the $in condition, I must filter the array returned
const pushBack = all100ElementsRead.value.statements.filter(_ => from5To28.includes(_.id))
// push back the 5-28 range at position 72
const pushed = await coll.findOneAndUpdate(
{ _id: insert._id },
{
$push: {
statements: {
$each: pushBack,
$position: 72 // 0-indexed
}
}
},
{ returnOriginal: false }
)
console.log(pushed.value.statements) // show all the 100 elements
client.close()
})()
This old issue helped
if you want "desired behavior" when mutating arrays ,
you add these to checklist:
array.length atleast==7 if you want to add ,splice at 6
creates a new array if u use concat
mutates orignal if used array.push or splice or a[a.length]='apple'
USE slice() to select between incex1 to index2.
or run a native for loop to select few elements of array or
apply a array.filter() finction.
once you select your elements which needed to be manupulated you mentioned you want to add it to end. so this is the method below.
about adding elements at end:
CONCAT EXAMPLE
const original = ['🦊']; //const does not mean its immutable just that it cant be reassigned
let newArray;
newArray = original.concat('🦄');
newArray = [...original, '🦄'];
// Result
newArray; // ['🦊', '🦄']
original; // ['🦊']
SPLICE EXAMPLE:
const zoo = ['🦊', '🐮'];
zoo.splice(
zoo.length, // We want add at the END of our array
0, // We do NOT want to remove any item
'🐧', '🐦', '🐤', // These are the items we want to add
);
console.log(zoo); // ['🦊', '🐮', '🐧', '🐦', '🐤']
This is a Controller in which I'm trying to catch multiple candidates id(ObjectId) and try to store it in the database in the array Candidates. But data is not getting pushed in Candidates column of Array type.
routes.post('/Job/:id',checkAuthenticated,function(req,res){
var candidates=req.body.candidate;
console.log(candidates);
Job.update({_id:req.params.id},{$push:{Appliedby : req.user.username}},{$push:{Candidates:{$each:
candidates}}}
});
Console screens output
[ '5eb257119f2b2f0b4883558b', '5eb2ae1cff3ae7106019ad7e' ] //candidates
you have to do all the update operations ($set, $push, $pull, ...) in one object, and this object should be the second argument passed to the update method after the filter object
{$push:{Appliedby : req.user.username}},{$push:{Candidates:{$each: candidates}}
this will update the Appliedby array only, as the third object in update is reserved for the options (like upsert, new, ....)
you have to do something like that
{ $push: { Appliedby: req.user.username, Candidates: { $each: candidates } } }
then the whole query should be something like that
routes.post('/Job/:id', checkAuthenticated, function (req, res) {
var candidates = req.body.candidate;
console.log(candidates);
Job.update(
{ _id: req.params.id }, // filter part
{ $push: { Appliedby: req.user.username, Candidates: { $each: candidates } } } // update part in one object
)
});
this could do the trick I guess, hope it helps
I have array of Ids and I want to get record from mongodb collection against the array. But now its gives data but this will not maintain the order with array order.
var array = ['gshbfewueinbsdeipp38x', 'ou38xvbad084jgmblaxzp', 'plaz3tc61bs0cmzpIKtq', '1az94ocnmzb36GH92skaw63bx'];
In my code:
db.collection("fruits").aggregate([{'$match':{_id:array}}])
Actual Result:
[{_id: ObjectId('ou38xvbad084jgmblaxzp'),name:"Apple"},{_id: ObjectId('1az94ocnmzb36GH92skaw63bx'),name:"mango"},{_id: ObjectId('plaz3tc61bs0cmzpIKtq'),name:"orange"},{_id: ObjectId('gshbfewueinbsdeipp38x'),name:"Banana"}]
Expected Result:
[{_id: ObjectId('gshbfewueinbsdeipp38x'),name:"Banana"},{_id: ObjectId('ou38xvbad084jgmblaxzp'),name:"Apple"},{_id: ObjectId('plaz3tc61bs0cmzpIKtq'),name:"orange"},{_id: ObjectId('1az94ocnmzb36GH92skaw63bx'),name:"mango"}]
This will not get the same order of above array. Please help me on above.
Thanks
Please try this:-
var array = ['gshbfewueinbsdeipp38x', 'ou38xvbad084jgmblaxzp','plaz3tc61bs0cmzpIKtq','1az94ocnmzb36GH92skaw63bx'];
projection = {
"$addFields" : {
"__custom_order" : { "$indexOfArray" : [ array, "$_id" ] }
}
},
sort = { "$sort" : { "__custom_order" : 1 } };
db.collection("fruits").aggregate([ projection, sort]);