How to create objects inside Mongodb map - mongodb

I have a document that looks like this, I am using mongo $map to project fields inside the table and rename the key. I can't use $unwind due to some internal complexity.
{
"Table":[
{"Lookup":{
"CreatedBy":{
"id": "User001",
"Name":"UserName"
}
}
}]
}
The output I am expecting looks something like this
{
"Table":[
{"Lookup":{
"CreatedBy":"UserName"
}
}]
}
I am trying to achieve it with mongo $map but it is not supported
db.getCollection('TableDoc').aggregate([
{
"$project": {
"Table": {
"$map": {
"input": "$Table",
"in": {
"Lookup.CreatedAt": "$$this.Lookup.CreatedAt.Name",
}
}
}
}
}
])
Is there any other way to achieve this without using $unwind

This is supported with $map, but just not using "dotted field paths". Instead you use "absolute" object structures:
collection.aggregate([
{ "$addFields": {
"Table": {
"$map": {
"input": "$Table",
"in": {
"Lookup": {
"CreatedBy": "$$this.Lookup.CreatedBy.Name"
}
}
}
}
}}
])
Alternately if you have lots of fields in the objects you can use $mergeObjects where supported:
collection.aggregate([
{ "$addFields": {
"Table": {
"$map": {
"input": "$Table",
"in": {
"$mergeObjects": [
"$$this",
{
"Lookup": {
"CreatedBy": "$$this.Lookup.CreatedBy.Name"
}
}
]
}
}
}
}}
])
That makes more sense when an example shows more fields than your sample in the question does.

Related

Mongodb array projection

I have a set of documents in a mongo collection that have a schema like so:
{
"itemIds":{
"12341234-1234-1234-1234-123412341234": true,
"23452345-2345-2354-2345-234523452354": false,
"34563456-3456-3456-3456-345634563456": true
}
}
Because of our move to azure cognitive search, I need to use a mongo projection to get the data to look like this:
{
"itemIds": ["12341234-1234-1234-1234-123412341234",
"34563456-3456-3456-3456-345634563456"]
}
The properties with a false value should be excluded, and the properties with a true value should be converted to array values.
Option 1. Maybe something like this:
db.collection.aggregate([
{
"$addFields": {
"itemIds": {
"$filter": {
"input": {
"$objectToArray": "$itemIds"
},
"as": "item",
"cond": {
$eq: [
"$$item.v",
true
]
}
}
}
}
},
{
$project: {
_id: "$_id",
itemIds: "$itemIds.k"
}
}
])
Explained:
In the addFileds stage convert the object to array to get the key /values in the form k,v so you can filter only the true values.
In the project stage project the _id and itemIds.k values inside the itemIds array
Playground1
Option 2. Faster option via single stage: addFields/$map/$filter:
db.collection.aggregate([
{
"$addFields": {
"itemIds": {
"$map": {
"input": {
"$filter": {
"input": {
"$objectToArray": "$itemIds"
},
"as": "item",
"cond": {
$eq: [
"$$item.v",
true
]
}
}
},
"as": "it",
"in": "$$it.k"
}
}
}
}
])
Explained:
Map the filtered values from ObjectToArray to project in addFields only the necessary array values.
Playground2

How to rename mongodb field using regex expression

I'm trying to update the name of a field in the mongodb document using the regex expression on the name of that field but I can't, can someone help me to do it?
I have a person document that contains a field with the email of the person, the name of that field is like that "firstname_lastname#gmail.com" and I want to replace "_" character in all email fields by "_dot_".
Here is what I did
:
db.getCollection("person").updateMany(
{
{'email.'+'.*_.*': { $exists: true }
},
{
$rename:{'email.'+'.*_.*': 'email.'+'.*_dot_.*'
});
and here the structure of my document:
person {
name: { .... }
email:{
firstname_lastname1#gmail.com : {
.... other fields
},
firstname_lastname2#gmail.com : {
.... other fields
}
}
}
thanks in advance
As #Wernfried Domscheit suggested in the comment, you should change your schema to avoid using dynamic email address as field name.
Nevertheless, you can use $objectToArray to convert the emails into an array of k-v tuples. Then, $split them by _ and rejoin by _dot_. Finally do a $arrayToObject to convert back to original structure.
db.collection.aggregate([
{
"$addFields": {
"email": {
"$objectToArray": "$email"
}
}
},
{
"$addFields": {
"email": {
"$map": {
"input": "$email",
"as": "e",
"in": {
k: {
"$ltrim": {
"input": {
"$reduce": {
"input": {
"$split": [
"$$e.k",
"_"
]
},
"initialValue": "",
"in": {
"$concat": [
"$$value",
"_dot_",
"$$this"
]
}
}
},
"chars": "_dot_"
}
},
v: "$$e.v"
}
}
}
}
},
{
"$addFields": {
"email": {
"$arrayToObject": "$email"
}
}
}
])
Here is the Mongo Playground for your reference.

Update name of a key in nested mongo document

I did check on multiple platforms for the solution, however did not get the answer. Here is my problem:
I have a nested Mongo Document of the format:
{
_id: "xxxx",
"Type": <value>,
"Data" : {
"1": { <<< this key might differ for diff documents
"00": { <<< this key might differ for diff documents
"cont": "India"
},
"05": {
"cont": "India"
},
....
}
"32": {
"41": {
"cont": "India"
},
"44": {
"cont": "India"
},
....
}
}
}
I want to rename the key cont to country in all the documents in the collection, however as mentioned above the key 1, 00, 32 might differ for different documents hence I could not reach to a solution for this.
Is there something like:
db.collection.updateMany({Type: "abc"}, { $rename: { "Data.*.*.cont": "Data.*.*.country" }})
because the key cont resides at the third level after the key Data
Can someone please help me out here?
Thanks!
Using dynamic value as field name is generally considered as an anti-pattern. Nevertheless, you can still use $objectToArray to convert the nested objects into k-v tuples and manipulate them. Then converted it back using $arrayToObject
db.collection.update({},
[
{
"$addFields": {
"arr": {
// first conversion
"$objectToArray": "$Data"
}
}
},
{
"$addFields": {
"arr": {
// second conversion
"$map": {
"input": "$arr",
"as": "a",
"in": {
k: "$$a.k",
v: {
"$objectToArray": "$$a.v"
}
}
}
}
}
},
{
"$addFields": {
"arr": {
"$map": {
"input": "$arr",
"as": "a",
"in": {
k: "$$a.k",
v: {
"$map": {
"input": "$$a.v",
"as": "a2",
"in": {
k: "$$a2.k",
// rename the field `country`
v: {
country: "$$a2.v.cont"
}
}
}
}
}
}
}
}
},
{
"$addFields": {
"arr": {
"$map": {
"input": "$arr",
"as": "a",
"in": {
k: "$$a.k",
v: {
// 1st backward conversion
"$arrayToObject": "$$a.v"
}
}
}
}
}
},
{
"$project": {
_id: 1,
Type: 1,
"Data": {
// 2nd backward conversion
"$arrayToObject": "$arr"
}
}
}
])
Here is the Mongo playground for your reference.

How to check if a key exists in a mongodb object where the key is a value of some another field in the document while doing aggregation?

First of all I know we can check if a key exists using the dot operator but in my case it is not working and I dont know why.
So far in the aggregation pipeline I have the following records.
{
"my_key":"1234"
"data":{
1234:"abc"
4567:"xyz"
}
}
{
"my_key":"6666"
"data":{
1234:"abc"
4567:"xyz"
}
}
I want to return the document where the my_key value does not exists in the data object. So according to the above example it should return the 2nd document.
I was trying using the $match operator as following but it does not seem to work.
$match :
{
"data.$my_key":{$exists:false}
}
This does not work and I dont get why :(
Is it because the my_key value is a string and the keys in the data object are not strings?
playground
db.collection.aggregate([
{
"$project": {//Reshape the data
"data": {
"$objectToArray": "$data"
},
"my_key": 1
}
},
{
"$unwind": "$data"
},
{
"$match": {//matching
"$expr": {
"$eq": [
"$data.k",
"$my_key"
]
}
}
}
])
Another way
Wihtout unwind
db.collection.aggregate([
{
"$project": {
"data": {
"$objectToArray": "$data"
},
"my_key": 1
}
},
{
$project: {
"output": {
"$map": {
"input": "$data",
"as": "data",
"in": {
"$eq": [
"$$data.k",
"$my_key"
]
}
}
},
"data": 1,
"my_key": 1
}
},
{
$match: {
output: true
}
}
])
If you need original format of data, you can add the below as last stage
{
$project: {
"data": {
"$arrayToObject": "$data"
},
"my_key": 1
}
}

How can I concat an array of integer in MongoDB aggregation method?

I have a field in mongodb document which is an array of integer as like:
import_ids=[5200, 4710, 100]
I want this array as double ## separated string, So that expected result would be
import_hashed="5200##4710##100"
I have tried with following code in $project pipeline of aggregation method.
{
$projct:{
import_hashed:{
$reduce:{
input:"$import_ids",
initialValue:"",
in:{$concat:["$$value", "##", "$$this"]}
}
}
}
}
But no result found and no erros too!
You can try below aggregation
You can use $toLower aggregation to convert integer to string or $toString if you are using mongodb 4.0
db.collection.aggregate([
{ "$project": {
"import_hashed": {
"$let": {
"vars": {
"key": {
"$reduce": {
"input": "$import_ids",
"initialValue": "",
"in": { "$concat": ["$$value", "##", { "$toLower": "$$this" }] }
}
}
},
"in": { "$substrCP": ["$$key", 2, { "$strLenCP": "$$key" }] }
}
}
}}
])
Output
{ "import_hashed": "5200##4710##100 }