Mongo $addField For nested structured data - mongodb

I have a result like below, Is there any way I can change the _id value which is the same for the all nested data. I want to rename each id with some other name not simply _id
{
"items": [
{
"_id": "5bd1a3da2901d60f9edc4d10",
"addons": [
{
"_id": "5bd1a3da2901d60f9edc4d96",
"options": [
{
"_id": "5bd1a3da2901d60f9edc4d98",
}
],
}
]
}
]
}
Here in the above code, I tried $addfields attribute but it works fine only for the topmost nesting
Here is the code I've tried:
Items.aggregate([
{
$match: { _id: '123'}
},
{ $addFields: { item_id: "$_id" ,
"addons.addon_id" : "$addons._id" ,
"addons.options.options_id" : "$addons.options._id"
}
},
{
$project: {
_id: 0
}
},
],
This code works fine and added item_id in the root element but added addon and option_id as an array value,
{
"item_list": [
{
"_id": "5bd1a3da2901d60f9edc4d10",
"addons": [
{
"_id": "5bd1a44b2901d60f9edc4d9a",
"options": [
{
"_id": "5bd1a44b2901d60f9edc4d9b",
"options_id": [
[
"5bd1a44b2901d60f9edc4d9b"
]
]
}
],
"addon_id": [
"5bd1a44b2901d60f9edc4d9a"
]
}
]
}
]
}
I got something like this a nested array of addon_id and options_id, I want it to be a simple string and not array,
{
"item_list": [
{
"_id": "5bd1a3da2901d60f9edc4d10",
"addons": [
{
"_id": "5bd1a44b2901d60f9edc4d9a",
"options": [
{
"_id": "5bd1a44b2901d60f9edc4d9b",
"options_id": "5bd1a44b2901d60f9edc4d9b"
}
],
"addon_id": "5bd1a44b2901d60f9edc4d9a"
}
]
}
]
}
Am expecting the above result.

Related

mongodb update document from first element of array

Consider a collection client with the following documents:
[
{
"id": 1,
"Name": "Susie",
"ownership" : {
"ownershipContextCode" : "C1"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_1",
"clientId": "11"
}
]
},
{
"id": 2,
"Name": "John",
"ownership" : {
"ownershipContextCode" : "C2"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_2",
"clientId": "22"
}
]
}
]
I am attempting to set a field (ownershipClientCode) as the first element of the clientIds array.
The result should be like that:
[
{
"id": 1,
"Name": "Susie",
"ownership" : {
"ownershipContextCode" : "C1",
"ownershipClientCode" : "clientClusterCode_1"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_1",
"clientId": "11"
}
],
},
{
"id": 2,
"Name": "John",
"ownership" : {
"ownershipContextCode" : "C2",
"ownershipClientCode" : "clientClusterCode_2"
},
"clientIds": [
{
"clientClusterCode": "clientClusterCode_2",
"clientId": "22"
}
],
}
]
I'm using this query but I can't get sub object from the first element in the array
db.collection.aggregate([
{
$addFields: {
"Last Semester": {
"$arrayElemAt": [
"$clientIds",
0
]
}
}
}
])
This query add the all object but I want only the field (clientClusterCode).
Some thing like that
db.collection.aggregate([
{
$addFields: {
"Last Semester": {
"$arrayElemAt": [
"$clientIds",
0
].clientClusterCode
}
}
}
])
I'm using mongodb 4.0.0
You're very close: https://mongoplayground.net/p/HY1Pj0P4z12
db.collection.aggregate([
{
$addFields: {
"ownership.ownershipClientCode": {
"$arrayElemAt": [
"$clientIds.clientClusterCode",
0
]
}
}
}
])
You can use the dot notation within the $arrayElemAt as well as when you defining the field name.
To directly set the field, do something like this (use aggregation in the update): https://mongoplayground.net/p/js-usEJSH_A
db.collection.update({},
[
{
$set: {
"ownership.ownershipClientCode": {
"$arrayElemAt": [
"$clientIds.clientClusterCode",
0
]
}
}
}
],
{
multi: true
})
Note: The second method to update needs to be an array, so that it functions as an pipeline.

MongoDB: get documents by last element value in nested array

This question is slightly different from others since I need to get the whole documents and not just specific fields.
I need to filter documents(all of the document, not just specific fields), according to the last elements value of a nested array. (doc.array[i].innerArray[innerArray.length - 1].desiredField)
Documents are looking like this:
[
{
"_id": 0,
"matches": [
{
"name": "match 1",
"ids": [
{
"innerName": "1234"
},
{
"innerName": "3"
}
]
}
]
},
{
"_id": 1,
"matches": [
{
"name": "match 5",
"ids": [
{
"innerName": "123"
},
{
"innerName": "1"
}
]
},
{
"name": "match 5",
"ids": [
{
"innerName": "1"
},
{
"innerName": "1234"
},
]
},
]
}
]
So if we filter according to innerName = '1234', this is the result:
{
"_id": 1,
"matches": [
{
"name": "match 5",
"ids": [
{
"innerName": "123"
},
{
"innerName": "1"
}
]
},
{
"name": "match 5",
"ids": [
{
"innerName": "1"
},
{
"innerName": "1234"
},
]
}
One option is:
db.collection.find({
$expr: {
$in: [
"1234",
{$reduce: {
input: "$matches",
initialValue: [],
in: {$concatArrays: ["$$value", [{$last: "$$this.ids.innerName"}]]}
}
}
]
}
})
See how it works on the playground example
Another option:
db.collection.aggregate([
{
$match: {
$expr: {
$gt: [
{
$size: {
$filter: {
input: "$matches",
cond: {
$in: [
{
$last: "$$this.ids.innerName"
},
[
"1234"
]
]
}
}
}
},
0
]
}
}
}
])
Explained:
Match only documents where size of array is >0 for those who has "1234" in last nested array element.
Playground:

MongoDB aggregate query - How to make array of array list in to single array

I have a document like this:
this is my example data is attached below,
[
{
"_id": ObjectId("6218b836405919280c209f7e"),
"projectId": ObjectId("6218a31f405919280c209e18"),
"accountId": ObjectId("621888e852bd8836c04b8f82"),
"personalIdRoot": [
{
"_id": ObjectId("6221e7514195b43f24c9953f"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c99540"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdFill": [
{
"_id": ObjectId("6221e7514195b43f24c9953d"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953e"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdCap": [
{
"_id": ObjectId("6221e7514195b43f24c9953b"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953c"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"aps": ObjectId("6218bc18405919280c209f8e"),
}
]
i used this example data for my aggregate query.
my aggregate query is attached below:
please find below code:
db.getCollection('funds').aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$facet: {
"results": [
{
$group: {
_id: 0,
values: { "$addToSet": "$personalIdRoot.welderId" },
values: { "$addToSet": "$personalIdFill.welderId" },
values: { "$addToSet": "$personalIdCap.welderId" }
}
},
],
}
}
])
my result:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
[
ObjectId("6218b48c405919280c209f6c")
],
[
ObjectId("6218b2e4405919280c209f68")
],
[
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
]
}
]
}
But i need my result in a single query:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
}
]
}
I have a result of array of array collections.
but i need it in a single array. like above result
Thanks in advance.
It sounds like what you want is:
db.collection.aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$project: {
values: {
$setUnion: [
"$personalIdCap.personalId",
"$personalIdFill.personalId",
"$personalIdRoot.personalId"
]
}
}
}
])
See how it works on the playground example

MongoDB - How to find and update elements in a nested array

Here is the collection:
db.employees.insertMany([
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"ART",
]
},
{
"name": "HELLO",
"subcategory": [
"GG",
"ART",
]
},
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"SHORE",
]
}
]
}
},
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"EDUCATION",
"HELLO",
]
}
]
}
},
{
"data": {
"category": [
{
"name": "HELLO",
"subcategory": [
"GG",
"ART",
]
}
]
}
}
]);
What I want is to locate the elements in 'category' with a 'subcategory' that contains 'EDUCATION' and replace 'EDUCATION' with another string, let's say 'SPORTS'.
I tried a couple of commands but nothing really did the job:
db.employees.updateMany({
"data.category.subcategory": "EDUCATION"
},
{
"$set": {
"data.category.$": {
"subcategory": "SPORTS"
}
}
})
What I saw is that it doesn't update the element by replacing it and it doesn't replace every element that meets the criteria.
Think that MongoDB Update with Aggregation Pipeline fulfills your scenario.
$set - Set data.category value.
1.1. $map - Iterate each element in data.category and return an array.
1.1.1. $mergeObjects - Merge the current document with the document with subcategory field from 1.1.1.1.
1.1.1.1 $map - Iterate each value from the subcategory array. With $cond to replace the word EDUCATION with SPORTS if fulfilled, else use existing value ($$this).
db.employees.updateMany({
"data.category.subcategory": "EDUCATION"
},
[
{
"$set": {
"data.category": {
$map: {
input: "$data.category",
in: {
$mergeObjects: [
"$$this",
{
subcategory: {
$map: {
input: "$$this.subcategory",
in: {
$cond: {
if: {
$eq: [
"$$this",
"EDUCATION"
]
},
then: "SPORTS",
else: "$$this"
}
}
}
}
}
]
}
}
}
}
}
]
Sample Mongo Playground
Here's another way to do it using "arrayFilters".
db.collection.update({
"data.category.subcategory": "EDUCATION"
},
{
"$set": {
"data.category.$[].subcategory.$[elem]": "SPORTS"
}
},
{
"arrayFilters": [
{ "elem": "EDUCATION" }
],
"multi": true
})
Try it on mongoplayground.net.

How to flatten nested array (parent->child) tree (of any depth) in MongoDB aggregation?

I have a nested tree level structure of item->items that looks something like this
{ "id":"1",
"type":"panel",
"items": [
{ "id":"2", "type":"input", },
{ "id":"4", "type":"group", "items": [
{ "id":"5", "type":"input" },
{ "id":"6", "type":"panel", "items":[...] },
]
}
]}
I'm looking to flatten the tree and get a single array list of all items like this:
[ { "id":"1", "type":"panel", },
{ "id":"2", "type":"input", },
{ "id":"4", "type":"panel", },
{ "id":"5", "type":"input", },
...
]
Is there a generic way to flatten the tree (that would work for any depth level)?
All answers I found here just manually $unwind each child level (I can't predict the number of levels) nor do I have reference to parent to use traverse with $graphLookup.
Or something like {'$*.items'}?
MQL doesn't have functions, so we can't recur, if we find a array.
Maybe there is a way to do it with MQL and 1 query.
But there is way to do it fast with more than 1 query.
The bellow example is 1 level/query.
With small change it can do 10 level/query or 100 level/query etc
so only 1 query will be needed, but we will do some redadent attempts to flatten arrays even if they are empty.
First 1 small modification.
Add 1 field on all documents "all-items": [{"id": "$id","type": "$type"}]
and removed the top level "id" and "type". Like bellow
aggregate(
[ {
"$project" : {
"all-items" : [ {
"id" : "$id",
"type" : "$type"
} ],
"items" : 1
}
} ]
)
Modified data
[
{
"all-items": [
{
"id": "1",
"type": "panel"
}
],
"items": [...like it was...]
}
]
And now we can do it with multiple queries 1 per/level
First call, code example
Second call, code example, with the result of first call
Third call we dont need, while will be false.
In each call we do $out, and we aggregate on the result of previous call.
while(there_is_1_document_with_not_empty_items[]) (send 1 find query)
db.collection.aggregate([
{
"$addFields": {
"level-nlevel": {
"$reduce": {
"input": "$items",
"initialValue": [
[],
[]
],
"in": {
"$let": {
"vars": {
"info": "$$value",
"i": "$$this"
},
"in": {
"$let": {
"vars": {
"level": {
"$arrayElemAt": [
"$$info",
0
]
},
"nlevel": {
"$arrayElemAt": [
"$$info",
1
]
}
},
"in": [
{
"$concatArrays": [
"$$level",
[
{
"id": "$$i.id",
"type": "$$i.type"
}
]
]
},
{
"$cond": [
{
"$isArray": [
"$$i.items"
]
},
{
"$concatArrays": [
"$$nlevel",
"$$i.items"
]
},
"$$nlevel"
]
}
]
}
}
}
}
}
}
}
},
{
"$project": {
"all-items": {
"$concatArrays": [
"$all-items",
{
"$arrayElemAt": [
"$level-nlevel",
0
]
}
]
},
"items": {
"$arrayElemAt": [
"$level-nlevel",
1
]
}
}
}
])
This flattens per document(no $unwind is used), if you want to flatten all collection, $unwind one time after the while ends the $all-items.
There is not a mongodb query language aggregation sage that supports flatting to an unknown depth, but $function would allow you to execute a method against the document
here is a javascript example:
var fn = function(items) {
var ret = [];
var toCheck = [...items];
while (toCheck.length) {
var nxtToCheck = [];
for (var item of toCheck) {
ret.push({ id: item.id, type: item.type });
nxtToCheck.push(...(item.items || []));
}
toCheck = nxtToCheck;
}
return ret;
}
db.myCol.aggregate([
{ $match: {} },
{ $addFields: { allItems: { $function: { body: fn, args: ["$items"], lang: "js" } } } }
]);