How does $unset work? - mongodb

In the mongo documentation unsetting a field is done with $unset. I can't quite grasp how it works, but it seems like it should be simple.
The following operation uses the $unset operator to remove the tags field:
db.books.update( { _id: 1 }, { $unset: { tags: 1 } } )
My confusion arises when setting what to unset. What is the value 1 for in the $unset clause?

As per the $unset documentation :-
The $unset operator deletes a particular field.
Syntax : { $unset: { <field1>: "", ... } }
The specified value in the $unset expression (i.e. "") does not impact the operation.
If the field does not exist, then $unset does nothing (i.e. no operation).
So you can use
db.books.update( { _id: 1 }, { $unset: { tags: 1 } } )
OR
db.books.update( { _id: 1 }, { $unset: { tags: 0 } } )
OR
db.books.update( { _id: 1 }, { $unset: { tags: "" } } )
All the above queries will delete tags field.
Hope your doubt is clear now.

{$unset : { tags : 1 } } will clear the field tags from the document. The value 1 is just to tell that, clear this field tags from the document.
If you want to clear multiple fields, you need to write {$unset : { tags : 1, randomField : 1} } and like that.
You can refer official documentation of $unset for further info.
According to the documentation:
The $unset operator deletes a particular field. Consider the
following syntax:
{ $unset: { field1: "", ... } }
The specified value in the $unset
expression (i.e. "") does not impact the operation.
If the field does not exist, then $unset does nothing (i.e. no
operation).
When used with $ to match an array element, $unset replaces the
matching element with null rather than removing the matching element
from the array.

The $unset operator deletes a particular field.

Related

Does MongoDB's $elemMatch projection guarantee the returned element is the same one that was matched in the query?

Let's say I have a collection with this single document:
{
"_id" : ObjectId("…"),
"cartId" : "61",
"items" : [
{
"prodType" : "hardware",
"prod" : "screwdriver",
"checked": false
},
{
"prodType" : "hardware",
"prod" : "hammer",
"checked": false
},
{
"prodType" : "decor",
"prod" : "vase",
"checked": false
}
]
}
And I want to do findAndModify to find any hardware product and modify its checked field. Then it will look like this:
db.col.findAndModify({
query: {
items: {
$elemMatch: {
prodType: "hardware"
}
}
},
update: {
$set: {
"items.$.checked": true
}
}
})
Okay, but this isn't the whole story. findAndModify will return the whole matched document, and I want to project specifically the array item that was matched (and also modified), so I'll add a fields section to my query:
db.col.findAndModify({
query: {
items: {
$elemMatch: {
prodType: "hardware"
}
}
},
update: {
$set: {
"items.$.checked": true
}
},
fields: {
items: {
$elemMatch: {
prodType: "hardware"
}
}
}
})
And now to the question: does MongoDB guarantee that the returned array item from my query is the exact same one that was matched (and modified) in the update section even though we have two items matching the criteria?
YES. It will return only the first sub-document that matched your criteria and was modified in the update section as shown here
According to the official docs, then yes - the projected array element is the exact one that was modified using the same one modified by the positional operator.
$ (update) states:
the positional $ operator acts as a placeholder for the first element that matches the query document
and $elemMatch (projection) states:
The $elemMatch operator limits the contents of an <array> field from the query results to contain only the first element matching the $elemMatch condition
They both apply to the first array element so it directly implies that the modified array element is the one that is projected

Increment all array elements

I have a document like this:
{
"_id" : ObjectId("534527e489e46c16787bda0f"),
"packages" : [
{
number: 1
},
{
number: 2
}
]
}
I want to increment all packages.number at once. Is it possible?
I tried this db.collection.update({}, {$inc: {"packages.number": 1}}) but didn't work.
No it isn't possible and the reason is given in the use of the positional $ operator. And this says that even when used you will only ever get the first match found.
So something you did not do was try to match something in the array, in which you get a value in the positional operator to use as an index as in:
db.collection.update(
{ "packages.number": 1 },
{ "$inc": { "packages.$.number": 1 } }
)
But in general you cannot update all members of an array at once, you can only retrieve the document and then update in your client code before writing back.
You can do like this.
// Store document in Array
temp = db.collection.find().toArray()
// Iterate Array and update the number
temp[0].packages.forEach(function(x){x.number += 1})
// Save the updated array in Collection
db.collection.save(temp[0])
This will update the required document
In the shell you can run a command like this using cursor.forEach to loop through the array elements and increment each:
> db.collection.find().forEach(function(doc){
... doc.packages.forEach(function(elem){elem.number+=1});
... db.collection.update({_id:doc._id}, {$set:{packages:doc.packages}})
... })
this is possible,
since mongodb allows multiple fields increment:
{ $inc: { field1: amount1, field2: amount2, ... } }
and arrays are treated as different fields:
"packages.0.number" // which is 1
"packages.1.number" // 2
you can do
db.collection.findOneAndUpdate(
{ "_id" : ObjectId("534527e489e46c16787bda0f") },
{
"$inc": {
"packages.0.number": 1, // which is 1
"packages.1.number": 1 // 2
//... and so on, you might want a loop to build the updates
}
}
)
Since version 3.6 you can use the $[] operator:
db.collection.update({}, {$inc: {"packages.$[].number": 1}})
Note that if you would like to update all array elements in multiple documents you need to add the { multi: true } optional argument:
db.collection.update({}, {$inc: {"packages.$[].number": 1}}, { multi: true })
See the documentation for more details:
https://docs.mongodb.com/manual/reference/operator/update/positional-all/#update-all-documents-in-an-array

MongoDB setOnInsert and push if already existent

I would like to add a document if it does not exist and else add an element to one of it's sub-documents.
db.test.update(
{
name : 'Peter'
},
$setOnInsert : {
name : 'Peter',
visits: { 'en' : ['today'], 'us' : [] }
},
$push : {
visits.en : 'today'
},
{ upsert : true }
)
If Peter exists, add an element to its visists.en or visists.us arrays. Else, create a document for Peter. This document should have the format for visits which should contain the current element ('today').
My issue is that I have "have conflicting mods in update".
I.e. (afaik), I cannot write to two things in one query. Yet how can I solve this dilemma?
You could implement it without $setOnInsert operator.
db.test.update(
{
name : 'Peter'
},
{
$push : {
"visits.en" : 'today'
}
},
{ upsert : true }
)
If Peter exists, element 'today' will be added to its visits.en array. Else, will be created a document for Peter, with visits object, that will be contain array en with 'today' element.
And I think, that error occured because of you using same property (visits) in two operations ($setOnInsert and $push).
You can still use $setOnInsert but when $setOnInsert and $push doesn't updates in the same fields as mentioned before.
N.b: We use $addToSet if you don't want a duplicated values in your Array
db.test.update(
{
name : 'Peter'
},
{
$setOnInsert: {name : 'Peter'},
$addToSet: {"visits.en": 'today'} // or $push
},
{upsert: true})

MongoDB update. Trying to set one field from a property of another

What I'm trying to do is pretty straightforward, but I can't find out how to give one field the value of another.
I simply want to update one field with the character count of another.
db.collection.update({$exists:true},{$set : {field1 : field2.length}})
I've tried giving it dot notation
db.collection.update({$exits:true},{$set : {field1: "this.field2.length"}})
As well as using javascript syntax
db.collection.update({$exits:true},
{$set : {field1: {$where : "this.field2.length"}})
But just copied the string and got a "notOkforstorage" respectively. Any help?
Update:
I only get the "notOkforStorage" when I query by ID:
db.collection.update({_id:ObjectID("38289842bbb")},
{$set : {field1: {$where :"this.field2.length"}}})
Try the following code:
db.collection.find(your_querry).forEach(function(doc) {
doc.field1 = doc.field2.length;
db.collection.save(doc);
});
You can use your_querry to select only part of the original collection do perform an update. If you want to process an entire collection, use your_querry = {}.
If you want all operations to be atomic, use update instead of save:
db.collection.find( your_querry, { field2: 1 } ).forEach(function(doc) {
db.collection.update({ _id: doc._id },{ $set: { field1: doc.field2.length } } );
});
Starting Mongo 4.2, db.collection.update() can accept an aggregation pipeline, finally allowing the update/creation of a field based on another field:
// { "_id" : ObjectId("5e84c..."), "field1" : 12, "field2" : "world" }
db.collection.update(
{ "_id" : ObjectId("5e84c...") },
[{ $set: { field1: { $strLenCP: "$field2" } } }]
)
// { "_id" : ObjectId("5e84c..."), "field1" : 5, "field2" : "world" }
The first part {} is the match query, filtering which documents to update.
The second part [{ $set: { field1: { $strLenCP: "$field2" } } }] is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline). $set is a new aggregation operator and an alias for $addFields. Any aggregation operator can be used within the $set stage; in our case $strLenCP which provides the length of field2.
As far I know the easiest way is the read and write aproach:
//At first, get/prepare your new value:
var d= db.yourColl.fetchOne({....});
d.field1== d.field2.length;
// then update with your new value
db.yourColl.save(d);
Your are using exists in the wrong way.
Syntax: { field: { $exists: <boolean> } }
You use of $where is also incorrect
Use the $where operator to pass either a string containing a JavaScript expression or a full JavaScript function to the query system
db.myCollection.find( { $where: "this.credits == this.debits" } );
db.myCollection.find( { $where: "obj.credits == obj.debits" } );
db.myCollection.find( { $where: function() { return (this.credits == this.debits) } } );
db.myCollection.find( { $where: function() { return obj.credits == obj.debits; } } );
I think you should use Map-Reduce for what you are trying to do.

In mongoDb, how do you remove an array element by its index?

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}})