Deleting specific record from an array nested within another array - mongodb

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.

Related

Update data two element in mongo db

{
"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

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.

Update nested array mongodb

I want do an updateMany() operation on a nested array for all documents of my collection.
Here is the documents format :
{
"number": 1,
"products": [{
"name": "test",
"compositions": ["water", "sugar"],
}]
},
{
"number": 2,
"products": [{
"name": "test12",
"compositions": ["cotton", "linen"],
}]
}
How can add element ("color" for example) in compositions array nested in product array for all documents by doing updateMany() operation ?
I try this but it is not work :
db.getSiblingDB("mydatabase").getCollection("stock").find().forEach(element => {
element.products.forEach(product => {
db.stock.updateOne(
{$set: {
'compositions': { $addToSet: { 'product.compositions' : "color"}}
}})
})
})
Thank you in advance.
You can use all positional operator $[] to update all elements in the array.
db.getSiblingDB("mydatabase").getCollection("stock").updateMany(
{},
{ $addToSet: { "products.$[].compositions": "color" } }
)
In case of if you don't want to update all elements, you can use arrayFilters (which allows you to use $[i] notation inside option section):
mongodb example playground
db.getSiblingDB("mydatabase").getCollection("stock").updateMany(
{},
{ $addToSet: { "products.$[i].compositions": "color" } },
{ arrayFilters: [{"i.name": "test12"}]
)

How to use $set and dot notation to update embedded array elements using corresponding old element?

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.

Updating a nested Array in using UpdateOne()

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