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

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

Related

Grouping and summing after using $addToSet in MongoDB

Assume I have the following data:
[{
"type" : "DIVIDEND_OR_INTEREST",
"netAmount" : 2.43,
"transactionDate" : "2019-01-01T17:02:36+0000",
"transactionId" : 1,
"transactionItem" : {
"instrument" : {
"symbol" : "SPHD"
}
}
},
{
"type" : "DIVIDEND_OR_INTEREST",
"netAmount" : 5.00,
"transactionDate" : "2019-01-01T17:02:36+0000",
"transactionId" : 2,
"transactionItem" : {
"instrument" : {
"symbol" : "ATT"
}
}
},
{
"type" : "DIVIDEND_OR_INTEREST",
"netAmount" : 2.43,
"transactionDate" : "2019-02-01T17:02:36+0000",
"transactionId" : 3,
"transactionItem" : {
"instrument" : {
"symbol" : "SPHD"
}
}
},
{
"type" : "DIVIDEND_OR_INTEREST",
"netAmount" : 5.00,
"transactionDate" : "2019-02-01T17:02:36+0000",
"transactionId" : 4,
"transactionItem" : {
"instrument" : {
"symbol" : "ATT"
}
}
}]
I want to group the data by year and get a total sum for that year. I also want an array of the items used during the group, grouped by a field and summed, if that makes sense. This is ultimately what I want to end up with:
{
"year": [
{
"year": "2019",
"totalYear": 14.86,
"dividends": [
{
"symbol": "T",
"amount": 10.00
},
{
"symbol": "SPHD",
"amount": 4.86
}
]
}
]
}
Below is the code I have written so far using Mongoose. The problem is that I can't figure out how to group and sum the items that I added to the set. I could always do that in the application layer but I was hoping to accomplish this entirely inside of a query.:
const [transactions] = await Transaction.aggregate([
{ $match: { type: TransactionType.DIVIDEND_OR_INTEREST, netAmount: { $gte: 0 } } },
{
$facet: {
year: [
{
$group: {
_id: { $dateToString: { format: '%Y', date: '$transactionDate' } },
totalYear: { $sum: '$netAmount' },
dividends: {
$addToSet: {
symbol: '$transactionItem.instrument.symbol',
amount: '$netAmount',
},
},
},
},
{ $sort: { _id: 1 } },
{
$project: {
year: '$_id',
totalYear: { $round: ['$totalYear', 2] },
dividends: '$dividends',
_id: false,
},
},
],
},
},
]).exec();
It requires to do two group stages,
First group by year and symbol
Second group by only year
If the transactionDate field has date type value then just use $year operator to get the year
I would suggest you do $sort after the immediate $match stage to use an index if you have created or planning for future
const [transactions] = await Transaction.aggregate([
{
$match: {
type: TransactionType.DIVIDEND_OR_INTEREST,
netAmount: { $gte: 0 }
}
},
{ $sort: { transactionDate: 1 } },
{
$facet: {
year: [
{
$group: {
_id: {
year: { $year: "$transactionDate" },
symbol: "$transactionItem.instrument.symbol"
},
netAmount: { $sum: "$netAmount" }
}
},
{
$group: {
_id: "$_id.year",
totalYear: { $sum: "$netAmount" },
dividends: {
$push: {
symbol: "$_id.symbol",
amount: "$netAmount"
}
}
}
},
{
$project: {
_id: 0,
year: "$_id",
totalYear: 1,
dividends: 1
}
}
]
}
}
]).exec();
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
}
]
}
]
}
]
}

Aggregate Fields together

I have the following structure as an input from which data needs to be aggregated:
I need to aggregate the data such that I end up with the following structure:
start: A {
tripdetails: [{
destination: B [{
duration: 10,
type: male
},
duration: 12,
type: female
},
duration: 9,
type: female
}]
]}
}
Basically I need to group "type" and "duration" together under the same destination.
I came up with the following query, but this results in a a single field for "type" for each "destination", but not for every "duration".
db.test.aggregate(
{
$group: {
_id: {"StationID": "$start", "EndStationID": "$destination"},
durations: {$addToSet: "$duration" },
usertypes: {$addToSet: "$type" }
}
},
{
$group: {
_id: "$_id.StationID",
Tripcount_out: {$sum: "durations"},
Trips_out: { $addToSet: { EndStationID: "$_id.EndStationID", Tripduration: "$durations", Usertype: "$usertypes"} }
}
}
)
My question is how I can achieve the structure described above.
You could try running the following aggregate pipeline:
db.test.aggregate([
{
"$group": {
"_id": { "StationID": "$start", "EndStationID": "$destination" },
"details": {
"$push": {
"duration": "$duration",
"type": "$type"
}
}
}
},
{
"$group": {
"_id": "$_id.StationID",
"tripdetails": {
"$push": {
"destination": "$_id.EndStationID",
"trips": "$details"
}
}
}
}
])
which yields:
{
"_id" : "A",
"tripdetails" : [
{
"destination" : "B",
"trips" : [
{
"duration" : 10,
"type" : "male"
},
{
"duration" : 9,
"type" : "female"
},
{
"duration" : 12,
"type" : "female"
}
]
}
]
}

MongoDB nested object aggregation sum and sort

I have highly nested mongodb set of objects and i want to sort subofdocuments according to the result of sum their votes for example :
{
"_id":17846384es,
"company_name":"company1",
"products":[
{
"product_id":"123785",
"product_name":"product1",
"user_votes":[
{
"user_id":1,
"vote":1
},
{
"user_id":2,
"vote":2
}
]
},
{
"product_id":"98765",
"product_name":"product2",
"user_votes":[
{
"user_id":5,
"vote":3
},
{
"user_id":3,
"vote":3
}
]
}
]
}
i want to sort as descending products according to the result of sum their votes
the expected output is
{
"_id":17846384es,
"company_name":"company1",
"products":[
{
"product_id":"98765",
"product_name":"product2",
"user_votes":[
{
"user_id":5,
"vote":3
},
{
"user_id":3,
"vote":3
}
]
"votes":8
},
{
"product_id":"123785",
"product_name":"product1",
"user_votes":[
{
"user_id":1,
"vote":1
},
{
"user_id":2,
"vote":2
}
],
"votes":3
}
]
}
Any Idea ?
The following pipeline
db.products.aggregate([
{ $unwind: "$products" },
{
$project: {
company_name: 1,
products: 1,
totalVotes: {
$sum: "$products.user_votes.vote"
}
}
},
{ $sort: { totalVotes: -1 } },
{
$group: {
_id: "$_id",
company_name: { $first: "$company_name" },
products: { $push: "$products" }
}
}
])
would output
{
"_id" : "17846384es",
"company_name" : "company1",
"products" : [
{
"product_id" : "98765",
"product_name" : "product2",
"user_votes" : [
{
"user_id" : 5,
"vote" : 3
},
{
"user_id" : 3,
"vote" : 3
}
]
},
{
"product_id" : "123785",
"product_name" : "product1",
"user_votes" : [
{
"user_id" : 1,
"vote" : 1
},
{
"user_id" : 2,
"vote" : 2
}
]
}
]
}
In case you want to keep the sum of the votes at the product level as shown in your expected output simply modify the $project stage as follows
...
{
$project: {
company_name: 1,
products: {
product_id: 1,
product_name: 1,
user_votes: 1,
votes: { $sum: "$products.user_votes.vote" }
}
}
}
...

How to aggregate common values in Mongo?

I have following document :
{ asset : {
assetName : abc,
attributes : {
Artist : XYZ,
Duration : 10
}
}
}
{ asset : {
assetName : pqr,
attributes : {
Artist : EFG,
Duration : 5
}
}
}
{ asset : {
assetName : HIJ,
attributes : {
Artist : XYZ,
Duration : 10
}
}
}
I want to collect retrieve data result as follows :
{ result : {
Artist: {
values : [XYZ, EFG],
freq : [2, 1]
}
Duration : {
values : [10, 5],
freq : [2, 1]
}
}
Result should be sorted based upon the frequency.
Will be querying using mongoose package of node.
You can use aggregation
db.collectionName.aggregate(
[
{ "$group": {
"_id":null,
"Artist": { "$push":"$asset.attributes.Artist" },
"Duration": { "$push":"$asset.attributes.Duration" }
}
},
{ "$project": { "_id":0,"Artist":1,"Duration": 1 }}
]
)
Edit
As per new edited question you can do following aggregation to get the result
db.collectionName.aggregate({
"$group": {
"_id": null,
"data": {
"$push": {
"artist": "$asset.attributes.Artist",
"duration": "$asset.attributes.Duration"
}
}
}
}, {
"$unwind": "$data"
}, {
"$group": {
"_id": {
"a": "$data.artist",
"b": "$data.duration"
},
"count": {
"$sum": 1
}
}
}, {
"$group": {
"_id": null,
"ArtistValues": {
"$push": "$_id.a"
},
"ArtistFreq": {
"$push": "$count"
},
"DurationValue": {
"$push": "$_id.b"
},
"durationfreq": {
"$push": "$count"
}
}
}, {
"$project": {
"Artist": {
"values": "$ArtistValues",
"freq": "$ArtistFreq"
},
"Duration": {
"values": "$DurationValue",
"freq": "$durationfreq"
},
"_id": 0
}
}).pretty()