MongoDB - How to update a specific property of a nested array element - mongodb

I have a collection with the following structure:
{
arrangements: [
{ displayName: "MRT.8" },
{ displayName: "MRT.10" },
{ displayName: "MRT.12" },
(...)
]
}
I want the substring MRT to be replaced with MOBILE, so the result will be as follows:
{
arrangements: [
{ displayName: "MOBILE.8" },
{ displayName: "MOBILE.10" },
{ displayName: "MOBILE.12" },
(...)
]
}
Following the solution for a similar problem on SO I did the following:
db.collection('releaseDocument').updateMany({"arrangements.displayName": {$regex: /MRT\..*/}}, [
{
$set: {
'arrangements.displayName': {
$concat: [
"MOBILE.",
{$arrayElemAt: [{$split: ["$displayName", "MRT."]}, 0]}
]
}
}
}
])
But that doesn't work because $ refers to the current document, not the nested array element. How can I achieve what I described above?

Seems you are working with Update with Aggregation Pipeline, you need $map operator.
For $arrayElementAt part, I think you need 1 as an index to take the second element.
Assume that the filter returns documents to be updated:
db.collection.update({
"arrangements.displayName": {
$regex: /MRT\..*/
}
},
[
{
$set: {
"arrangements": {
$map: {
input: "$arrangements",
in: {
$mergeObjects: [
"$$this",
{
displayName: {
$concat: [
"MOBILE.",
{
$arrayElemAt: [
{
$split: [
"$$this.displayName",
"MRT."
]
},
1
]
}
]
}
}
]
}
}
}
}
}
])
Sample Mongo Playground

Related

Update element in array with mongoose

I have an array with multiple objects in it (there can be more than two). Now I need to update an element (change verified to true) inside the object (e.g. an object with method = "app"). If the object doesn't exist yet, it should be recreated. Is there any way to handle this with Mongoose?
I have found a solution for updating, but it does not solve the problem when no object exists
const result = await User.updateOne({email},
{ $set: { "multifactors.$[elem].verified" : true } },
{ arrayFilters: [ { "elem.method": "app" } ] }
)
This is one way of doing it, using a pipelined update.
a. Check if the array contains the required object using $filter and $size.
b. If it's there, updates the matching object $map.
c. Else, append the new object to the array.
db.collection.update({},
[
{
"$set": {
"multifactors": {
$cond: {
if: {
$gt: [
{
$size: {
$filter: {
input: "$multifactors",
as: "factor",
cond: {
$eq: [
"$$factor.method",
"app"
]
}
}
}
},
0
]
},
then: {
$map: {
input: "$multifactors",
as: "factor",
in: {
$cond: {
if: {
$eq: [
"$$factor.method",
"app"
]
},
then: {
$mergeObjects: [
"$$factor",
{
verified: true
}
]
},
else: "$$factor"
}
}
}
},
else: {
$concatArrays: [
"$multifactors",
[
{
method: "app",
verified: true
}
]
]
}
}
}
}
}
])
Playground link.

MongoDB - Update nested document based on its current content

I have a simple collection with a document like this one:
{
_id: ObjectId('62b196e43581370007773393'),
name: 'John',
jobs: [
{
company: 'ACME',
type: 'programmer',
technologies: [
{
level: 1,
name: 'Java'
},
{
level: 3,
name: 'MongoDB'
}
]
}
]
}
I'd like to collect all technologies into a new field in the "job" sub-document for "programmers" jobs only, to achieve this effect:
(...)
jobs: [
{
it_technologies : ["Java", "MongoDB"]
(...)
}
]
(...)
The problem is that I do not know how to refer to and collect proper elements of the documents with this query:
db.runCommand({update: "employees",
updates: [
{
q: {},
u: { $set: { "jobs.$[j].it_technologies": "<how to collect all technologies in this job?>" } },
upsert: false,
multi: true,
arrayFilters: [{
$and:
[
{"j.type": "programmer"},
{"j.technologies": {$exists : true, $ne: []} }
]
}]
}
] });
Is it at all possible?
I'd be grateful for any clue!
Think that you need to work the update with the aggregation pipeline.
map - Iterate with the jobs array and return a new array.
1.1. $cond - Check the condition (Match the current document of jobs type and technology is not an empty array). If fulfilled, then update the document, else remains existing.
1.1.1. $mergeObjects - Merge current document with the document with it_technologies.
db.collection.update({},
[
{
$set: {
"jobs": {
$map: {
input: "$jobs",
in: {
$cond: {
if: {
$and: [
{
$eq: [
"$$this.type",
"programmer"
]
},
{
$ne: [
"$$this.technologies",
[]
]
}
]
},
then: {
$mergeObjects: [
"$$this",
{
it_technologies: "$$this.technologies.name"
}
]
},
else: "$$this"
}
}
}
}
}
}
],
{
upsert: false,
multi: true,
})
Sample Mongo Playground

How to use $addFields in mongo to add elements to just existing documents?

I am trying to add a new field to an existing document by using a combination of both $ifnull and $cond but an empty document is always added at the end.
Configuration:
[
{
line: "car",
number: "1",
category: {
FERRARI: {
color: "blue"
},
LAMBORGHINI: {
color: "red"
}
}
},
{
line: "car",
number: "2",
category: {
FERRARI: {
color: "blue"
}
}
}
]
Query approach:
db.collection.aggregate([
{
$match: {
$and: [
{ line: "car" },
{ number: { $in: ["1", "2"] } }
]
}
},
{
"$addFields": {
"category.LAMBORGHINI.number": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
"$number",
"$$REMOVE"
]
}
}
},
{
$group: {
_id: null,
CATEGORIES: {
$addToSet: "$category.LAMBORGHINI"
}
}
}
])
Here is the link to the mongo play ground:
https://mongoplayground.net/p/RUnu5BNdnrR
I tried the mentioned query but I still get that ugly empty set added at the end.
$$REMOVE will remove last field/key, from your field category.LAMBORGHINI.number the last field is number that is why it is removing number from the end, you can try another approach,
specify just category.LAMBORGHINI, if condition match then it will return object of current category.LAMBORGHINI and number object after merging using $mergeObjects
{
"$addFields": {
"category.LAMBORGHINI": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
{
$mergeObjects: [
"$category.LAMBORGHINI",
{ number: "$number" }
]
},
"$$REMOVE"
]
}
}
}
Playground

Using MongoDB Aggregation to merge two lists into an object

I'm using mongoDB and I have documents similar to the following
{
"files": ["Customers", "Items", "Contacts"],
"counts": [1354, 892, 1542],
...
}
And using an aggregation pipeline stage, I want to convert the above into something more like..
{
"file_info": [
{"file_name": "Customers", "record_counts": 1354},
{"file_name": "Items", "record_counts": 892},
{"file_name": "Contacts", "record_counts": 1542}
]
}
I've tried using $map, $reduce, and $arrayToObject but without any success. What operators can I use to get from where I currently am to where I need to be?
You can use $zip to combine two arrays and $map to get the new structure:
{
$project: {
file_info: {
$map: {
input: { $zip: { inputs: [ "$files", "$counts" ] } },
in: {
file_name: { $arrayElemAt: [ "$$this", 0 ] },
record_counts: { $arrayElemAt: [ "$$this", 1 ] },
}
}
}
}
}
Mongo Playground

Aggregation: adding fields to mongo document from another object

I have an array which looks like this
const posts = [{ _id: '1', viewsCount: 52 }, ...]
Which corresponds to mongodb documents in posts collection
{
_id: '1',
title: 'some title',
body: '....',
}, ...
I want to perform an aggregation which would result in documents fetched from the posts collection to have a viewsCount field. I'm not sure how I should form my aggregation pipeline:
[
{ $match: {} },
{ $addFields: { viewsCount: ?? } }
]
UPDATE
So far the following code almost does the trick:
[
{ $match: {} },
{ $addFields: { viewsCount: { $arrayElemAt: [posts, { $indexOfArray: [ posts, '$_id' ] } ] } } },
]
But viewsCount in this case turns to be an object, so I guess I need to add $project
UPDATE
I've found out one possible solution which is to use $addFields stage twice - overriding the first viewsCount
[
{ $match: {} },
{ $addFields: { viewsCount: { $arrayElemAt: [posts, { $indexOfArray: [ posts, '$_id' ] } ] } } },
{ $addFields: { viewsCount: '$viewsCount.viewsCount' } }
]
But is there a better/more concise solution?
UPDATE
This pipeline actually works correct:
[
{ $match: {} },
{ $addFields: { viewsCount: { $arrayElemAt: [posts, { $indexOfArray: [ postsIds, '$_id' ] } ] } } },
{ $addFields: { viewsCount: '$viewsCount.viewsCount' } }
]
I have updated the second stage by replacing posts with postsIds
To have a more concise solution (one-stage) you can use $let operator which lets you to define temporary variable that can be then used inside your expression, try:
db.posts.aggregate([
{ $addFields: {
viewsCount: {
$let: {
vars: { viewsCountObj: { $arrayElemAt: [posts, { $indexOfArray: [ posts, '$_id' ] } ] } },
in: "$$viewsCountObj.viewsCount"
}
} }
}
])