Grouping the results of a group query in mongodb - mongodb

I have the following sample data:
{
"name": "Bob",
"mi": "K",
"martialStatus": "M",
"age": 30,
"city": "Paris",
"job": "Engineer"
}
{
"name": "Chad",
"mi": "M",
"martialStatus": "W",
"age": 31,
"city": "Paris",
"job": "Doctor"
}
{
"name": "Mel",
"mi": "A",
"martialStatus": "D",
"age": 31,
"city": "London",
"job": "Doctor"
}
{
"name": "Frank",
"mi": "F",
"martialStatus": "S",
"age": 30,
"city": "London",
"job": "Engineer"
}
I am trying to write a mongo query that would return results in the following format:
"peopleCount": 4,
"jobsList": {
"job": "Doctor",
"ageList": [
{
"age": 31,
"cityList": [
{
"city": "London",
"people": [
{
"name": "Mel",
"martialStatus": "D"
}
]
},
{
"city": "Paris",
"people": [
{
"name": "Chad",
"martialStatus": "W"
}
]
},
{
"city": "Berlin",
...
...
]
}
]
}
To try on the first two level (jobsList and ageList), I am trying the below
db.colName.aggregate([
{
$group: {
_id: { job: "$job" },
jobsList: {
$push: {
age: "$age",
city: "$city",
name: "$name",
martialStatus: "$martialStatus"
}
}
}
},
{
$group: {
_id: { age: "$age" },
ageList: {
$push: {
city: "$city",
name: "$name",
martialStatus: "$martialStatus"
}
}
}
}
]);
The above however does not work although the first group/push part works... Any hints on how to get that output format/groupping?

db.colName.aggregate([
{
$group: {
_id: { job: "$job", age: "$age", city: "$city" },
people: { $push: { name: "$name", martialStatus: "$martialStatus" } }
}
},
{
$group: {
_id: { job: "$_id.job", age: "$_id.age" },
peopleCount: { $sum: { $size: "$people" } },
cityList: { $push: { city: "$_id.city", people: "$people" } },
}
},
{
$group: {
_id: { job: "$_id.job" },
peopleCount: { $sum: "$peopleCount" },
agesList: { $push: { age: "$_id.age", cityList: "$cityList" } }
}
},
{
$group: {
_id: null,
peopleCount: { $sum: "$peopleCount" },
jobsList: { $push: { job: "$_id.job", agesList: "$agesList" } }
}
},
{
$project: { _id: 0, peopleCount: 1, jobsList: 1 }
}
]);
on the provided by you collection gives me the result
{
"peopleCount" : 4,
"jobsList" :
[
{
"job" : "Engineer",
"agesList" :
[
{
"age" : 30,
"cityList" :
[
{
"city" : "London",
"people" :
[
{ "name" : "Frank", "martialStatus" : "S" }
]
},
{
"city" : "Paris",
"people" :
[
{ "name" : "Bob", "martialStatus" : "M" }
]
}
]
}
]
},
{
"job" : "Doctor",
"agesList" :
[
{
"age" : 31,
"cityList" :
[
{
"city" : "London",
"people" :
[
{ "name" : "Mel", "martialStatus" : "D" }
]
},
{
"city" : "Paris",
"people" :
[
{ "name" : "Chad", "martialStatus" : "W" }
]
}
]
}
]
}
]
}
that seems to be correct. Thought, I am not sure it's the best solution. I am new to aggregation-framework.

Related

How to write a mongo query to find the count based on gender field with $accumulator?

I'm trying to use the $accumulator function to get the count of names based on gender. Below is my code.
db.collection.aggregate([
{
$match:
{ department: "Finance"}
,
},
{
$group :
{
_id : "$department",
count: { $sum: 1 },
data :
{
$accumulator:
{
init: function() {
return { count: 0, maleCount: 0, femaleCount: 0 }
},
accumulate: function(state, gender) {
if (gender === "Male") {
state.maleCount+ 1;
}
if (type === "Female") {
state.femaleCount+ 1;
}
state.count + 1;
return state;
},
accumulateArgs: ["$user.gender"],
merge: function(state1, state2) {
return {
count: state1.count + state2.count,
maleCount: state1.maleCount+ state2.maleCount,
femaleCount: state1.femaleCount+ state2.femaleCount
}
},
finalize: function(state) {
return state;
},
lang: "js"
}
}
}
}
])
My data format is similar to the below:
[
{
"_id": 1,
"department": "Finance",
"user": {
"name": "Staff 1",
"gender": "Male",
}
},
{
"_id": 2,
"department": "Finance",
"user": {
"name": "Staff 2",
"gender": "Female",
}
},
{
"_id": 3,
"department": "Finance",
"user": {
"name": "Staff 3",
"gender": "Male",
}
}
]
I'm expecting the output to be the count of all the staff with maleCount and femaleCount. The below:
{
"_id" : "Finance",
"count" : 3,
"data" : {
"count" : 3,
"maleCount" : 2,
"femaleCount" : 1
}
}
I'm not getting the output as desired. Instead, the output I'm getting is something like the below:
{
"_id" : "Finance",
"count" : 3,
"data" : {
"count" : 0.0,
"maleCount" : 0.0,
"femaleCount" : 0.0
}
}
How about this:
db.collection.aggregate([
{
$setWindowFields: {
partitionBy: { department: "$department", gender: "$user.gender" },
output: {
count: { $count: {} }
}
}
},
{
$group: {
_id: "$department",
data: { $addToSet: { k: "$user.gender", v: "$count" } },
count: { $count: {} }
}
},
{
$project: {
_id: 1,
count: 1,
data: { $arrayToObject: "$data" }
}
}
])
Mongo Playground

Grouping into array in MongoDB

I have MongoDB collection with below documents:
[
{
"productType":"Bike",
"company":"yamaha",
"model":"y1"
},
{
"productType":"Bike",
"company":"bajaj",
"model":"b1"
},
{
"productType":"Bike",
"company":"yamaha",
"model":"y1"
},
{
"productType":"Car",
"company":"Maruti",
"model":"m1"
},
{
"productType":"Bike",
"company":"yamaha",
"model":"y2"
},
{
"productType":"Car",
"company":"Suzuki",
"model":"s1"
}
]
I want my output to be like :
{
"productType": [
{
"name": "Bike",
"count": 4,
"companies": [
{
"name": "Yamaha",
"count": 3,
"models": [
{
"name": "y1",
"count": 2
},
{
"name": "y2",
"count": 1
}
]
},
{
"name": "Bajaj",
"count": 1,
"models": [
{
"name": "b1",
"count": 1
}
]
}
]
},
{
"name": "Car",
"count": 2,
"companies": [
{
"name": "Maruti",
"count": 1,
"models": [
{
"name": "m1",
"count": 1
}
]
},
{
"name": "Suzuki",
"count": 1,
"models": [
{
"name": "s1",
"count": 1
}
]
}
]
}
]
}
I am not able to understand how to create arrays inside existing array using $push. I know we can create an array using $push but how to create array of array with it ?
In future, I might want to add "metaData" field also along with name and count.
You have to run multiple $group stages, one for each level:
db.collection.aggregate([
{
$group: {
_id: { company: "$company", productType: "$productType", model: "$model" },
count: { $sum: 1 }
}
},
{
$group: {
_id: { productType: "$_id.productType", company: "$_id.company" },
models: { $push: { name: "$_id.model", count: "$count" } },
count: { $sum: "$count" }
}
},
{
$group: {
_id: "$_id.productType",
companies: { $push: { company: "$_id.company", models: "$models", count: "$count" } },
count: { $sum: "$count" }
}
},
{ $set: { name: "$_id", _id: "$$REMOVE" } },
{
$group: {
_id: null,
productType: { $push: "$$ROOT" }
}
}
])
Mongo Playground
Try this:
db.testCollection.aggregate([
{
$group: {
_id: {
name: "$productType",
company: "$company",
model: "$model"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: {
name: "$_id.name",
company: "$_id.company"
},
count: { $sum: "$count" },
models: {
$push: {
name: "$_id.model",
count: "$count"
}
}
}
},
{
$group: {
_id: { name: "$_id.name" },
count: { $sum: "$count" },
companies: {
$push: {
name: "$_id.company",
count: "$count",
models: "$models"
}
}
}
},
{
$group: {
_id: null,
productType: {
$push: {
name: "$_id.name",
count: "$count",
companies: "$companies"
}
}
}
},
{
$project: { _id: 0 }
}
]);
Output:
{
"productType" : [
{
"name" : "Car",
"count" : 2,
"companies" : [
{
"name" : "Suzuki",
"count" : 1,
"models" : [
{
"name" : "s1",
"count" : 1
}
]
},
{
"name" : "Maruti",
"count" : 1,
"models" : [
{
"name" : "m1",
"count" : 1
}
]
}
]
},
{
"name" : "Bike",
"count" : 4,
"companies" : [
{
"name" : "yamaha",
"count" : 3,
"models" : [
{
"name" : "y2",
"count" : 1
},
{
"name" : "y1",
"count" : 2
}
]
},
{
"name" : "bajaj",
"count" : 1,
"models" : [
{
"name" : "b1",
"count" : 1
}
]
}
]
}
]
}

Flatten an object into object.name , object.value in Mongodb aggregation

I have a record in collection like this. In cities, the name of the key or values are not constant
[
{
"id" : "xxx",
"countryName" : "xxx",
"cities" : {
"melbourne" : {
"id" : "xxx",
"cityName" : "xxx",
"population" : 124
},
"brisbane" : {
"column1" : "xxx",
"column2" : "xxx"
}
.....
}
}
]
I need a response like
{
cities.melbourne : {"id" : "xxx", "cityName" : "xxx", "population" : 124 },
cities.brisbane : { "column1" : "xxx", "column2" : "xxx" },
......}
You can do as below
db.collection.aggregate([
{
"$project": { //Converting cities to array
"cities": {
"$objectToArray": "$cities"
}
}
},
{ //Flattening
"$unwind": "$cities"
},
{
"$replaceRoot": {
"newRoot": { //Reshaping to the desired structure
$arrayToObject: [
[
{
"k": {
"$concat": [
"cities",
".",
"$cities.k"
]
},
"v": "$cities.v"
}
]
]
}
}
}
])
play
Output:
[
{
"cities.melbourne": {
"cityName": "xxx",
"id": "xxx",
"population": 124
}
},
{
"cities.brisbane": {
"column1": "xxx",
"column2": "xxx"
}
}
]
You can get the expected output using the aggregation operators $objectToArray and $arrayToObject to transformation the input document.
db.collection.aggregate([
{
$addFields: { cities: { $objectToArray: "$cities" } }
},
{
$unwind: "$cities"
},
{
$addFields: { "cities.k": { $concat: [ "cities", ".", "$cities.k" ] } }
},
{
$group: { _id: "$_id", cities: { $push: "$cities" } }
},
{
$replaceWith: { $arrayToObject: "$cities" }
}
])

Compare integers stored as Strings in Mongodb

In the below collection, column "qty" holds the integer values but the datatype is string.
I want to compare the "qty" field with an integer in the aggregate and "warehouse" field with a string "A". ("qty" > 2 and "warehouse" = "A")
[Can't change the datatype in the collection to integer as huge dependency is present]
Edit : Need to retrieve all the columns and all the documents matching the criteria.
Query : getting improper results
db.runCommand(
{
aggregate: "products", pipeline: [
{
$match: {
instock: {
$elemMatch: {
warehouse: "A",
qty: { $gt: "2" }
}
}
}
},
{ $project: { _id: 0 } }],
cursor: { batchSize: 200 }
});
Result : not getting documents where item = journal though it satisfies the conditions
/* 1 */
{
"item" : "paper",
"instock" : [
{
"warehouse" : "A",
"qty" : "60"
},
{
"warehouse" : "B",
"qty" : "15"
}
]
},
/* 2 */
{
"item" : "planner",
"instock" : [
{
"warehouse" : "A",
"qty" : "22"
},
{
"warehouse" : "B",
"qty" : "5"
}
]
}
Products Collection
[
{
"item": "journal",
"instock": [
{
"warehouse": "A",
"qty": "11"
},
{
"warehouse": "C",
"qty": "15"
}
]
},
{
"item": "paper",
"instock": [
{
"warehouse": "A",
"qty": "60"
},
{
"warehouse": "B",
"qty": "15"
}
]
},
{
"item": "planner",
"instock": [
{
"warehouse": "A",
"qty": "22"
},
{
"warehouse": "B",
"qty": "5"
}
]
}
]
Getting improper results as greater than operator in this case is working lexicographically but it should work like integers. Though I tried converting that to double but I am getting no results.
Query with $convert to double : no result
db.runCommand(
{
aggregate: "products", pipeline: [
//{ $match: { "item": { $in: ["planner", "paper","journal"] } } },
{
$match: {
instock: {
$elemMatch: {
warehouse: "A",
qty: {
$gt: [
{$convert:{ input: "$qty", to: "double" }}, 5]
}
}
}
}
},
{ $project: { _id: 0 } }],
cursor: { batchSize: 200 }
});
Try this:
db.products.aggregate([
{
$unwind: "$instock"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$instock.warehouse",
"A"
]
},
{
$gt: [
{
$toInt: "$instock.qty"
},
2
]
}
]
}
}
},
{
$group: {
_id: "$_id",
item: {
$first: "$item"
},
instock: {
$push: "$instock"
}
}
},
{
$project: {
_id: 0
}
}
])
MongoPlayground
Try this, it uses $filter to retain objects has criteria :
db.runCommand(
{
aggregate: "products", pipeline: [
{ $match: { 'instock.warehouse': 'A' } },
{
$addFields: {
instockCheck: {
$filter: {
input: '$instock', as: 'each', cond: {
$and: [{ $gt: [{ $toInt: '$$each.qty' }, 2] },
{ $eq: ['$$each.warehouse', 'A'] }]
}
}
}
}
}, { $match: { instockCheck: { $gt: [] } } }, { $project: { instockCheck: 0, _id: 0 } }],
cursor: { batchSize: 200 }
});
Test : MongoDB-Playground

Create 4 level nested object aggregate output from a linear mongo collection

I have following schema
{
question: String,
answer: Number,
option1: String,
option2: String,
option3: String,
option4: String,
subject: String,
chapter: String,
topic: String,
subtopic: String,
tags: { type: Array, default: [] },
difficulty: String,
media: { type: String, default: "" }
}
I want to create a index from this question schema, where a unique entry in index can be identified by tuple (subject, chapter, topic, subtopic)
{
"subject": {
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
},
"subject": {
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
},
}
To achieve above result I have been trying some aggregations and so far I am able to get upto
{
"subject": ["chapters"],
"subject": ["chapters"],
"subject": ["chapters"],
"subject": ["chapters"],
}
using following pipeline in mongoose
{
$group: {
_id: "$subject",
chapters: { $push: "$chapter" }
}
},
{
$group: {
_id: null,
chapters: { $push: { "k": "$_id", "v": "$chapters" } }
}
},
{
$replaceRoot: { "newRoot": { "$arrayToObject": "$chapters" } }
}
When I tried to extend my above logic to include topic ans subtopics using
{
$group: {
_id: "$subject",
chapters: { $push: { "k": "$chapter", "v": { topic: "$topic", subtopic: "$subtopic" } } }
}
},
{
$group: {
_id: null,
chapters: { $push: { "k": "$_id", "v": { "$arrayToObject": "$chapters" } } }
}
},
{
$replaceRoot: { "newRoot": { "$arrayToObject": "$chapters" } }
}
I get following output
{
"subject": {
"chapter": {
"topic": "value",
"subtopic": "value"
},
"chapter": {
"topic": "value",
"subtopic": "value"
},
},
"subject": {
"chapter": {
"topic": "value",
"subtopic": "value"
},
"chapter": {
"topic": "value",
"subtopic": "value"
},
},
}
Problem is that chapter is a object of last topic and subtopic only I know where I am making mistake but I don;t know how to fix it.
You can use this aggregation that mainly utilizes $objectToArray, the data restructuring is not so elegant though:
db.collection.aggregate(
[
{
"$group" : {
"_id" : {
"subject" : "$subject",
"chapter" : "$chapter",
"topic" : "$topic"
},
"topics" : {
"$push" : "$subtopic"
}
}
},
{
"$addFields" : {
"topic" : {
"k" : "$_id.topic",
"v" : "$topics"
}
}
},
{
"$group" : {
"_id" : {
"subject" : "$_id.subject",
"chapter" : "$_id.chapter"
},
"topics" : {
"$push" : "$topic"
}
}
},
{
"$addFields" : {
"topics" : {
"$arrayToObject" : "$topics"
}
}
},
{
"$addFields" : {
"chapters" : [
{
"k" : "$_id.chapter",
"v" : "$topics"
}
]
}
},
{
"$addFields" : {
"chapters" : {
"$arrayToObject" : "$chapters"
}
}
},
{
"$group" : {
"_id" : null,
"subject" : {
"$push" : {
"k" : "$_id.subject",
"v" : "$chapters"
}
}
}
},
{
"$project" : {
"final" : {
"$arrayToObject" : "$subject"
}
}
},
{
"$replaceRoot" : {
"newRoot" : "$final"
}
}
]);