{
"Templates": [
{
"ProductId": "63a2b0f87a810608e6ca6d95",
"RevisionNum": [
"221222"
],
"EffectiveDate": "2022-12-22T02:45:22.587Z",
"HardwareVer": "A",
"SoftwareVer": "1.0",
"WorkTasks": [],
"_id": "63a3c4d950b22e564d2b8dee"
}
],
"_id": "63a3c4d950b22e564d2b8ded",
"__v": 0
}
Update data element by productId one are update value in array ["221222"] to ["111111"] in RevisionNum array other are for HardwareVer in MongoDB and Nodejs
You can use arrayFilters as follow:
db.collection.update({},
{
$set: {
"Templates.$[y].RevisionNum.$[x]": "1111",
"Templates.$[y].HardwareVer": "B"
}
},
{
arrayFilters: [
{
x: "221222"
},
{
"y.ProductId": "63a2b0f87a810608e6ca6d95"
}
]
})
Explained:
Create two arrayFilters x & y to identify the element that you need to update.
Playground
Related
You can see my Mongodb Records at last... I am now trying to implement search functionality,
I mad checkbox filtration for my project and below I listed those arrays after I clicked multiple checkboxes (see 1, 2 and 3).
I tried in aggregate with multiple match queries with $in, but it doesn't worked. Below arrays are used to check the records.
for example:
["Restaurant", "Mall"] need to check with "commercialType" in records, at the same time ["AC Rooms", "3 Phase Electricity"] need to check with "propertyFeatures.name" in records.. so all matching records must display if records exist with those filtrations.
I tried with multiple $in queries like this, but it gives empty records.
"$match": {
"commercialType": {
"$in": ["Restaurant", "Hotel"]
},
{
"propertyFeatures.name": {
"$in": ['AC Rooms']
}
},
... other match filters
}
1. Below Array is used to find commercialType (field in doc)
[
'Restaurant',
'Office space',
'Hotel'
]
2. Below Array is used to find landType (field in doc)
[
'Bare land',
'Beachfront land',
'Coconut land'
]
3. Below Array is used to find "propertyFeatures.name" (field in doc)
[
'AC Rooms',
'3 Phase Electricity',
'Hot Water'
]
[
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "House",
"propertyFeatures": [
{
"id": 1,
"name": "AC Rooms",
"value": true
}
]
},
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "Land",
"landType": "Bare land",
"propertyFeatures": [
{
"id": 1,
"name": "Wider Road",
"value": true
}
]
},
{
"_id": {
"$oid": "6343b68edf5e889a575c8502"
},
"propertyType": "Commercial",
"commercialType": "Restaurant",
"propertyFeatures": [
{
"id": 1,
"name": "3 Phase Electricity",
"value": true
}
]
}
]
You are probably missing $or operator, so your example pipeline becomes
[
{"$match": {
"$or": [
{
"commercialType": {
"$in": ["Restaurant", "Hotel"]
},
{
"propertyFeatures.name": {
"$in": ['AC Rooms']
}
}
]
}
]
MongoDB docs: https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/#error-handling
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"
}}
]);
I have following documents in a MongoDb:
from pymongo import MongoClient
client = MongoClient(host='my_host', port=27017)
database = client.forecast
collection = database.regions
collection.delete_many({})
regions = [
{
'id': 'DE',
'sites': [
{
'name': 'paper_factory',
'energy_consumption': 1000
},
{
'name': 'chair_factory',
'energy_consumption': 2000
},
]
},
{
'id': 'FR',
'sites': [
{
'name': 'pizza_factory',
'energy_consumption': 3000
},
{
'name': 'foo_factory',
'energy_consumption': 4000
},
]
}
]
collection.insert_many(regions)
Now I would like to copy the property sites.energy_consumption to a new field sites.new_field for each site:
set_stage = {
"$set": {
"sites.new_field": "$sites.energy_consumption"
}
}
pipeline = [set_stage]
collection.aggregate(pipeline)
However, instead of copying the individual value per site, all site values are collected and added as an array. Intead of 'new_field': [1000, 2000] I would like to get 'new_field': 1000 for the first site:
{
"_id": ObjectId("61600c11732a5d6b103ba6be"),
"id": "DE",
"sites": [
{
"name": "paper_factory",
"energy_consumption": 1000,
"new_field": [
1000,
2000
]
},
{
"name": "chair_factory",
"energy_consumption": 2000,
"new_field": [
1000,
2000
]
}
]
},
{
"_id": ObjectId("61600c11732a5d6b103ba6bf"),
"id": "FR",
"sites": [
{
"name": "pizza_factory",
"energy_consumption": 3000,
"new_field": [
3000,
4000
]
},
{
"name": "foo_factory",
"energy_consumption": 4000,
"new_field": [
3000,
4000
]
}
]
}
=> What expression can I use to only use the corresponding entry of the array?
Is there some sort of current-index operator:
$sites[<current_index>].energy_consumption
or an alternative dot operator (would remind me on difference between * multiplication and .* element wise matrix multiplication)?
$sites:energy_consumption
Or is this a bug?
Edit
I also tried to use the "$" positional operator, e.g. with
sites.$.new_field
or
$sites.$.energy_consumption
but then I get the error
FieldPath field names may not start with '$'
Related:
https://docs.mongodb.com/manual/reference/operator/aggregation/set/#std-label-set-add-field-to-embedded
In MongoDB how do you use $set to update a nested value/embedded document?
If the field is member of an array by selecting it you are selecting all of them.
{ar :[{"a" : 1}, {"a" : 2}]}
"$ar.a" = [1 ,2]
Also you cant mix update operators with aggregation, you cant use things like
$sites.$.energy_consumption, if you are doing aggregation you have to use aggregate operators, with only exception the $match stage where you can use query operators.
Query
alternative slightly different solution from yours using $setField
i guess it will be faster, but probably little difference
no need to use javascript it will be slower
this is >= MongoDB 5 solution, $setField is new operator
Test code here
aggregate(
[{"$set":
{"sites":
{"$map":
{"input":"$sites",
"in":
{"$setField":
{"field":"new_field",
"input":"$$this",
"value":"$$this.energy_consumption"}}}}}}]
)
use $addFields
db.collection.update({},
[
{
"$addFields": {
"sites": {
$map: {
input: "$sites",
as: "s",
in: {
name: "$$s.name",
energy_consumption: "$$s.energy_consumption",
new_field: {
$map: {
input: "$sites",
as: "value",
in: "$$value.energy_consumption"
}
}
}
}
}
}
}
])
mongoplayground
I found following ugly workarounds that set the complete sites instead of only specifying a new field with dot notation:
a) based on javascript function
set_stage = {
"$set": {
"sites": {
"$function": {
"body": "function(sites) {return sites.map(site => {site.new_field = site.energy_consumption_in_mwh; return site})}",
"args": ["$sites"],
"lang": "js"
}
}
}
}
b) based on map and mergeObjects
set_stage = {
"$set": {
"sites": {
"$map": {
"input": "$sites",
"in": {
"$mergeObjects": ["$$this", {
"new_field": "$$this.energy_consumption_in_mwh"
}]
}
}
}
}
}
If there is some kind of $$this context for the dot operator expression, allowing a more elegant solution, please let me know.
I'm having an issue updating a nested Array in a document. Reading around the topic i've come across various method, one that i've tweaked below, however nothing seems to work for me!
I'm trying to update the field systemUpdate_DT which is in a parent Array called List and a child array called customData. I'm referring to the object in the child array using the key _id of the parent array and key field_id in the child array.
How do I update the systemUpdate_DT of the respective object?
Live Example: https://mongoplayground.net/p/453OFPOQqBp
A document in the collection looks like:
[
{
"_id": "6032a5ad80443334a35f2232",
"List": [
{
"_id": "6032a5af80443334a35f2234",
"customData": [
{
"_id": "6032a5bc80443334a35f223c",
"systemUpdate_DT": null,
"field_id": "6032a5bc80443334a35f223b"
},
{
"_id": "6032a5c280443334a35f223e",
"systemUpdate_DT": null,
"field_id": "6032a5c280443334a35f223d"
}
]
},
{
"_id": "6032a5b080443334a35f2236",
"customData": [
{
"_id": "6032a5bc80443334a35f223c",
"systemUpdate_DT": null,
"field_id": "6032a5bc80443334a35f223b"
},
{
"_id": "6032a5c280443334a35f223e",
"systemUpdate_DT": null,
"field_id": "6032a5c280443334a35f223d"
}
]
}
]
}
]
My Update Query looks like:
db.collection.updateOne({
{
"List._id": mongodb.ObjectId("6032a5af80443334a35f2234"),
"List.customData.field_id": mongodb.ObjectId("6032a5bc80443334a35f223b")
},
{
$set: {
"List.$.customData.systemUpdate_DT": 'updatedDTTM'
}
})
As there's two nested arrays in your document, you can't set the field with classic positional operator '$'.
Instead, you should use the arrayFilters option like this:
db.collection.update({
"_id": ObjectId("6032a5ad80443334a35f2232")
},
{
$set: {
"List.$[list].customData.$[customData].systemUpdate_DT": "updatedDTTM"
}
},
{
"multi": false,
"upsert": false,
arrayFilters: [
{
"list._id": {
"$eq": ObjectId("6032a5af80443334a35f2234")
}
},
{
"customData._id": {
"$eq": ObjectId("6032a5bc80443334a35f223c")
}
}
]
})
try it online: mongoplayground.net/p/fb_86rNUKvt
I have a MongoDB record as follow:
"id": 1,
"Tasks": [
{
"description": "BLAH",
"Tags": [
{
"Name": "test",
"tagID": "YRG+crq3SJucvlUwTo/uSg=="
},
{
"Name": "Cars",
"tagID": "ObwiiZpNTOGECgHb1HehHg=="
}
]
},
......
]
I'm trying to delete the object from 'Tags' with the 'Name: test' by reference to its 'tagID'. The query I have deletes the whole record within 'Tasks' not just that particular Tags object.
db.user.update({ 'id': 1 },
{
'$pull': { 'Tasks': {'Tags.tagID': "YRG+crq3SJucvlUwTo/uSg==" }}
},
{ '$multi': 'true' }
)
How can I ammend my query to only remove that particular tag and not remove the entire record?
Using Pymongo and the $ operator
col.update({"id": 1, "Tasks.description": "BLAH"},
{
"$pull": {"Tasks.$.Tags" : { "tagID": "YRG+crq3SJucvlUwTo/uSg==" }}
}, multi=True
)
Use the positional operator $ together with the $pull update operator to remove the specific array element object:
db.user.update({"id": 1, "Tasks.description": "BLAH"},
{
"$pull": {"Tasks.$.Tags" : { "tagID": "YRG+crq3SJucvlUwTo/uSg==" }}
},
{ multi: true}
);
I think you are looking for the $unset command. Reference here.