MongoDB - Updating all matching Nested Documents - mongodb

I have this MongoDB Document Collection. I would like to update a PropertyContractors Phone number for all documents where "ContractorTelephone": "0865215486".
{
"_id": "4",
"PropertyType": "House",
"PropertyNameNumber": "49",
"PropertyStreet": "Paul Street",
"PropertyTown": "Farmleigh",
"PropertyCity": "Waterford City",
"PropertyCounty": "Co Waterford",
"PropertyBedrooms": "3",
"PropertyDescription": "Central, Cheap",
"PropertyFacilities": [
{
"FacilitiesSmoking": "Yes",
"FacilitiesPets": "Yes",
"FacilitiesBroadBand": "Yes",
"FacilitiesTV": "Yes"
}
],
"PropertyAvailable": "1",
"PropertyContractor": [
{
"ContractorName": "John Murphy",
"ContractorTelephone": "0865215486",
"ContractorType": "Plumber"
}
]
}
Ive Tried
db.Property.update(
{ PropertyContractor.ContractorTelephone: "0865215486" },
{
$set: { PropertyContractor.ContractorTelephone: "0854854215" }
},
{ multi: true }
)
But it just says that the . is wrong Syntax
Thanks in advance.

Use the $ positional operator, it identifies an element in an array to update without explicitly specifying the position of the element in the array:
db.Property.update(
{
"PropertyContractor.ContractorTelephone": "0865215486"
},
{
"$set": {
"PropertyContractor.$.ContractorTelephone": "0854854215"
}
},
{ "multi": true }
);

Related

MongoDB -Query documents where nested array fields is equal to some value

I have a JSON object:
{
"ownershipSheetB": {
"lrUnitShares": [{
"description": "blabla1",
"lrOwners": [{
"lrOwnerId": 35780527,
"name": "somename1",
"address": "someadress1",
"taxNumber": "12345678910"
}
],
"lrUnitShareId": 29181970,
"subSharesAndEntries": [],
"orderNumber": "1"
}, {
"description": "blabla2",
"lrOwners": [{
"lrOwnerId": 35780528,
"name": "somename2",
"address": "someadress2",
"taxNumber": "12345678911"
}
],
"lrUnitShareId": 29181971,
"subSharesAndEntries": [],
"orderNumber": "2"
}
],
"lrEntries": []
}
}
I would like to query all documents that have taxNumber field equal to some string (say "12345678911" from the example above).
I have tried this query:
{"ownershipSheetB.lrUnitShares": { "lrOwners": {"taxNumber": "12345678910"}}}
but it returns no documents.
Solution 1: With dot notation
db.collection.find({
"ownershipSheetB.lrUnitShares.lrOwners.taxNumber": "12345678910"
})
Demo Solution 1 # Mongo Playground
Solution 2: With $elemMatch
db.collection.find({
"ownershipSheetB.lrUnitShares": {
$elemMatch: {
"lrOwners": {
$elemMatch: {
"taxNumber": "12345678910"
}
}
}
}
})
Demo Solution 2 # Mongo Playground

MongoDB use array field's element to $set a new field of the document

In the database, I have documents like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
]
}
I would like to add a closedAt field to the relevant Tickets, getting the value from the eventHistory array's last element. The resultant document would look like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
],
"closedAt": "xx-yy-zzzz"
}
The following pipeline allows me to use the entire object that's present as the eventHistory array's last element.
db.collection.updateMany(
<query>,
[
"$set": {
"closedAt": {
"$arrayElemAt": [
"$eventHistory",
-1
]
}
}
]
...
)
But I want to use only the timestamp field; not the entire object.
Please help me adjust (and/or improve) the pipeline.
One option to fix your query is:
db.collection.updateMany(
<query>,
[
{
$set: {
"Ticket.closedAt": {
$last: "$Ticket.eventHistory.timestamp"
}
}
}
])
See how it works on the playground example
But note that you assume that last item is a closing one. Is this necessarily the case? Otherwise you can validate it.

MongoDB - How to add a field to all object within an array

I have a document with a field called info, and info has a field inside it called data. data is an array of objects. I want to add a new boolean field, isActive: false, to each object in data, with updateMany.
This is how it looks now
{
info: {
data: [{
"name": "Max"
},
{
"name": "Brian"
},
...
]
}
}
This is what I want:
{
info: {
data: [{
"name": "Max",
"isActive": false
},
{
"name": "Brian",
"isActive": false
},
...
]
}
}
How do I do that?
Add the isActive field with all positional operator $[].
db.collection.update({},
{
$set: {
"info.data.$[].isActive": false
}
},
{
multi: true
})
Consider applying { multi: true } if you want to update multiple documents.

How to save deletion in a deeply nested MongoDB document

I am new to MongoDB and I am using MongoDB shell to perform the operations.
I am working to remove the array named Process from all the Items, but it seems that I do not grasp the remove concept correctly.
The documents we use are deeply nested - we do not know how many items there are, or how deep the level of nesting.
What I tried so far is to use recursion to iterate through the items:
function removeAllProcessFields(docItems)
{
if(Array.isArray(docItems))
{
docItems.forEach(function(item)
{
print("idItem: "+item._id);
if(item.Process == null)
{
print("Process null");
}
else
{
$unset: { Process: ""}
}
removeAllProcessFields(item.Items);
})
}
}
var docs = db.getCollection('MyCollection').find({})
docs.forEach(function(doc)
{
print("idDoc: "+doc._id);
removeAllProcessFields(doc.Items);
})
But I have difficulties on using unset properly to save the operation.
An example document would be:
{
"_id": "622226d319517e83e8ed6151",
"Name": "test1",
"Description": "",
"Items": [{
"_id": "622226d319517e83e8ed614e",
"Name": "test-item",
"Description": "",
"Process": [{
"Name": "Step1"
}, {
"Name": "Step2"
}],
"Items": [{
"_id": "622226d319517e83e8ed614f",
"Name": "test-subItem1",
"Description": "",
"Process": [{
"Name": "StepSub1"
}, {
"Name": "StepSub2"
}, {
"Name": "StepSub3"
}],
"Items": []
},
{
"_id": "622226d319517e83e8ed6150",
"Name": "test-subItem2",
"Description": "",
"Process": [{
"Name": "StepSub4"
}, {
"Name": "StepSub5"
}, {
"Name": "StepSub6"
}],
"Items": []
}
]
}]
}
What I hope to achieve would be:
{
"_id": "622226d319517e83e8ed6151",
"Name": "test1",
"Description": "",
"Items": [{
"_id": "622226d319517e83e8ed614e",
"Name": "test-item",
"Description": "",
"Items": [{
"_id": "622226d319517e83e8ed614f",
"Name": "test-subItem1",
"Description": "",
"Items": []
},
{
"_id": "622226d319517e83e8ed6150",
"Name": "test-subItem2",
"Description": "",
"Items": []
}
]
}]
}
Something like this maybe using the $[] positional operator:
db.collection.update({},
{
$unset: {
"Items.$[].Items.$[].Process": 1,
"Items.$[].Process": 1
}
})
You just need to construct it in the recursion ...
playground
JavaScript recursive function example:
mongos> db.rec.find()
{ "_id" : ObjectId("622a6c46ae295edb276df8e2"), "Items" : [ { "a" : 1 }, { "Items" : [ { "Items" : [ { "Items" : [ ], "Process" : [ 1, 2, 3 ] } ], "Process" : [ 4, 5, 6 ] } ], "Process" : [ ] } ] }
mongos> db.rec.find().forEach(function(obj){ var id=obj._id,ar=[],z=""; function x(obj){ if(typeof obj.Items != "undefined" ){ obj.Items.forEach(function(k){ if( typeof k.Process !="undefined" ){ z=z+".Items.$[]";ar.push(z.substring(1)+".Process") }; if(typeof k.Items != "undefined"){x(k)}else{} }) }else{} };x(obj);ar.forEach(function(del){print( "db.collection.update({_id:ObjectId('"+id+"')},{$unset:{'"+del+"':1}})" );}) })
db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Process':1}})
db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Items.$[].Process':1}})
db.collection.update({_id:ObjectId('622a6c46ae295edb276df8e2')},{$unset:{'Items.$[].Items.$[].Items.$[].Process':1}})
mongos>
Explained:
Loop over all documents in collection with forEach
Define recursive function x that will loop over any number of nested Items and identify if there is Process field and push to array ar
Finally loop over array ar and construct the update $unset query , in the example only printed for safety , but you can improve generating single query per document and executing unset query ...
Assuming you are on v>=4.4 you can use the "merge onto self" feature of $merge plus defining a recursive function to sweep through the collection and surgically remove one or a list of fields at any level of the hierarchy. The same sort of needs arise when processing json-schema data which is also arbitrarily hierarchical.
The solution below has extra logic to "mark" documents that had any modifications so the others can be removed from the update set passed to $merge. It also can be further refined to reduce some variables; it was edited down from a more general solution that had to examine keys and values.
db.foo.aggregate([
{$replaceRoot: {newRoot: {$function: {
body: function(obj, target) {
var didSomething = false;
var process = function(holder, spot, value) {
// test FIRST since [] instanceof Object is true!
if(Array.isArray(value)) {
for(var jj = value.length - 1; jj >= 0; jj--) {
process(value, jj, value[jj]);
}
} else if(value instanceof Object) {
walkObj(value);
}
};
var walkObj = function(obj) {
Object.keys(obj).forEach(function(k) {
if(target.indexOf(k) > -1) {
delete obj[k];
didSomething = true;
} else {
process(obj, k, obj[k]);
}
});
}
// ENTRY POINT:
if(!Array.isArray(target)) {
target = [ target ]; // if not array, make it an array
}
walkObj(obj);
if(!didSomething) {
obj['__didNothing'] = true;
}
return obj;
},
// Invoke!
// You can delete multiple fields with an array, e.g.:
// ..., ['Process','Description']
args: [ "$$ROOT", 'Process' ],
lang: "js"
}}
}}
// Only let thru docs WITHOUT the marker:
,{$match: {'__didNothing':{$exists:false}} }
,{$merge: {
into: "foo",
on: [ "_id" ],
whenMatched: "merge",
whenNotMatched: "fail"
}}
]);

Mongo Update $set not working

I'm trying to mass update some mongo documents.
I'm using the query
db.articles.update(
{
'categories.id': ObjectId("51cd5272222wb6zs464fa4d9"),
'source.importer': 'pa'
},
{
$set :
{
'source.expires-at': ISODate("2014-01-01T08:39:45Z")
}
}
)
This query does not update the source.expires-at field, however the where part of the statement works fine.
The document structure is
{
"_id": ObjectId("5211dc100000044707000015"),
"categories": {
"0": {
"id": ObjectId("51cd5272222wb6zs464fa4d9")
}
},
"source": {
"importer": "pa",
"expires-at": ISODate("2013-09-18T08:49:32.0Z")
}
}
Try this:
db.articles.update(
{
'categories.id': ObjectId("51cd5272222wb6zs464fa4d9"),
'source.importer': 'pa'
},
{
$set: {
'source.expires-at': ISODate("2014-01-01T08:39:45Z")
}
},
{ multi: true }
)
You will need to pass additional option argument {multi: true};
Look in to this https://education.10gen.com/courses/10gen/M101JS/2013_August/courseware/CRUD/Multi-update/