MongoDB - Update nested document based on its current content - mongodb

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

Related

How to add new field conditionally in MongoDB?

I have an aggregation pipeline in which i want to add new field based on certain condition. My pipeline is like this
[
{ // match stage
$or:[
{
$and: [
{placement: {'$nin': [-1,-2]}},
{contract_proposal_metadata : {$exists: true}}
]
},
{
risk_info_request_metadata: {$exists: true}
}
]
}
]
Now i want to add a new field record_type based on the condition that if contract_proposal_metadata exists so record type will be 'renewal' and if risk_info_request_metadata is exists then record_type will be request.
How can i achieve this?
You are not adding new field conditionally. You are always adding the field, just with different values.
There is $cond operator which returns 1 of 2 values depending on condition in the first argument.
You already know $exist for the $match stage, and the equivalent operator to use in aggregation expression is $type
[
{ // match stage
.....
},
{ // adding the field
$addFields: {
record_type: { $cond: {
if: { $eq: [ { $type: "$contract_proposal_metadata" }, "missing" ] },
then: "request",
else: "renewal"
} }
}
}
]
You need to use aggregate update
db.collection.update({
placement: { //Your match goes here
"$nin": [
-1,
-2
]
},
},
[
{
$set: {
status: {
$switch: {
branches: [
{ //update condition goes here
case: {
$ifNull: [
"$contract_proposal_metadata",
false
]
},
then: "renewal"
},
{
case: {
$ifNull: [
"$risk_info_request_metadata",
false
]
},
then: "request"
},
],
default: ""
}
}
}
}
],
{
multi: true
})
It supported from mongo 4.2+
$exists cannot be used, hence $ifnull used
playground

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

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

MongoDB Aggregation project field to be conditional value

I try to project my latest aggregation artifact, into a new one, with an additional field, whose value should be conditional.
My latest artifact of the aggregation pipeline is:
[
server: {
_id: 6108d87b9255993b26796ca9,
version: '2.0.366',
name: 'aaaaaa',
variables: [
{
key: 'aKey',
value: 'aVal',
},
{
key: 'bKey',
value: 'bVal',
},
{
key: 'cKey',
value: 'cVal',
}
],
__v: 0
}
]
So basically I want to project the additional artifact with an additional field whose name is bucketName.
I want to check in the variables array, whether there is an item, whose key is pre-known of value aKey. If so, the result of the projected bucketName would be the value field: aVal. If could not find, value would be NOT_FOUND.
So the result of the sample above would be:
[
server: {
_id: 6108d87b9255993b26796ca9,
version: '2.0.366',
name: 'aaaaaa',
variables: [
{
key: 'aKey',
value: 'aVal',
},
{
key: 'bKey',
value: 'bVal',
},
{
key: 'cKey',
value: 'cVal',
}
],
bucketName: 'aVal',
__v: 0
}
]
My try was:
{
$project: {
bucketName: {
$cond: {
if: {
$eq : ['$server.variables.key', 'aKey'],
},
then: '$server.variables.value',
else: 'NOT_FOUND',
},
},
},
},
But that just didn't do the query correctly.
You should use $in operator because this variables key is array:
Then, use also $filter within the then to extract that value.
db.collection.aggregate([
{
"$set": {
"bucketName": {
"$cond": {
"if": {
"$in": [
"aKey",
"$server.variables.key"
]
},
"then": {
"$filter": {
"input": "$server.variables",
"as": "variable",
"cond": {
"$eq": [
"$$variable.key",
"aKey"
]
}
},
},
"else": "NOT_FOUND"
}
}
},
},
{
"$unwind": "$bucketName",
},
{
"$project": {
"bucketName": "$bucketName.value",
"server": 1
}
}
])
you can check $in condition because server.variables.key will return an array of string
$indexOfArray to get index of matching key element from array server.variables.key
$arrayElemAt to select value from server.variables.value by about returned index
db.collection.aggregate([
{
$project: {
bucketName: {
$cond: {
if: { $in: ["aKey", "$server.variables.key"] },
then: {
$arrayElemAt: [
"$server.variables.value",
{ $indexOfArray: ["$server.variables.key", "aKey"] }
]
},
else: "NOT_FOUND"
}
}
}
}
])
Playground
Your $server.variables.key is an array, is that why $eq fails, you are comparing an string with an array. You need to use $in. Check this example.
But also there is a problem, as $server.variables.key is an array with all keys values, the bucketName variable will be an array too.
You have to use the string aKey into then stage like this
db.collection.aggregate({
"$set": {
"bucketName": {
"$cond": {
"if": {
"$in": [
"aKey",
"$server.variables.key"
]
},
"then": "aKey",
"else": "NOT_FOUND"
}
}
}
})

How to use $elemMatch query in mongodb

I tried to filter the data using $elemmatch but it's not correctly working.
My Scenario
Prod Table
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
},
{
id:3,
name:false
}
]
}
]
Query
db.Prod.find(
{"product.name": true},
{_id: 0, product: {$elemMatch: {name: true}}});
I got Output
[
{
id:1.
product:[
{
id:1,
name:true
}
]
}
]
Excepted Output
[
{
id:1.
product:[
{
id:1,
name:true
},
{
id:2,
name:true
}
]
}
]
How to achieve this Scenario and I referred this Link Retrieve only the queried element in an object array in MongoDB collection and I tried all the answers in this link mentioned. but Still it's not working can give an example query.
Hmm, you probably didn't try the aggregation provided in referenced link, 'cause it perfectly works.
db.collection.aggregate([
{
$match: {
"product.name": true
}
},
{
$project: {
product: {
$filter: {
input: "$product",
as: "prod",
cond: {
$eq: [
"$$prod.name",
true
]
}
}
}
}
}
])
Will output :
[
{
"_id": 1,
"product": [
{
"id": 1,
"name": true
},
{
"id": 2,
"name": true
}
]
}
]
Here's the example.
EDIT :
From the doc :
Usage Considerations
Both the $ operator and the $elemMatch operator project the first
matching element from an array based on a condition.
Considering this, you cannot achieve what you need with a find query, but only with an aggregation query.

Mongo project test if value is in array

In a project step of a mongo aggregation, I'd like to create a boolean field like:
{
$project: {
isInArray: { $cond: [
{ $in: ['$_id', ids] },
{ $const: true },
{ $const: false },
] },
},
},
but this is failing with
invalid operator $in
I could not find documentation on the correct syntax
You can use $setIsSubset operator
db.people.aggregate([
{ $project: {
isInArray: { $cond: [ {$setIsSubset: [['$_id'], ids]}, true, false ] }
}}
])