mongodb $pull doesn't work with array in subdocument - node.js

I have a problem with an update with MongoDB.
My schema look like this:
Project: {
_id: ObjectId(pro_id)
// some data
dashboard_group: [
{
_id: ObjectId(dgr_id)
dgr_name: "My Dashboard"
dgr_tasks: [
id1,
id2,
...
]
},
// other dashboards
]
}
I want to remove id2 but the $pull operator seems not work. Mongo return me this :
result: {
lastErrorObject: {
n: 1,
updatedExisting: true
},
ok: 1
}
This is my request:
db.Project.findOneAndUpdate({
"dashboard_group._id": dgr_id
}, {
$pull: {
"dashboard_group.$.dgr_tasks": id2
}
});
dgr_id is already cast to ObjectId before the query and I verified the value that I want to remove.
Can anyone have an idea ?

You will need to select the particular array element using "$elemMatch" like this
Query : {"dashboard_group":{"$elemMatch":{dgr_name:"My Dashboard"}}}
Update : {$pull:{"dashboard_group.$.dgr_tasks":"id2"}}

So, I found a solution with the $[] identifier. It's not its basic utility, but it fit to my case.
A task ID cannot be at 2 location, it belongs to 1 and only 1 dashboard. So if you make a request like :
db.Project.findOneAndModify({
"dashboard_group._id": dgr_id
}, {
$pull: {
"dashboard_group.$[].dgr_tasks": id2
}
});
Mongo will remove all value that match id2. Without the {multi: true} option, it will make the update 1 time, and my item is indeed remove from my nested array.

Related

find the document which equals any one of the array element query with a non array field

mongo db schema variable
status:{
type: Number,
enum: [0,1,2,3,4,5], //0-NOT ACCEPTED,1-COMPLETED,2-PENDING
default: 0
}
status stored in db like 0 or 1 or 2. status search with user selection is array of datas like
status: {1,2}
how to get the documents which has any one the of the array element. I can't do a static search because array size can change every time
// if(status){
// query = {
// ...query,
// "status": status
// }
// }
console.log(body_status);
if(body_status){
query = {
...query,
"status": {"$in":body_status}
}
}
this works for me.
I don't know if I've understand the question but I think you want something like this:
db.collection.find({
"status": {
"$in": [
1,
2,
4
]
}
})
Example here
Please check if it works as expected or not and in this case update the question with more information.
Or maybe you want something like this:
db.collection.find({
"status": 1
})

how to remove object in array by index mongodb / mongoose [duplicate]

In the following example, assume the document is in the db.people collection.
How to remove the 3rd element of the interests array by it's index?
{
"_id" : ObjectId("4d1cb5de451600000000497a"),
"name" : "dannie",
"interests" : [
"guitar",
"programming",
"gadgets",
"reading"
]
}
This is my current solution:
var interests = db.people.findOne({"name":"dannie"}).interests;
interests.splice(2,1)
db.people.update({"name":"dannie"}, {"$set" : {"interests" : interests}});
Is there a more direct way?
There is no straight way of pulling/removing by array index. In fact, this is an open issue http://jira.mongodb.org/browse/SERVER-1014 , you may vote for it.
The workaround is using $unset and then $pull:
db.lists.update({}, {$unset : {"interests.3" : 1 }})
db.lists.update({}, {$pull : {"interests" : null}})
Update: as mentioned in some of the comments this approach is not atomic and can cause some race conditions if other clients read and/or write between the two operations. If we need the operation to be atomic, we could:
Read the document from the database
Update the document and remove the item in the array
Replace the document in the database. To ensure the document has not changed since we read it, we can use the update if current pattern described in the mongo docs
You can use $pull modifier of update operation for removing a particular element in an array. In case you provided a query will look like this:
db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})
Also, you may consider using $pullAll for removing all occurrences. More about this on the official documentation page - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull
This doesn't use index as a criteria for removing an element, but still might help in cases similar to yours. IMO, using indexes for addressing elements inside an array is not very reliable since mongodb isn't consistent on an elements order as fas as I know.
in Mongodb 4.2 you can do this:
db.example.update({}, [
{$set: {field: {
$concatArrays: [
{$slice: ["$field", P]},
{$slice: ["$field", {$add: [1, P]}, {$size: "$field"}]}
]
}}}
]);
P is the index of element you want to remove from array.
If you want to remove from P till end:
db.example.update({}, [
{ $set: { field: { $slice: ["$field", 1] } } },
]);
Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.
For instance, in order to update an array by removing an element at a given index:
// { "name": "dannie", "interests": ["guitar", "programming", "gadgets", "reading"] }
db.collection.update(
{ "name": "dannie" },
[{ $set:
{ "interests":
{ $function: {
body: function(interests) { interests.splice(2, 1); return interests; },
args: ["$interests"],
lang: "js"
}}
}
}]
)
// { "name": "dannie", "interests": ["guitar", "programming", "reading"] }
$function takes 3 parameters:
body, which is the function to apply, whose parameter is the array to modify. The function here simply consists in using splice to remove 1 element at index 2.
args, which contains the fields from the record that the body function takes as parameter. In our case "$interests".
lang, which is the language in which the body function is written. Only js is currently available.
Rather than using the unset (as in the accepted answer), I solve this by setting the field to a unique value (i.e. not NULL) and then immediately pulling that value. A little safer from an asynch perspective. Here is the code:
var update = {};
var key = "ToBePulled_"+ new Date().toString();
update['feedback.'+index] = key;
Venues.update(venueId, {$set: update});
return Venues.update(venueId, {$pull: {feedback: key}});
Hopefully mongo will address this, perhaps by extending the $position modifier to support $pull as well as $push.
I would recommend using a GUID (I tend to use ObjectID) field, or an auto-incrementing field for each sub-document in the array.
With this GUID it is easy to issue a $pull and be sure that the correct one will be pulled. Same goes for other array operations.
For people who are searching an answer using mongoose with nodejs. This is how I do it.
exports.deletePregunta = function (req, res) {
let codTest = req.params.tCodigo;
let indexPregunta = req.body.pregunta; // the index that come from frontend
let inPregunta = `tPreguntas.0.pregunta.${indexPregunta}`; // my field in my db
let inOpciones = `tPreguntas.0.opciones.${indexPregunta}`; // my other field in my db
let inTipo = `tPreguntas.0.tipo.${indexPregunta}`; // my other field in my db
Test.findOneAndUpdate({ tCodigo: codTest },
{
'$unset': {
[inPregunta]: 1, // put the field with []
[inOpciones]: 1,
[inTipo]: 1
}
}).then(()=>{
Test.findOneAndUpdate({ tCodigo: codTest }, {
'$pull': {
'tPreguntas.0.pregunta': null,
'tPreguntas.0.opciones': null,
'tPreguntas.0.tipo': null
}
}).then(testModificado => {
if (!testModificado) {
res.status(404).send({ accion: 'deletePregunta', message: 'No se ha podido borrar esa pregunta ' });
} else {
res.status(200).send({ accion: 'deletePregunta', message: 'Pregunta borrada correctamente' });
}
})}).catch(err => { res.status(500).send({ accion: 'deletePregunta', message: 'error en la base de datos ' + err }); });
}
I can rewrite this answer if it dont understand very well, but I think is okay.
Hope this help you, I lost a lot of time facing this issue.
It is little bit late but some may find it useful who are using robo3t-
db.getCollection('people').update(
{"name":"dannie"},
{ $pull:
{
interests: "guitar" // you can change value to
}
},
{ multi: true }
);
If you have values something like -
property: [
{
"key" : "key1",
"value" : "value 1"
},
{
"key" : "key2",
"value" : "value 2"
},
{
"key" : "key3",
"value" : "value 3"
}
]
and you want to delete a record where the key is key3 then you can use something -
db.getCollection('people').update(
{"name":"dannie"},
{ $pull:
{
property: { key: "key3"} // you can change value to
}
},
{ multi: true }
);
The same goes for the nested property.
this can be done using $pop operator,
db.getCollection('collection_name').updateOne( {}, {$pop: {"path_to_array_object":1}})

How to update a field using its previous value in MongoDB/Mongoose

For example, I have some documents that look like this:
{
id: 1
name: "foo"
}
And I want to append another string to the current name field value.
I tried the following using Mongoose, but it didn't work:
Model.findOneAndUpdate({ id: 1 }, { $set: { name: +"bar" } }, ...);
Edit:
From Compatibility Changes in MongoDB 3.6:
MongoDB 3.6.1 deprecates the snapshot query option.
For MMAPv1, use hint() on the { _id: 1} index instead to prevent a cursor from returning a document more than once if an intervening write operation results in a move of the document.
For other storage engines, use hint() with { $natural : 1 } instead.
Original 2017 answer:
You can't refer to the values of the document you want to update, so you will need one query to retrieve the document and another one to update it. It looks like there's a feature request for that in OPEN state since 2016.
If you have a collection with documents that look like:
{ "_id" : ObjectId("590a4aa8ff1809c94801ecd0"), "name" : "bar" }
Using the MongoDB shell, you can do something like this:
db.test.find({ name: "bar" }).snapshot().forEach((doc) => {
doc.name = "foo-" + doc.name;
db.test.save(doc);
});
The document will be updated as expected:
{ "_id" : ObjectId("590a4aa8ff1809c94801ecd0"), "name": "foo-bar" }
Note the .snapshot() call.
This ensures that the query will not return a document multiple times because an intervening write operation moves it due to the growth in document size.
Applying this to your Mongoose example, as explained in this official example:
Cat.findById(1, (err, cat) => {
if (err) return handleError(err);
cat.name = cat.name + "bar";
cat.save((err, updatedCat) => {
if (err) return handleError(err);
...
});
});
It's worth mentioning that there's a $concat operator in the aggregation framework, but unfortunately you can't use that in an update query.
Anyway, depending on what you need to do, you can use that together with the $out operator to save the results of the aggregation to a new collection.
With that same example, you will do:
db.test.aggregate([{
$match: { name: "bar" }
}, {
$project: { name: { $concat: ["foo", "-", "$name"] }}
}, {
$out: "prefixedTest"
}]);
And a new collection prefixedTest will be created with documents that look like:
{ "_id" : ObjectId("XXX"), "name": "foo-bar" }
Just as a reference, there's another interesting question about this same topic with a few answers worth reading: Update MongoDB field using value of another field
If this is still relevant, I have a solution for MongoDB 4.2.
I had the same problem where "projectDeadline" fields of my "project" documents were Array type (["2020","12","1"])
Using Robo3T, I connected to my MongoDB Atlas DB using SRV link. Then executed the following code and it worked for me.
Initial document:
{
_id : 'kjnolqnw.KANSasdasd',
someKey : 'someValue',
projectDeadline : ['2020','12','1']
}
CLI Command:
db
.getCollection('mainData')
.find({projectDeadline: {$not: {$eq: "noDeadline"}}})
.forEach((doc) => {
var deadline = doc.projectDeadline;
var deadlineDate = new Date(deadline);
db
.mainData
.updateOne({
_id: doc._id},
{"$set":
{"projectDeadline": deadlineDate}
}
)}
);
Resulting document:
{
_id : 'kjnolqnw.KANSasdasd',
someKey : 'someValue',
projectDeadline : '2020-12-01 21:00:00.000Z'
}

$addToSet aggregation and multiple arrays

I have this collection :
[
{
_id: ObjectId('myId1'),
probes: ['id_probe_1', 'id_probe_2']
},
{
_id: ObjectId('myId2'),
probes: ['id_probe_1', 'id_probe_3']
}
]
I want to get an array like this :
['id_probe_1', 'id_probe_2', 'id_probe_3']
So I try this request (from nodeJS driver) :
let find = [
{
$match: {
_id: {
$in: [new ObjectId('miId1'), new ObjectId('myId2')]
}
}
},
{
$group: {
_id: null,
probes: {
$addToSet: {
$each: '$probes'
}
}
}
}
];
This doesn't work, give me this error :
invalid operator '$each'
From the doc, they mention that it will appends the whole array as a single element.
If the value of the expression is an array, $addToSet appends the whole array as a single element.
But they don't say how to have an unique array. So I use the $each operator like this page indicates (I don't really know what's the difference...)
Is there a way to make this work ?
Thanks !
insert $unwind before $group
{$unwind:"$probes"},
then remove $each
Why don't you try distinct operation? In mongo shell, db.col.distinct('probs'); you can try the distinct function in nodejs mongo driver.

In Mongo, Increment and return value -- possible in 1 call?

In Mongo, is it possible to increase and get the result of the increment?
collection.update({id: doc_id}, {$inc: {view_count: 1}});
I tried to output the result of that statement (in node) and I got the following:
{ _id: 1,
_state: undefined,
_result: undefined,
_subscribers: [] }
You can use findAndModify. Add the new:true option.
According to the docs:
The findAndModify command modifies and returns a single document. By default, the returned document does not include the modifications made on the update. To return the document with the modifications made on the update, use the new option.
You could do the following:
db.collection.findAndModify(
query: {_id: doc_id},
update: { $inc: { view_count :1 } },
new: true,
)
If you canĀ“t find the findAndModify method to use on your collection,you can use the findOneAndUpdate method.
Here is how to use:
The following code finds the first document where name : R. Stiles and increments the score by 5:
const result = await db.grades.findOneAndUpdate(
{ "name" : "R. Stiles" }, //also you can search for id
{ $inc: { "points" : 5 } }
)
The code returns the original document before the update inside the "value" propety:
{ _id: 6319, name: "R. Stiles", "points" : 0,... } // result.value returns document before update, but in the db it changued
If you want get the document uploaded, you has to set "returnNewDocument" to true, so the operation would return the updated document instead.
I hope it works for you.
source: https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/

Resources