kind of struggling around with a mongodb query. My mongo database has following structure:
name: String
test: String
competences: [{name: String, code: String, value: Float}]
subcompetences: [{name: String, code: String, value: Float}]
My query looks like this:
async function getAggregatedDataForCompetences(filter, category) {
return await getCollection('competences').aggregate([
{$match: { $or: filter }},
{$unwind: "$competences" },
{$group: {
_id: "$_id",
code: { $first: "$competences.code" },
name: { $first: "$competences.name"},
avgValue: { $avg: "$competences.value" },
subcompetences: { $first: "$subcompetences"},
}
},
{$unwind: "$subcompetences" },
{$group: {
_id: "$subcompetences.code",
code: { $first: "$subcompetences.code" },
name: { $first: "$subcompetences.name"},
avgValue: { $avg: "$subcompetences.value" },
}}
]).toArray();
}
What am I trying to do is unwind the first (competences) array for all elements, group them by and calculate the average value for each item. The same procedure is repeated for the following subcompetences array of objects. As a result I'm getting only the average values for the last subcompetences array. Do you have any idea how can I reach the following result:
{
competences: [{name: String, code: String, avgValue: Float}],
subcompetences: [{name: String, code: String, avgValue: Float}]
}
$facet to the rescue -- the "multigroup" operator. Given input like this:
var r =
[
{
"_id" : 0,
"name": "N1",
"competences": [
{name: "AAA", code: "A", value: 1.1},
{name: "BBB", code: "B", value: 2.2},
{name: "CCC", code: "C", value: 3.3}
],
"subcompetences": [
{name: "DDD", code: "D", value: 4.4},
{name: "EEE", code: "E", value: 5.5},
{name: "FFF", code: "F", value: 6.6}
]
}
,{
"_id" : 1, "name": "N2",
"competences": [
{name: "AAA", code: "A", value: 9.9},
{name: "BBB", code: "B", value: 8.8},
{name: "KKK", code: "K", value: 11.11}
],
"subcompetences": [
{name: "FFF", code: "F", value: 4.9},
{name: "GGG", code: "G", value: 6.7}
]
}
];
then $facet will allow you to do two groups "in parallel". Indeed, you can do two or more complete pipelines at the same time (some restrictions apply):
db.foo.aggregate([
{$facet: {
"avg_competences": [
{$unwind: "$competences"}
,{$group: {_id: "$competences.code",
name: {$first: "$competences.name"},
count: {$sum: 1},
avgval: {$avg: "$competences.value"},
}}
]
,"avg_subcompetences": [
{$unwind: "$subcompetences"}
,{$group: {_id: "$subcompetences.code",
name: {$first: "$subcompetences.name"},
count: {$sum: 1},
avgval: {$avg: "$subcompetences.value"},
}}
]
}
}
// The output of the stage above will be a *single* doc with two fields,
// avg_competence and avg_subcompetences. Let's add more fields to this doc!
,{$addFields: {N: {$reduce: {
input: {$concatArrays: ["$avg_competences","$avg_subcompetences"]},
initialValue: 0,
in:{$sum: [ "$$value", "$$this.count"]}
}}
}}
]);
to yield:
{
"avg_competences" : [
{
"_id" : "K",
"name" : "KKK",
"count" : 1,
"avgval" : 11.11
},
{
"_id" : "C",
"name" : "CCC",
"count" : 1,
"avgval" : 3.3
},
{
"_id" : "B",
"name" : "BBB",
"count" : 2,
"avgval" : 5.5
},
{
"_id" : "A",
"name" : "AAA",
"count" : 2,
"avgval" : 5.5
}
],
"avg_subcompetences" : [
{
"_id" : "G",
"name" : "GGG",
"count" : 1,
"avgval" : 6.7
},
{
"_id" : "F",
"name" : "FFF",
"count" : 2,
"avgval" : 5.75
},
{
"_id" : "E",
"name" : "EEE",
"count" : 1,
"avgval" : 5.5
},
{
"_id" : "D",
"name" : "DDD",
"count" : 1,
"avgval" : 4.4
}
],
"N" : 11
}
Related
here, I have carrier array in MongoDB, which has 1000000 elements,how to sort carrier array by alphanumerically on the name field
{
"_id" : 1,
"arraySize" : 1000000,
"carrier" : [
{
"name" : "AB"
},
{
"name" : "AD"
},
{
"name" : "CD"
},
{
"name" : "DC"
},
{
"name" : "ZY"
}
]
}
Try this:
db.collection.aggregate([
{$unwind: "$carrier"},
{$sort: {"carrier.name": 1}},
{$group: {_id: "$_id", carrier: {$push: "$carrier"}}}
])
Mongo Playground
I need to change the structure of complex json like structure consists of objects and arrays .
Let's say the original object is dataObj
const dataObj = {
name: "flare",
children: [
{
name: "analytics",
desc : "A",
children: [
{
name: "cluster",
desc : "AB",
children: [
{
name: "AgglomerativeCluster",
desc : "ABC",
value: 3938,
count:39
},
{
name: "CommunityStructure",
desc : "ABCD",
value: 3812,
count:38
},
]
},
{
name: "graph",
desc : "ABCDE",
children: [
{
name: "BetweennessCentrality",
desc : "AHF",
value: 3534,
count:39
},
{
name: "LinkDistance",
desc : "AmH",
value: 5731,
count:39
},
]
},
{
name: "optimization",
desc : "Ashg",
children: [
{
name: "AspectRatioBanker",
desc : "Avnvs",
value: 7074,
count:39
}
]
}
]
},
{
name: "animate",
desc : "Amvs",
children: [
{
name: "Easing",
desc : "Amnvs",
value: 17010,
count:39
},
{
name: "FunctionSequence",
desc : "Abnvs",
value: 5842 ,
count:39
},
]
},
]
};
I need to get the last child node which consists of keys "value" and "count"
for eg :
children: [
{
name: "AgglomerativeCluster",
desc : "ABC",
value: 3938,
count:39
},
{
name: "CommunityStructure",
desc : "ABCD",
value: 3812,
count:38
},
]
This one is the last leaf node with keys "value" and "count" which I need to restructure and make the child node in this format
children: [
{
name: "AgglomerativeCluster",
desc : "ABC",
children : [
{
name: "AgglomerativeCluster",
desc : "ABC",
value: 3938,
count:39
}
]
},
{
name: "CommunityStructure",
desc : "ABCD",
children : [
{
name: "CommunityStructure",
desc : "ABCD",
value: 3812,
count:38
}
]
},
]
As you can see this child node again should have a children key inside which I need to pass the value and count keys with the same "name" and "desc" keys . I couldn't get the logic , it looks super complex anyone could solve this ?
Expected result :
const result = {
name: "flare",
children: [
{
name: "analytics",
desc : "A",
children: [
{
name: "cluster",
desc : "AB",
children: [
{
name: "AgglomerativeCluster",
desc : "ABC",
children : [
{
name: "AgglomerativeCluster",
desc : "ABC",
value: 3938,
count:39
}
]
},
{
name: "CommunityStructure",
desc : "ABCD",
children : [
{
name: "CommunityStructure",
desc : "ABCD",
value: 3812,
count:38
}
]
},
]
},
{
name: "graph",
desc : "ABCDE",
children: [
{
name: "BetweennessCentrality",
desc : "AHF",
children : [
{
name: "BetweennessCentrality",
desc : "AHF",
value: 3534,
count:39
}
]
},
{
name: "LinkDistance",
desc : "AmH",
children : [
{
name: "LinkDistance",
desc : "AmH",
value: 5731,
count:39
}
]
},
]
},
{
name: "optimization",
desc : "Ashg",
children: [
{
name: "AspectRatioBanker",
desc : "Avnvs",
children : [
{
name: "AspectRatioBanker",
desc : "Avnvs",
value: 7074,
count:39
}
]
}
]
}
]
},
{
name: "animate",
desc : "Amvs",
children: [
{
name: "Easing",
desc : "Amnvs",
children : [
{
name: "Easing",
desc : "Amnvs",
value: 17010,
count:39
}
]
},
{
name: "FunctionSequence",
desc : "Abnvs",
children : [
{
name: "FunctionSequence",
desc : "Abnvs",
value: 5842 ,
count:39
}
]
},
]
},
]
};
You could restructure the last object.
function restructure(object) {
return object.children
? Object.assign({}, object, { children: object.children.map(restructure) })
: { name: object.name, desc: object.desc, children: [object] };
}
var data = { name: "flare", children: [{ name: "analytics", desc: "A", children: [{ name: "cluster", desc: "AB", children: [{ name: "AgglomerativeCluster", desc: "ABC", value: 3938, count: 39 }, { name: "CommunityStructure", desc: "ABCD", value: 3812, count: 38 }] }, { name: "graph", desc: "ABCDE", children: [{ name: "BetweennessCentrality", desc: "AHF", value: 3534, count: 39 }, { name: "LinkDistance", desc: "AmH", value: 5731, count: 39 }] }, { name: "optimization", desc: "Ashg", children: [{ name: "AspectRatioBanker", desc: "Avnvs", value: 7074, count: 39 }] }] }, { name: "animate", desc: "Amvs", children: [{ name: "Easing", desc: "Amnvs", value: 17010, count: 39 }, { name: "FunctionSequence", desc: "Abnvs", value: 5842, count: 39 }] }] },
result = restructure(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do this using recursive function that will pass through index of the element in children array and also the parent so you can use splice method.
const dataObj = {"name":"flare","children":[{"name":"analytics","desc":"A","children":[{"name":"cluster","desc":"AB","children":[{"name":"AgglomerativeCluster","desc":"ABC","value":3938,"count":39},{"name":"CommunityStructure","desc":"ABCD","value":3812,"count":38}]},{"name":"graph","desc":"ABCDE","children":[{"name":"BetweennessCentrality","desc":"AHF","value":3534,"count":39},{"name":"LinkDistance","desc":"AmH","value":5731,"count":39}]},{"name":"optimization","desc":"Ashg","children":[{"name":"AspectRatioBanker","desc":"Avnvs","value":7074,"count":39}]}]},{"name":"animate","desc":"Amvs","children":[{"name":"Easing","desc":"Amnvs","value":17010,"count":39},{"name":"FunctionSequence","desc":"Abnvs","value":5842,"count":39}]}]}
function update(data, index = null, parent = null,) {
if(data.children) {
data.children.forEach((c, i) => {
update(c, i, data.children)
})
}
else {
const {name, desc} = data;
const object = {name, desc, children: [{...data}]}
parent.splice(index, 1, object)
}
}
update(dataObj);
console.log(dataObj)
I have this object called grouped:
{ '1':
[ { id: 1,
name: 'John Doe',
supplierName: 'Sup1',
count: '21' } ],
'2':
[ { id: 2,
name: 'Jane Doe',
supplierName: 'Sup1',
count: '95' } ],
'3':
[ { id: 3,
name: 'Paul',
supplierName: 'Sup1',
count: '2' },
{ id: 3,
name: 'Paul',
supplierName: 'Sup2',
count: '1' }
]
}
I create a users object with this:
let users = _.map(grouped, userArray => ({
id: userArray[0].id,
name: userArray[0].name,
suppliers: _.reduce(userArray, (accumulated, supplierObj) => {
if (supplierObj.supplierName) {
accumulated.push({
title: supplierObj.supplierName,
});
}
return accumulated;
}, []),
}));
It currently looks like this:
{
"users": [
{
"id": 1,
"name": "John Doe",
"suppliers": [
{
"title": "Sup1"
}
]
},{
"id": 2,
"name": "Jane Doe",
"suppliers": [
{
"title": "Sup1"
}
]
},{
"id": 3,
"name": "Paul",
"suppliers": [
{
"title": "Sup1"
},{
"title": "Sup2"
}
]
}
]
}
I now want to add count in a way that my output is like this:
{
"users": [
{
"id": 1,
"name": "John Doe",
"count": "21",
"suppliers": [
{
"title": "Sup1"
}
]
},{
"id": 2,
"name": "Jane Doe",
"count": "95",
"suppliers": [
{
"title": "Sup1"
}
]
},{
"id": 3,
"name": "Paul",
"count": "2",
"suppliers": [
{
"title": "Sup1"
},{
"title": "Sup2"
}
]
}
]
}
If I simply add count: userArray[0].count, my output is correct for every user who has a single supplier but only picks the 1st for any with more than one. E.g. For the above, Paul would show a count of 2 and for Paul, there are 2 customers for Sup1 and 1 for Sup2. How do I fix this so users like Paul who have more than one supplier?
count: userArray.reduce((sum, user) => sum + +user.count, 0)
Using .reduce you can sum up the counts. The unary plus is needed to cast the strings into numbers.
And you don't need lodash, and you don't need to reduce if you can filter and map:
const users = Object.values(grouped).map(users => ({
id: users[0].id,
name: users[0].name,
suppliers: users.map(user => user.supplierName).filter(it => it),
count: users.reduce((sum, user) => sum + +user.count, 0),
}));
You can use simple map(). And use reduce() to get count
let obj = { '1': [ { id: 1, name: 'John Doe', supplierName: 'Sup1', count: '21' } ], '2': [ { id: 2, name: 'Jane Doe', supplierName: 'Sup1', count: '95' } ], '3': [ { id: 3, name: 'Paul', supplierName: 'Sup1', count: '2' }, { id: 3, name: 'Paul', supplierName: 'Sup2', count: '1' } ] }
const res = Object.values(obj).map((x) => (
{
id:x[0].id,
name:x[0].name,
suppliers:x.map(title => ({title})),
count:x.reduce((ac,a) => +ac + +a.count, 0)
}
))
console.log(res)
You can use map() and length()
users.map(user => ({
...user,
count: user.suppliers.length()
}))
const usersData = [
{
count: 5,
providerList: [
{
gender: "F",
languageCodes: [
{
code: "JPN",
desc: "Japan"
}
],
},
{
gender: "F",
languageCodes: [
{
code: "SPN",
desc: "Spanish"
},
{
code: "IND",
desc: "Indian"
},
],
},
{
gender: "F",
languageCodes: [
{
code: "IND",
desc: "Indian"
}
],
},
{
gender: "M",
languageCodes: [
{
code: "US",
desc: "English"
}
],
},
{
gender: "M",
languageCodes: [
{
code: "Aus",
desc: "English"
}
],
}
]
}
];
let key=['gender']
let value=['M']
let finalData = usersData[0].providerList.filter(function(e) {
return key.every(function(a) {
return value.includes(e[a]);
});
});
console.log(finalData)
Here the code contains the sample data for the gender and language.when user executes the using key and value is maintained into the snippet you will get the finalData as output.
Here my question is I also want to filter the value of languageCodes means if the key=['code'] value=['Indian'] it will filter the data accordingly.
Here my problem is that I do not want to change the key and value variable into the snippet like
return key.every(function(a) {return value.includes(e[a]);
Thanks
This will only works if usersData has always the same format
var _key=['code']
var _value=['Aus']
function _filter(data, key, value){
return data.filter(function(a){
return a[key] == value || (a.languageCodes && _filter(a.languageCodes, key, value).length)
})
}
console.log(_filter(usersData[0].providerList, _key, _value))
<script>
const usersData = [{
count: 5,
providerList: [{
gender: "F",
languageCodes: [{
code: "JPN",
desc: "Japan"
}],
},
{
gender: "F",
languageCodes: [{
code: "SPN",
desc: "Spanish"
},
{
code: "IND",
desc: "Indian"
},
],
},
{
gender: "F",
languageCodes: [{
code: "IND",
desc: "Indian"
}],
},
{
gender: "M",
languageCodes: [{
code: "US",
desc: "English"
}],
},
{
gender: "M",
languageCodes: [{
code: "Aus",
desc: "English"
}],
}
]
}];
</script>
This question already has answers here:
MongoDB $graphLookup get children all levels deep - nested result
(2 answers)
Closed 3 years ago.
I have an output from mongodb $graphLookup aggregation:
db.getCollection('projects').aggregate([
{
$lookup: {
from: "projects",
localField: "_id",
foreignField: "parent",
as: "childrens"
}
}
])
{
"_id" : "1",
"name" : "Project1",
"parent" : null,
"childrens" : [
{
"_id" : "3",
"name" : "ProjectForId1",
"parent" : "1"
}
]
},
{
"_id" : "3",
"name" : "ProjectForId1",
"parent" : "1",
"childrens" : [
{
"_id" : "6",
"name" : "ProjectForId3",
"parent" : "3"
},
{
"_id" : "7",
"name" : "ProjectForId3",
"parent" : "3"
}
]
}
I need to build hierarchy from this output in javascript or if is possible directly from query so the final output should look like:
{
"_id" : "1",
"name" : "Project1",
"parent" : null,
"childrens" : [
{
"_id" : "3",
"name" : "ProjectForId1",
"parent" : "1",
"childrens" : [
{
"_id" : "6",
"name" : "ProjectForId3",
"parent" : "3"
},
{
"_id" : "7",
"name" : "ProjectForId3",
"parent" : "3"
}
]
}
]
}
Also if someone have a brave heart to help in one more case where the hierarchy will be created by filtering _id:
ex: For _id = "1" the output will be same as above but if _id is 3 the final output should look like:
{
"_id" : "3",
"name" : "ProjectForId1",
"parent" : "1",
"childrens" : [
{
"_id" : "6",
"name" : "ProjectForId3",
"parent" : "3"
},
{
"_id" : "7",
"name" : "ProjectForId3",
"parent" : "3"
}
]
}
Below solution is more or less the same as one of my past answers so you can get thorough explanation here
db.projects.aggregate([
{
$graphLookup: {
from: "projects",
startWith: "$_id",
connectFromField: "_id",
connectToField: "parent",
as: "children",
maxDepth: 4,
depthField: "level"
}
},
{
$unwind: "$children"
},
{
$sort: { "children.level": -1 }
},
{
$group: {
_id: "$_id",
children: { $push: "$children" }
}
},
{
$addFields: {
children: {
$reduce: {
input: "$children",
initialValue: {
currentLevel: -1,
currentLevelProjects: [],
previousLevelProjects: []
},
in: {
$let: {
vars: {
prev: {
$cond: [
{ $eq: [ "$$value.currentLevel", "$$this.level" ] },
"$$value.previousLevelProjects",
"$$value.currentLevelProjects"
]
},
current: {
$cond: [
{ $eq: [ "$$value.currentLevel", "$$this.level" ] },
"$$value.currentLevelProjects",
[]
]
}
},
in: {
currentLevel: "$$this.level",
previousLevelProjects: "$$prev",
currentLevelProjects: {
$concatArrays: [
"$$current",
[
{ $mergeObjects: [
"$$this",
{ children: { $filter: { input: "$$prev", as: "e", cond: { $eq: [ "$$e.parent", "$$this._id" ] } } } }
] }
]
]
}
}
}
}
}
}
}
},
{
$addFields: { children: "$children.currentLevelProjects" }
},
{
$match: {
_id: "1"
}
}
])
Last stage is supposed to be the filtering so you can get the data for any level of depth here.