Mongo DB group records and return selected fields - mongodb

I want to group the documents on the basis of a specific value. Also I need the selected keys in return.
Here is my document:
{
title: "Add 1",
userId: 1,
description: "this is add 1",
location: {
city: "Newyork",
address: "Street 1, Flat # 40"
},
createdAt: "2015-10-20",
title: "Add 2",
userId: 1,
description: "this is add 2",
location: {
city: "Paris",
address: "Street 2, Flat # 80"
},
createdAt: "2015-10-22",
title: "Add 3",
userId: 1,
description: "this is add 3",
location: {
city: "Newyork",
address: "Street 5, Flat # 58"
},
createdAt: "2015-10-23"
}
I want to group the documents by location.city. Also I need only title and location in return.
What should be the query for above scenario?
I have tried this query:
Adds.aggregate([
{ $match: { userId: params.userId } },
{ $group: { _id: "$location.city" } },
]);
The above query only returns location.city, I want complete object of location & title.

Assuming that your data actually looks like this:
[
{
title: "Add 1",
description: "this is add 1",
location: {
city: "Newyork",
address: "Street 1, Flat # 40"
},
createdAt: "2015-10-20",
},
{
title: "Add 2",
description: "this is add 2",
location: {
city: "Paris",
address: "Street 2, Flat # 80"
},
createdAt: "2015-10-22",
},
{
title: "Add 3",
description: "this is add 3",
location: {
city: "Newyork",
address: "Street 5, Flat # 58"
},
createdAt: "2015-10-23"
}
]
You could use an aggregate query such as:
db.adds.aggregate([
{ $group :
{
_id : "$location.city",
data : { $push : { "title" : "$title", "location" : "$location" } }
}
}
]);

userId is missing in your documents, if your collection looks like this your aggregate function is correct.
{
"title": "Add 1",
"userId": 1,
"description": "this is add 1",
"location": {
"city": "Newyork",
"address": "Street 1, Flat # 40"
},
"createdAt": "2015-10-20"
},
{
"title": "Add 2",
"userId": 2
"description": "this is add 2",
"location": {
"city": "Paris",
"address": "Street 2, Flat # 80"
},
"createdAt": "2015-10-22"
},
{
"title": "Add 3",
"userId": 1,
"description": "this is add 3",
"location": {
"city": "Newyork",
"address": "Street 5, Flat # 58"
},
"createdAt": "2015-10-23"
}
Test using Mongodb's console
db.Adds.aggregate([
{ $match: { userId: 1 } },
{ $group: { _id: "$location.city" } }
]);
Adding title to your results
db.Adds.aggregate([
{ $match: { userId: 1 } },
{
$group: {
_id: "$location.city",
title: {$push: "$title"}
}
}
]);

Related

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

How can you search by country and return the title and id matching those countries?

I'm trying to do a sorter in where you can search by country and then if it matches it should return the title and id matching those countries.
static async getMoviesByCountry(countries) {
/**
Ticket: Projection
Write a query that matches movies with the countries in the "countries"
list, but only returns the title and _id of each movie.
Remember that in MongoDB, the $in operator can be used with a list to
match one or more values of a specific field.
*/
let cursor
try {
// TODO Ticket: Projection
// Find movies matching the "countries" list, but only return the title
// and _id. Do not put a limit in your own implementation, the limit
// here is only included to avoid sending 46000 documents down the
// wire.
cursor = await movies.find({
country: {$in: [countries]}})
.then(
movies.findOne({_id:_id, title: title})
)
.limit(1)
} catch (e) {
console.error(`Unable to issue find command, ${e}`)
return []
}
return cursor.toArray()
}
I've tried doing this in where you search the country by the ones that are included in countries and then find movies by id and title. But this is not working.
cursor = await movies.find({
country: {$in: [countries]}})
.then(
movies.findOne({_id:_id, title: title})
)
.limit(1)
Try this
let collection countries = [
{
"countryId": 1,
"Countryname": "India",
"movieId": 11,
},
{
"countryId": 2,
"Countryname": "India",
"movieId": 13,
},
{
"countryId": 3,
"Countryname": "China",
"movieId": 12,
},
{
"countryId": 4,
"Countryname": "Japan",
"movieId": 14,
},
]
let movies= [
{
"MovieId": 11,
"title": "Movie 11",
"description": "Description 11",
},
{
"MovieId": 12,
"title": "Movie 12",
"description": "Description 12",
},
{
"MovieId": 13,
"title": "Movie 13",
"description": "Description 13",
},
{
"MovieId": 14,
"title": "Movie 14",
"description": "Description 14",
},
{
"MovieId": 15,
"title": "Movie 15",
"description": "Description 15",
},
]
}
Query
db.countries.aggregate([
{
"$lookup": {
"from": "movies",
"localField": "movieId",
"foreignField": "MovieId",
"as": "data"
}
},
{
$unwind: "$data"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$data",
"$$ROOT"
]
}
}
},
{
$project: {
data: 0
}
},
{
$match: {
Countryname: {
"$in": [
"India",
"China"
]
}
}
}
])
Expected Output
[
{
"Countryname": "India",
"MovieId": 11,
"_id": ObjectId("5a934e000102030405000000"),
"countryId": 1,
"description": "Description 11",
"movieId": 11,
"title": "Movie 11"
},
{
"Countryname": "India",
"MovieId": 13,
"_id": ObjectId("5a934e000102030405000001"),
"countryId": 2,
"description": "Description 13",
"movieId": 13,
"title": "Movie 13"
},
{
"Countryname": "China",
"MovieId": 12,
"_id": ObjectId("5a934e000102030405000002"),
"countryId": 3,
"description": "Description 12",
"movieId": 12,
"title": "Movie 12"
}
]
Check this Mongoplayground

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

How to reference to a schema inside the same collection?

I'm trying to store forms' questions and answers in the same collection. Here's the Model and schemas I've designed:
const mongoose = require("mongoose");
const questionSchema = mongoose.Schema({
type: String,
title: String,
});
const answerSchema = mongoose.Schema({
question: {
type: mongoose.Schema.Types.ObjectId,
ref: "Form.questions",
},
answer: String,
});
const Form = mongoose.model("Form", {
title: { type: String, default: "Titre du formulaire" },
questions: [questionSchema],
answers: [[answerSchema]],
});
module.exports = Form;
For each answer, I'm trying to reference the objectId of the question, but mongoose.Schema.Types.ObjectId seems to work only when referencing to another collection, not to some other id in the same collection. I don't know what to put instead of ref: "Form.questions" to get populate() to display the question in the answer.
Am I on the right track, how can I connect properly an answer to its question? Or do I need to create a separate collection for the answers?
Here's an example of the output:
{
"title": "Formulaire de test",
"answers": [
[
{
"_id": "5fd8f8938a111d3a1df9f3bd",
"question": "5fd8a65ae252ae5b903d4593",
"answer": "Question 1"
},
{
"_id": "5fd8f8938a111d3a1df9f3be",
"question": "5fd8a65ae252ae5b903d4595",
"answer": "Question 3"
},
{
"_id": "5fd8f8938a111d3a1df9f3bf",
"question": "5fd8a65ae252ae5b903d4594",
"answer": "Question 2"
},
{
"_id": "5fd8f8938a111d3a1df9f3c0",
"question": "5fd8f6898a111d3a1df9f3bb",
"answer": "Question 4"
},
{
"_id": "5fd8f8938a111d3a1df9f3c1",
"question": "5fd8f6898a111d3a1df9f3bc",
"answer": "Question 5"
}
],
[
{
"_id": "5fd8f96a9d75193ecd410464",
"question": "5fd8a65ae252ae5b903d4593",
"answer": "Question 1"
},
{
"_id": "5fd8f96a9d75193ecd410465",
"question": "5fd8a65ae252ae5b903d4595",
"answer": "Question 3"
},
{
"_id": "5fd8f96a9d75193ecd410466",
"question": "5fd8a65ae252ae5b903d4594",
"answer": "Question 2"
},
{
"_id": "5fd8f96a9d75193ecd410467",
"question": "5fd8f6898a111d3a1df9f3bb",
"answer": "Question 4"
},
{
"_id": "5fd8f96a9d75193ecd410468",
"question": "5fd8f6898a111d3a1df9f3bc",
"answer": "Question 5"
}
]
],
"_id": "5fd8f4c5600d65378596748a",
"questions": [
{
"_id": "5fd8a65ae252ae5b903d4593",
"title": "Question 1",
"type": "text"
},
{
"_id": "5fd8a65ae252ae5b903d4595",
"title": "Question 3",
"type": "text"
},
{
"_id": "5fd8a65ae252ae5b903d4594",
"title": "Question 2",
"type": "score"
},
{
"_id": "5fd8f6898a111d3a1df9f3bb",
"title": "Question 4",
"type": "text"
},
{
"_id": "5fd8f6898a111d3a1df9f3bc",
"title": "Question 5",
"type": "text"
}
],
"__v": 3
}

Group multiple times in aggregation framework keeping multiple fields

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