multiple grouping of documents with nested array in mongodb
I have a list of documents with these fields id:'int', category:'text', field:'text', and a list of items[{title, value}]
documents= [
{ "id":"1",
"category": "education",
"field": "science",
"items": [
{
"title": "item 1",
"value": "10"
},
{
"title": "item 2",
"value": "102"
},
{
"title": "item 4",
"value": "12"
}
]
},
{ "id":"2",
"category": "education",
"field": "science",
"items": [
{
"title": "item 1",
"value": "4"
},
{
"title": "item 2",
"value": "23"
},
{
"title": "item 4",
"value": "45"
}
]
},
{ "id":"3",
"category": "fitness",
"field": "body",
"items": [
{
"title": "item 1",
"value": "87"
},
{
"title": "item 5",
"value": "45"
},
{
"title": "item =3",
"value": "23"
}
]
},
{ "id":"4",
"category": "education",
"field": "culture",
"items": [
{
"title": "item 1",
"value": "187"
},
{
"title": "item 5",
"value": "145"
},
{
"title": "item 3",
"value": "123"
}
]]
i'm working with mongodb (beginner) and i'm confused about how to group those documents firstly by category, then by field, then by item's title to push their value in array like a list of values history. the desired result:
newDocument=[
{ "newid":"1",
"category": "education",
"field": "science",
"items": [
{
"title": "item 1",
"value": ["10","4"]
},
{
"title": "item 2",
"value": ["102","23"]
},
{
"title": "item 4",
"value": ["12", "45"]
}
]
},
{ "newid":"2",
"category": "education",
"field": "culture",
"items": [
{
"title": "item 1",
"value": ["187"]
},
{
"title": "item 5",
"value":["145"]
},
{
"title": "item 3",
"value": ["123"]
}
]
}
{ "newid":"3",
"category": "fitness",
"field": "body",
"items": [
{
"title": "item 1",
"value": ["87"]
},
{
"title": "item 5",
"value":["45"]
},
{
"title": "item 3",
"value": ["23"]
}
]
}
]
You can use an aggregation query, but it will impact on performance and speed of response, there are two options, one using $unwind
and another using $accumulator
operator,
using $accumulator
:
-
$group
bycategory
andfield
-
$accumulator
to do custom JavaScript logic and merge items array, you can improve and update as per your requirement -
$project
to show required fields
db.collection.aggregate([
{
$group: {
_id: {
category: "$category",
field: "$field"
},
items: {
$accumulator: {
init: function() { return []; },
accumulate: function(items1, items2) {
return items1.concat(items2);
},
accumulateArgs: ["$items"],
merge: function(items1, items2) {
return items1.concat(items2);
},
finalize: function(state) {
var items = {}, finalItems = [];
state.forEach(function(item) {
if (items[item.title]) {
items[item.title].value.push(item.value);
}
else {
item.value = [item.value];
items[item.title] = item;
}
});
for (var i in items) {
finalItems.push(items[i]);
}
return finalItems;
},
lang: "js"
}
}
}
},
{
$project: {
_id: 0,
category: "$_id.category",
field: "$_id.field",
items: 1
}
}
])
using $unwind
: (not recommended)
-
$unwind
deconstruct theitems
array -
$group
bycategory
,field
anditem
title and construct the array if item values -
$group
bycategory
andfield
and construct theitems
array withtitle
andvalue
-
$project
to show required fields
db.collection.aggregate([
{ $unwind: "$items" },
{
$group: {
_id: {
category: "$category",
field: "$field",
title: "$items.title"
},
value: { $push: "$items.value" }
}
},
{
$group: {
_id: {
category: "$_id.category",
field: "$_id.field"
},
items: {
$push: {
title: "$_id.title",
value: "$value"
}
}
}
},
{
$project: {
_id: 0,
category: "$_id.category",
field: "$_id.field",
items: 1
}
}
])
Playground