How to Find and Update Multiple Array Elements in mongodb - arrays

> db.Dashboards.find({user:{$regex:"adams"}}).pretty();
{
"_id" : ObjectId("123"),
"user" : "adam.adams",
"widgets" : [
{
"_id" : ObjectId("124"),
"column" : 1,
"configMap" : {
"coleg" : "bas.baser,cer.ceras,tom.tomsa"
},
}
]
}
I have a Mongo database that keeps records like the one above, unfortunately I need to find all users who have "cer.ceras" in the "coleg" field and then replace this with "per.peras"
I try with
db.Dashboards.find({widgets:{"$elemMatch":{configMap:{"$elemMatch":{coleg:/.*ceras.*/}}}}}}).pretty();
But I'm not finding anything for me

This may a bit complex.
Filter:
The criteria should work with $regex to find the occurrence of the text.
Update:
Require the update with aggregation pipeline.
$set - Set widgets field.
1.1. $map - Iterate the element in the widgets array and return a new array.
1.1.1. $mergeObjects - Merge current iterate document with the result of 1.1.1.1.
1.1.1.1. A document with configMap array. With $mergeObjects to merge current iterate configMap document with the result 1.1.1.1.1.
1.1.1.1.1. A document with coleg field. With $replaceAll to replace all matching text "cer.ceras" to "per.peras".
Update Options
{ multi: true } aims to update multiple documents.
db.collection.update({
widgets: {
$elemMatch: {
"configMap.coleg": {
$regex: "cer\\.ceras"
}
}
}
},
[
{
$set: {
widgets: {
$map: {
input: "$widgets",
in: {
$mergeObjects: [
"$$this",
{
configMap: {
$mergeObjects: [
"$$this.configMap",
{
coleg: {
$replaceAll: {
input: "$$this.configMap.coleg",
find: "cer.ceras",
replacement: "per.peras"
}
}
}
]
}
}
]
}
}
}
}
}
],
{
multi: true
})
Demo # Mongo Playground

Related

Removing an element in a mongoDB array based on the position of element with dynamically defined array

My question is a combination of:
This question: Removing the array element in mongoDB based on the position of element
And this question: mongodb set object where key value dynamically changes
I know you can define the field (array) dynamically like this:
{ [arrayName]: { <condition> } }
But, I then want to remove a certain element in a dynamically defined array by specifying the position (which is also defined dynamically). In other words, the function that processes this query is coming in which two parameters: the array's name and the index of the element to remove.
The options given by the selected answer were the following:
Option 1, does not work (in general), adapted to my case this looks like:
{ $pull : { [arrayName] : { $gt: index-1, $lt: index+1 } } }
Option 2, I cannot use dynamically defined values in field selectors with quotation marks (as far as I am aware):
{ $pull : "[arrayName].[index]" }
or
{ $pull : "[arrayName].$": index }
Option 3, is different method but can't use it for the same reason:
{ $unset: { "[arrayName].[index]": 1 } } // Won't work
{ $pull: { [arrayName]: null } } // Would probably work
The only workarounds I can think of right now involve significantly changing the design which would be a shame. Any help is appreciated!
PS: I'm using mongoose as a driver on the latest version as of today (v6.3.5) and MongoDB version 5.0.8
On Mongo version 4.2+ You can use pipelined updates to achieve this, you can get it done in multiple ways, here is what I consider the easiest two ways:
using $slice and $concatArrays to remove a certain element:
db.collection.update({},
[
{
$set: {
[arrayName]: {
$concatArrays: [
{
$slice: [
`$${arrayName}`,
index,
]
},
{
$slice: [
`$${arrayName}`,
index + 1,
{
$size: `$${arrayName}`
}
]
}
]
}
}
}
])
Mongo Playground
using $filter and $zip to filter out based on index:
db.collection.updateOne(
{},
[
{
"$set": {
[arrayName]: {
$map: {
input: {
$filter: {
input: {
$zip: {
inputs: [
{
$range: [
0,
{
$size: `$${arrayName}`
}
]
},
`$${arrayName}`
]
}
},
cond: {
$ne: [
{
"$arrayElemAt": [
"$$this",
0
]
},
index
]
}
}
},
in: {
$arrayElemAt: [
"$$this",
1
]
}
}
}
}
}
])
Alternatively you can just prepare

MongoDB: How to take multiple fields within a document and output their values into an array (as a new field)?

MongoDB: 4.4.9, Mongosh: 1.0.4
I have a MongoDB collection full of documents with monthly production data as separate fields (monthlyProd1, monthlyProd2, etc.). Each field is one month's production data, and the values are an object data type.
Document example:
_id: ObjectId("314e0e088f183fb7e699d635")
name: "documentName"
monthlyProd1: Object
monthlyProd2: Object
monthlyProd3: Object
...
I want to take all the months and put them into a single new field (monthlyProd) -- a single array of objects.
I can't seem to access the fields with the different methods I've tried. For example, this gets close to doing what I want:
db.monthlyProdData.updateMany({},
{ $push: { "monthlyProd": { $each: [ "$monthlyProd1", "$monthlyProd2", "$monthlyProd3" ] } } }
)
...but instead of taking the value / object data from each field, like I had hoped, it just outputs a string into the monthlyProd array ("$monthlyProd1", "$monthlyProd2", ...):
Actual output:
monthlyProd: Array
0: "$monthlyProd1"
1: "$monthlyProd2"
2: "$monthlyProd3"
...
Desired output:
monthlyProd: Array
0: Object
1: Object
2: Object
...
I want the data, not a string! Lol. Thank you for your help!
Note: some months/fields may be an empty string ("") because there was no production. I want to make sure to not add empty strings into the array -- only months with production / fields that have an object data type. That being said, I can try figuring that out on my own, if I can just get access to these fields' data!
Try this one:
db.collection.updateMany({}, [
// convert to k-v Array
{ $set: { monthlyProd: { $objectToArray: "$$ROOT" } } },
{
$set: {
monthlyProd: {
// removed not needed objects
$filter: {
input: "$monthlyProd",
cond: { $not: { $in: [ "$$this.k", [ "name", "_id" ] ] } }
// or cond: { $in: [ "$$this.k", [ "monthlyProd1", "monthlyProd2", "monthlyProd3" ] ] }
}
}
}
},
// output array value
{ $project: { monthlyProd: "$monthlyProd.v" } }
])
Mongo playground
Thank you to #Wernfried for the original solution to this question. I have modified the solution to incorporate my "Note" about ignoring any empty monthlyProd# values (aka months that didn't have any production), so that they are not added into the final monthlyProd array.
To do this, I added an $and operator to the cond: within $filter, and added the following as the second expression for the $and operator (I used "" and {} to take care of the empty field values if they are of either string or object data type):
{ $not: { $in: [ "$$this.v", [ "", {} ] ] } }
Final solution:
db.monthlyProdData2.updateMany({}, [
// convert to k-v Array
{ $set: { monthlyProd: { $objectToArray: "$$ROOT" } } },
{
$set: {
monthlyProd: {
// removed not needed objects
$filter: {
input: "$monthlyProd",
cond: { $and: [
{ $not: { $in: [ "$$this.k", [ "name", "_id" ] ] } },
{ $not: { $in: [ "$$this.v", [ "", {} ] ] } }
]}
}
}
}
},
// output array value
{ $project: { monthlyProd: "$monthlyProd.v", name: 1 } }
])
Thanks again #Wernfried and Stackoverflow community!

Update an array of strings nested within an array of objects in mongodb

I want to update an array of strings which is nested inside an array of Objects.
This is my Mongo document:
{
"_id" : ObjectId("5a52d995734d1d388d17eb0b"),
"teams" : [
{
"assignedModels" : [
"5a1665a82c7eb90001a7d676",
"58d8fc2d734d1d5af6dd4803"
]
},
{
"assignedModels" : [
"58d8fc2d734d1d5af6dd4803"
]
}
]
}
Now i want to remove "58d8fc2d734d1d5af6dd4803" string from the assignedModels of each team object.
I have already tried some queries, but nothing is working as i expected
Current query:
db.collection('organisations').updateMany(
{ _id: database.ObjectID("5a52d995734d1d388d17eb0b") ,'teams.assignedModels' : { $exists:true } },
{ $pull : {'teams.$.assignedModels' : "58d8fc2d734d1d5af6dd4803" } },
{ multi: true });
Current output:
updated the 0th element of the teams array correctly but does not traverse through the other objects.
i tried $[], teams.assignedModels as well
Expected output:
{
"_id" : ObjectId("5a52d995734d1d388d17eb0b"),
"teams" : [
{
"assignedModels" : [
"5a1665a82c7eb90001a7d676"
]
},
{
"assignedModels" : [
]
}
]
}
In MongoDB 3.4, there is no way to do this nicely. You will need to either iterate over your elements (client-side) and update everything one-by-one. Or you could run the following query multiple times (until there are no more updates):
db.collection('organisations').updateMany(
{ _id: database.ObjectID("5a52d995734d1d388d17eb0b"), 'teams.assignedModels': "58d8fc2d734d1d5af6dd4803" },
{ $pull : {'teams.$.assignedModels' : "58d8fc2d734d1d5af6dd4803" } }
);
For MongoDB 3.6 you can simply do this:
db.collection('organisations').updateMany(
{ _id: database.ObjectID("5a52d995734d1d388d17eb0b") },
{ $pull : {'teams.$[].assignedModels' : "58d8fc2d734d1d5af6dd4803" } }
);
There's a catch, though, in case you're updating your MongoDB v3.4 to v3.6 which is documented here:
3.6 deployments have the following default featureCompatibilityVersion values: [...] For deployments upgraded from 3.4: "3.4" until you
setFeatureCompatibilityVersion to "3.6".
So you will need to run the following command once to unleash the 3.6 power:
db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

query for number of array elements containing some field in mongodb

I have a collection of documents that look like this:
{
my_array : [
{
name : "...",
flag : 1 // optional
},
...
]
}
How can I query for documents that exactly one of their elements contain the flag field?
This will return documents where my_array length is exactly 1:
db.col.find({ "my_array" : { $size : 1 } })
This will return documents where at least one of my_array's object contain the flag field:
db.col.find({ "my_array.flag" : { $exists : true } })
And this will look for documents where flag is an array of size one:
db.col.find({ "my_array.flag" : { $size : 1 } })
I want to somehow combine the first two queries, checking for some inner field existence, and then querying for the size of the filtered array
You can try $redact with $filter for your query.
$filter with $ifNull to keep the matching element followed by $size and $redact and compare result with 1 to keep and else remove the document.
db.col.aggregate(
[{
$match: {
"my_array.flag": {
$exists: true
}
}
}, {
$redact: {
$cond: {
if: {
$eq: [{
$size: {
$filter: {
input: "$my_array",
as: "array",
cond: {
$eq: [{
$ifNull: ["$$array.flag", null]
}, "$$array.flag"]
}
}
}
}, 1]
},
then: "$$KEEP",
else: "$$PRUNE"
}
}
}]
)
Use Aggregation framework in mongodb,
pipeline stage 1: Match ,
checking for some inner field existence,
{ "my_array.flag" : { $exists : true } }
pipleline stage 2: Project,
querying for the size of the filtered array,
{$project:{ "my_array":{$size:"$my_array"}}}
mongoshell Command ,
db.col.aggregate([{
$match: {
"my_array.flag": {
$exists: true
}
}
}, {
$project: {
"my_array": {
$size: "$my_array"
}
}
}])
Following the current answers about using aggregate, here's a working solution using aggregation:
db.col.aggregate([
{ $match : { "my_array.flag" : { $exists : true } } },
{ $unwind : "$my_array" },
{ $match : { "my_array.flag" : { $exists : true } } },
{ $group : { _id : "$_id", count_flags : { $sum : 1 } } },
{ $match : { "count_flags" : 1 } }
])
Here are the steps explained:
Filter to only documents that contain at least one flag field (this will use my index to dramatically reduce the number of documents I need to process)
Unwind the my_array array, to be able to process each element as a separate doc
Filter the separated docs from elements not containing flag
Group again by _id, and count the elements (that contain the flag field)
Filter by the new count_flags field
I don't really like this solution, it seems like a lot of steps to get something that should have basic support in a standard mongo find query.

Find Query - Filter by array size after $elemMatch

Is it possible to return records based on the size of the array after $elemMatch has filtered it down?
For example, if I have many records in a collection like the following:
[
{
contents: [
{
name: "yorkie",
},
{
name: "dairy milk",
},
{
name: "yorkie",
},
]
},
// ...
]
And I wanted to find all records in which their contents field contained 2 array items with their name field equal to "yorkie", how would I do this? To clarify, the array could contain other items, but the criteria is met so long as 2 of those array items have the matching field:value.
I'm aware I can use $elemMatch (or contents.name) to return records where the array contains at least one item matching that name, and I'm aware I can also use $size to filter based on the exact number of array items in the record's field. Is there a way that they can be both combined?
Not in a find query, but it can be done with an aggregation:
db.test.aggregate([
{ "$match" : { "contents.name" : "yorkie" } },
{ "$unwind" : "$contents" },
{ "$match" : { "contents.name" : "yorkie" } },
{ "$group" : { "_id" : "$_id", "sz" : { "$sum" : 1 } } }, // use $first to include other fields
{ "$match" : { "sz" : { "$gte" : 2 } } }
])
I interpreted
the criteria is met so long as 2 of those array items have the matching field:value
as meaning the criteria is met if at least 2 array items have the matching value in name.
I know this thread is old, but today you can just use find
db.test.find({
"$expr": {
"$gt": [
{
"$reduce": {
"input": "$contents",
"initialValue": 0,
"in": {
"$cond": {
"if": {
"$eq": ["$$this.name", 'yorkie']
},
"then": {
"$add": ["$$value", 1]
},
"else": "$$value"
}
}
}
},
1
]
}
})
The reduce will do the trick here, and will return the number of objects that match the criteria

Resources