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

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.

Related

Mongodb: concat to existing document

I have my collection like this:
{
"_id" : "ID1234",
"read_object" : "sss-ssss",
"expireAt" : ISODate("2020-04-30T22:00:00.000Z")
}
In case he encounters the same ID, I would like to update the read_object field, otherwise create a new document.
I tried to do it like this:
db.collection.update(
{ _id: "ID1234" },
{
$set: { read_object: { $concat: ["$read_object", "test"] } },
},
{ upsert: true }
)
but I get an error every time:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
If I add square brackets before $set, like this:
db.collection.update(
{ _id: "1b1b871493-14a0-4d21-bd74-086442df953c-2020-02" },
[{
$set: { read_object: { $concat: ["$read_object", "test"] } },
}],
{ upsert: true }
)
I get this error:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
Where do I have a mistake?
$concat is an aggregation operator, meaning you can't use it while using the basic update syntax as you can only use update operators on it.
With that said Mongo version 4.2 introduces pipeline updates, which is basically what you're trying to do with the square brackets.
Assuming you are using Mongo version 4.2 heres a working example:
db.test1.update({_id: "ID1234"}, [
{$set: {"read_object": {$concat: [{$ifNull: ["$read_object", ""]}, "test"]}}}
], {upsert: true});
Basically we just need to "replace" read_object if document does not exist as it is undefined in that case.
If you are using Mongo version that's smaller than 4.2 then unfortunately there is no way to do what you want in one operation, you'll have to first read the document and then adjust accordingly.

In mongodb know index of array element matched with $in operator?

I am using aggregation with mongoDB now i am facing a problem here, i am trying to match my documents which are present in my input array by using $in operator. Now i want to know the index of the lement from the input array now can anyone please tell me how can i do that.
My code
var coupon_ids = ["58455a5c1f65d363bd5d2600", "58455a5c1f65d363bd5d2601","58455a5c1f65d363bd5d2602"]
couponmodel.aggregate(
{ $match : { '_id': { $in : coupons_ids }} },
/* Here i want to know index of coupon_ids element that is matched because i want to perform some operation in below code */
function(err, docs) {
if (err) {
} else {
}
});
Couponmodel Schema
var CouponSchema = new Schema({
category: {type: String},
coupon_name: {type: String}, // this is a string
});
UPDATE-
As suggested by user3124885 that aggregation is not better in performance, can anyone please tell me the performance difference between aggregation and normal query in mongodb. And which one is better ??
Update-
I read this question on SO mongodb-aggregation-match-vs-find-speed. Here the user himself commented that both take same time, also by seeing vlad-z answer i think aggregation is better. Please if anyone of you have worked on mongodb Then please tell me what are your opinion about this.
UPDATE-
I used sample json data containing 30,000 rows and tried match with aggregation v/s find query aggregation got executed in 180 ms where find query took 220ms. ALso i ran $lookup it is also taking not much than 500ms so think aggregation is bit faster than normal query. Please correct me guys if any one of you have tried using aggregation and if not then why ??
UPDATE-
I read this post where user uses below code as a replacement of $zip SERVER-20163 but i am not getting how can i solve my problem using below code. So can anybody please tell me how can i use below code to solve my issue.
{$map: {
input: {
elt1: "$array1",
elt2: "$array2"
},
in: ["$elt1", "$elt2"]
}
Now can anyone please help me, it would be really be a great favor for me.
So say we have the following in the database collection:
> db.couponmodel.find()
{ "_id" : "a" }
{ "_id" : "b" }
{ "_id" : "c" }
{ "_id" : "d" }
and we wish to search for the following ids in the collections
var coupons_ids = ["c", "a" ,"z"];
We'll then have to build up a dynamic projection state so that we can project the correct indexes, so we'll have to map each id to its corresponding index
var conditions = coupons_ids.map(function(value, index){
return { $cond: { if: { $eq: ['$_id', value] }, then: index, else: -1 } };
});
Then we can then inject this in to our aggregation pipeline
db.couponmodel.aggregate([
{ $match : { '_id' : { $in : coupons_ids } } },
{ $project: { indexes : conditions } },
{ $project: {
index : {
$filter: {
input: "$indexes", as: "indexes", cond: { $ne: [ "$$indexes", -1 ] }
}
}
}
},
{ $unwind: '$index' }
]);
Running the above will now output each _id and it's corresponding index within the coupons_ids array
{ "_id" : "a", "index" : 1 }
{ "_id" : "c", "index" : 0 }
However we can also add more items in to the pipeline at the end and reference $index to get the current matched index.
I think you could do it in a faster way simply retrieving the array and search manually. Remember that aggregation don't give you performance.
//$match,$in,$and
$match:{
$and:[
{"uniqueID":{$in:["CONV0001"]}},
{"parentID":{$in:["null"]}},
]
}
}])

Replace a word from a string

I have mongodb documents with a field like this:
Image : http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg
How can I replace the zoom part in the string value with some other text in order to get:
Image : http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg
You could use mongo's forEach() cursor method to do an atomic update with the $set operator :
db.collection.find({}).snapshot().forEach(function(doc) {
var updated_url = doc.Image.replace('zoom', 'product2');
db.collection.update(
{"_id": doc._id},
{ "$set": { "Image": updated_url } }
);
});
Given a very large collection to update, you could speed up things a little bit with bulkWrite and restructure your update operations to be sent in bulk as:
var ops = [];
db.collection.find({}).snapshot().forEach(function(doc) {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": { "$set": { "Image": doc.Image.replace('zoom', 'product2') } }
}
});
if ( ops.length === 500 ) {
db.collection.bulkWrite(ops);
ops = [];
}
})
if ( ops.length > 0 )
db.collection.bulkWrite(ops);
db.myCollection.update({image: 'http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg'}, {$set: {image : 'http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg'}})
If you need to do this multiple times to multiple documents, you need to iterate them with a function. See here: MongoDB: Updating documents using data from the same document
Nowadays,
starting Mongo 4.2, db.collection.updateMany (alias of db.collection.update) can accept an aggregation pipeline, finally allowing the update of a field based on its own value.
starting Mongo 4.4, the new aggregation operator $replaceOne makes it very easy to replace part of a string.
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg" }
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-boom.jpg" }
db.collection.updateMany(
{ "Image": { $regex: /zoom/ } },
[{
$set: { "Image": {
$replaceOne: { input: "$Image", find: "zoom", replacement: "product2" }
}}
}]
)
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg" }
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-boom.jpg" }
The first part ({ "Image": { $regex: /zoom/ } }) is just there to make the query faster by filtering which documents to update (the ones containing "zoom")
The second part ($set: { "Image": {...) is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline):
$set is a new aggregation operator (Mongo 4.2) which in this case replaces the value of a field.
The new value is computed with the new $replaceOne operator. Note how Image is modified directly based on the its own value ($Image).

How can I write a Mongoose find query that uses another field as it's conditional?

Consider the following:
I have a Mongoose model called 'Person'. In the schema for the Person mode, each Person has two fields: 'children' and 'maximum_children'. Both fields are of type Number.
I would like to write a find query that returns Persons when that Persons 'children' value is less that it's 'maximum_children' value.
I have tried:
person_model.find({
children: {
$lt: maximum_children
}
}, function (error, persons) {
// DO SOMETHING ELSE
});
and
person_model.find({
children: {
$lt: 'maximum_children'
}
}, function (error, persons) {
// DO SOMETHING ELSE
});
I'm doing something wrong in trying to specify the field name that I want to compare 'children' against.
OK.
I found a solution, just after I posted this question.
The answer seems to be:
person_model.find({
$where: "children < maximum_children"}, function (error, persons)
}, {
// DO SOMETHING ELSE
});
Seems to work OK, although it seems messy.
$where must execute its JavaScript conditional against every doc so its performance can be quite poor. Instead, you can use aggregate to include a new field in a $project stage the indicates whether the doc matches or not and then filter on that:
person_model.aggregate([
{$project: {
isMatch: {$lt: ['$children', '$maximum_children']},
doc: '$$ROOT'
}},
{$match: {isMatch: true}},
{$project: {_id: 0, doc: 1}}
], function(err, results) {...});
This uses $$ROOT to include the original doc as the doc field of the projection, with a final $project used to remove the isMatch field that was added.
results looks like:
{
"doc" : {
"_id" : ObjectId("54d04591257efd80c6965ada"),
"children" : 5,
"maximum_children" : 10
}
},
{
"doc" : {
"_id" : ObjectId("54d04591257efd80c6965add"),
"children" : 5,
"maximum_children" : 6
}
}
If you want to remove the added doc level of the objects you can use Array#map on results like so:
results = results.map(function(item) { return item.doc; });
Which reshapes results to put them back into their original form:
{
"_id" : ObjectId("54d04591257efd80c6965ada"),
"children" : 5,
"maximum_children" : 10
},
{
"_id" : ObjectId("54d04591257efd80c6965add"),
"children" : 5,
"maximum_children" : 6
}

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