Group multiple times in aggregation framework keeping multiple fields - mongodb

I need to group multiple time a collection keeping multiple fields.
I've tried $group in aggregation framework but I probably make a mistake because I can't keep the different fields I need.
Here is the example:
[
{
"courseTitle": "Master 1",
"courseCompleted": false,
"module_id": "m01",
"section": "01 - Introduction",
"order": 1,
"completed": true,
"moduleTitle": "Module 1"
},
{
"courseTitle": "Master 1",
"courseCompleted": false,
"module_id": "m02",
"section": "01 - Introduction",
"order": 2,
"completed": true,
"moduleTitle": "Module 2"
},
{
"courseTitle": "Master 1",
"courseCompleted": false,
"module_id": "m03",
"section": "01 - Introduction",
"order": 3,
"completed": false,
"moduleTitle": "Module 3"
},
{
"courseTitle": "Master 1",
"courseCompleted": false,
"module_id": "m04",
"section": "02 - First test",
"order": 4,
"completed": false,
"moduleTitle": "Module 4"
}
]
I need to group first by section and then by courseTitle AND courseCompleted fields:
[
{
"courseTitle": "Master 1",
"courseCompleted": false,
"sections": [
{
"section": "01 - Introduction",
"modules": [
{
"module_id": "m01",
"order": 1,
"completed": true,
"moduleTitle": "Module 1"
},
{
"module_id": "m02",
"order": 2,
"completed": true,
"moduleTitle": "Module 2"
},
{
"module_id": "m03",
"order": 3,
"completed": false,
"moduleTitle": "Module 3"
},
]
},
{
"section": "02 - First test",
"modules": [
{
"module_id": "m04",
"order": 4,
"completed": false,
"moduleTitle": "Module 4"
}
]
}
]
}
]
Example in playground: https://mongoplayground.net/p/ThqXLYmQTCe

You need to run $group in order to get nested array:
db.collection.aggregate([
{
$group: {
_id: { courseTitle: "$courseTitle", section: "$section", courseCompleted: "$courseCompleted" },
modules: { $push: { module_id: "$module_id", order: "$order", completed: "$completed", moduleTitle: "$moduleTitle" } }
}
},
{ $sort: { "_id.section": 1 } },
{
$group: {
_id: { courseCompleted: "$_id.courseCompleted", courseTitle: "$_id.courseTitle" },
sections: { $push: { section: "$_id.section", modules: "$modules" } }
}
},
{
$project: {
_id: 0,
courseTitle: "$_id.courseTitle",
courseCompleted: "$_id.courseCompleted",
sections: 1
}
}
])
Mongo Playground

Related

How to reverse $Unwind inside nested objects in mongodb aggregation

I am working in a review application which has business and business has review and it can be done by many users and one user can do many reviews to the same business so my data looks like:
[
{
Name: "business name",
"_id": "633efd0d6bc0e52519c7e107",
"review": [
{
"user": "62e6459847a818151639398e",
"reviewArray": [
{
"title": "review title 122",
"description": "this is description of review 2 by another user",
"isDeleted": true,
"suspended": false
},
{
"title": "review title",
"description": "this is description of review 2 by another user",
"isDeleted": false,
"suspended": false
}
]
},
{
"user": "62e6459847a818151639391f",
"reviewArray": [
{
"title": "review title 2",
"description": "this is description of review 1 by another user",
"isDeleted": false,
"suspended": false
},
{
"title": "review",
"description": "this is description of review by another user",
"isDeleted": false,
"suspended": true
}
]
}
]
},
{
Name: "business2",
"_id": "633efd0d6bc0e52519c7e108",
"review": [
{
"user": "62e6459847a818151639398e",
"reviewArray": [
{
"title": "review title",
"description": "this is description of review 2 by another user",
"isDeleted": false,
"suspended": false
},
{
"title": "review title",
"description": "this is description of review 2 by another user",
"isDeleted": false,
"suspended": false
}
]
},
{
"user": "62e6459847a818151639391f",
"reviewArray": [
{
"title": "review title of another review",
"description": "this is description of review11 by another user",
"isDeleted": true,
"suspended": false
},
{
"title": "review",
"description": "this is description of review by another user",
"isDeleted": false,
"suspended": true
}
]
}
]
}
]
I want the final result to be in same pattern but the reviews which has been deleted and are suspended do not want and if all the reviews are either deleted or suspended don't need the user info and review array in final result as :
[
{
"Name": "business name",
"_id": "633efd0d6bc0e52519c7e107",
"review": [
{
"reviewArray": [
{
"description": "this is description of review 2 by another user",
"isDeleted": false,
"suspended": false,
"title": "review title"
}
],
"user": "62e6459847a818151639398e"
},
{
"reviewArray": [
{
"description": "this is description of review 1 by another user",
"isDeleted": false,
"suspended": false,
"title": "review title 2"
}
],
"user": "62e6459847a818151639391f"
}
]
},
{
"Name": "business2",
"_id": "633efd0d6bc0e52519c7e108",
"review": [
{
"reviewArray": [
{
"description": "this is description of review 2 by another user",
"isDeleted": false,
"suspended": false,
"title": "review title"
},
{
"description": "this is description of review 2 by another user",
"isDeleted": false,
"suspended": false,
"title": "review title"
}
],
"user": "62e6459847a818151639398e"
}
]
}
]
I tried using mongodb aggregation using unwind and group but is not giving me accurate result. What will be the final mongodb aggregation query for the final result as shown above?
One option is to use $map and $filter:
db.collection.aggregate([
{$project: {
Name: 1,
review: {
$map: {
input: "$review",
as: "user",
in: {reviewArray: {
$filter: {
input: "$$user.reviewArray",
as: "review",
cond: {
$and: [
{$eq: ["$$review.isDeleted", false]},
{$eq: ["$$review.suspended", false]}
]
}
}
},
user: "$$user.user"
}
}
}
}},
{$project: {
Name: 1,
review: {
$filter: {
input: "$review",
cond: {$gt: [{$size: "$$this.reviewArray"}, 0]}
}
}
}}
])
See how it works on the playground example

MongoDB sort by multiple fields on boolean type

First of all i need "isFavourite": true then "isReadByPro": false and then updatedAt:-1.
I tried below way but not working.
{ $sort: { updatedAt: -1 } },
{ $sort: { isReadByPro: 1 } },
{ $sort: { isFavourite: -1 } },
[
{
"_id": "628c8378c92d4b969cf4b53b",
"post": {
"title": "project 94608"
},
"isFavourite": true,
"isReadByPro": true,
"updatedAt": "2022-06-06T10:34:05.776Z"
},
{
"_id": "628f507192034120c7fc261a",
"post": {
"title": "your payment test 1"
},
"isFavourite": true,
"isReadByPro": true,
"updatedAt": "2022-05-27T10:39:16.493Z"
},
{
"_id": "628f50a792034120c7fc2681",
"post": {
"title": "your payment test 3"
},
"isFavourite": true,
"isReadByPro": true,
"updatedAt": "2022-05-27T08:42:48.403Z"
},
{
"_id": "628c76840ffe2cd088654d50",
"post": {
"title": "showcase 1"
},
"isFavourite": false,
"isReadByPro": true,
"updatedAt": "2022-05-24T06:10:38.364Z"
},
{
"_id": "628c780a0ffe2cd088654e21",
"post": {
"title": "project 1 test"
},
"isFavourite": false,
"isReadByPro": true,
"updatedAt": "2022-05-27T06:30:54.058Z"
},
{
"_id": "628c79cb5b4b0ee6020482df",
"post": {
"title": "project 2 test"
},
"isFavourite": false,
"isReadByPro": true,
"updatedAt": "2022-05-27T06:30:54.058Z"
}
]
In order to sort by multiple fields, they should be on the same sorting object. The order of them is according to priority. This data is sorted first by isFavourite, then, all the documents with the same isFavourite value will be sorted between them by isReadByPro and so on.
{ $sort: {isFavourite: -1, isReadByPro: 1, updatedAt: -1} }

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 by category and field
$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 the items array
$group by category, field and item title and construct the array if item values
$group by category and field and construct the items array with title and value
$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

Aggregation pipeline to fetch nested language objects inside array of objects into a single document

Let's say I have a collection of objects:
{
"_id": "096439dc-6b57d5-470e-55bb-04ee6378edsg",
"SomeEnt": [
{
"_id": "ce4by887-e589-4b71-e6s4-24d0c586k863",
"userId": "some name",
"name": {
"de": "some name",
"en": "some name",
"fr": "some name",
"es": "some name"
}
},
{
"userId": "some name 2",
"name": {
"en": "some name 2",
"de": "some name 2",
"fr": "some name 2"
}
}
],
"type": {
"_id": "b4951229-6m95-4ui8-ba27-8dcc69lk5217",
"description": {
"en": "something"
},
"code": "something"
},
"Codes": {
"_id": "92ea1201-827b-411a-854b-7832995lkoib",
"type": {
"_id": "1189bd7785-a23f-49f1-be5a-d9l17hy563cd",
"code": "something",
"description": {
"def": "something",
"en": "something"
}
},
"code": "Some Bookings",
}
}
I want a single document where the name inside SomeEnt will be a string with given locale("en" or "fr")
{
"_id": "096439dc-6b57d5-470e-55bb-04ee6378edsg",
"SomeEnt": [
{
"_id": "ce4by887-e589-4b71-e6s4-24d0c586k863",
"userId": "some name",
"name": "some name",
},
{
"userId": "some name 2",
"name": "some name 2",
}
],
"type": {
"_id": "b4951229-6m95-4ui8-ba27-8dcc69lk5217",
"description": {
"en": "something"
},
"code": "something"
},
"Codes": {
"_id": "92ea1201-827b-411a-854b-7832995lkoib",
"type": {
"_id": "1189bd7785-a23f-49f1-be5a-d9l17hy563cd",
"code": "something",
"description": {
"def": "something",
"en": "something"
}
},
"code": "Some Bookings",
}
}
I tried to unwind and then replace the name with the locale but now I'm not understanding how to merge the unwinded documents into 1
[{
$match: {
_id: '096439dc-6b57d5-470e-55bb-04ee6378edsg'
}
}, {
$unwind: {
path: "$SomeEnt"
}
}, {
$project: {
_id: 1,
type: 1,
description: 1,
SomeEnt: {
_id:1,
userId:1,
name: "$SomeEnt.name.en"
},
Codes: {
_id: 1,
code: 1
}
}
}]
How to merge the 2 documents the unwind produced into 1
How to merge the 2 documents the unwind produced into 1
You can use $group stage after $project stage to reconstruct SomeEnt array, and required fields,
{
$group: {
_id: "$_id",
SomeEnt: { $push: "$SomeEnt" },
type: { $first: "$type" },
description: { $first: "$description" },
Codes: { $first: "$Codes" }
}
}
Playground
Second Approach: if you don't use $unwind
$map to iterate loop of SomeEnt array and return required fields
[
{ $match: { _id: "096439dc-6b57d5-470e-55bb-04ee6378edsg" } },
{
$project: {
type: 1,
description: 1,
Codes: {
_id: 1,
code: 1
},
SomeEnt: {
$map: {
input: "$SomeEnt",
in: {
_id: "$$this._id",
userId: "$$this.userId",
name: "$$this.name.en"
}
}
}
}
}
]
Playground

MongoDB inner join with specific condition from both collections

I have got two collections, chapters and courses with one-to-one association,
db={
"chapters": [
{
"_id": 10,
"course_id": 1,
"author": "John"
},
{
"_id": 20,
"course_id": 2,
"author": "John"
},
{
"_id": 30,
"course_id": 3,
"author": "Timmy"
},
{
"_id": 40,
"course_id": 4,
"author": "John"
},
],
"courses": [
{
"_id": 1,
"published": true,
"name": "course 1"
},
{
"_id": 2,
"published": false,
"name": "course 2"
},
{
"_id": 3,
"published": true,
"name": "course 3"
},
{
"_id": 4,
"published": true,
"name": "course 4"
}
]
}
How do I query all chapters with the published course (published=true) and the author is "John"?
You can use $match, $lookup then $group by author then simply $filter it based on published
db.chapters.aggregate([
{
$match: {
author: "John"
}
},
{
"$lookup": {
"from": "courses",
"localField": "course_id",
"foreignField": "_id",
"as": "course"
}
},
{
$project: {
_id: 1,
course: {
$first: "$course"
},
author: 1
}
},
{
$group: {
_id: "$author",
courseChapters: {
$push: "$$ROOT"
}
}
},
{
$project: {
courseChapters: {
$filter: {
input: "$courseChapters",
as: "cc",
cond: {
$eq: [
"$$cc.course.published",
true
]
}
}
}
}
}
])
Output
[
{
"_id": "John",
"courseChapters": [
{
"_id": 10,
"author": "John",
"course": {
"_id": 1,
"name": "course 1",
"published": true
}
},
{
"_id": 40,
"author": "John",
"course": {
"_id": 4,
"name": "course 4",
"published": true
}
}
]
}
]
Mongoplayground: https://mongoplayground.net/p/x-XghXzZUk4