Mongodb aggregates: how to project a formatted filter - mongodb

I think it's not a difficult question but I'm not sure how to do it
My collection is
[
{ type:"bananas", weight:"1"},
{ type:"bananas", weight:"10"},
{ type:"apple", weight:"5"}
]
The result I would like to have is the count of each type in the same query, result expected:
{
bananas: 2,
apple: 1
}
We have 2 items of type "bananas" and 1 of type "apple" in the collection
So I guess I have to $project the props I want (item types) but I don't know how to count/sum/match this ?
thanks in advance for your help

Try as below:
db.collection.aggregate([
{
$group: {
_id: "$type",
count: { $sum:1 },
"data": {"$push": "$$ROOT" },
}
},
{
$project: {
"specList": {
"$arrayToObject": {
"$map": {
"input": "$data",
"as": "item",
"in": {
"k": "$$item.type",
"v": "$count",
}
}
}
}
}
},
{ $replaceRoot: { newRoot: { "$mergeObjects":[ "$specList" , { "item":"$_id"} ] } } }
])

db.getCollection('test').aggregate([
{ $match: {} },
{ $group: { _id: "$type", count: { $sum: 1 }} },
{
$replaceRoot: {
newRoot: { $arrayToObject: [ [ { k: "$_id", v: "$count" } ] ]}
}
}
])
Result:
[{
"apple" : 1.0
},
{
"bananas" : 2.0
}]
Aggregation with merge
db.getCollection('test').aggregate([
{ $match: {} },
{ $group: { _id: "$type", count: { $sum: 1 }} },
{
$replaceRoot: {
newRoot: { $arrayToObject: [ [ { k: "$_id", v: "$count" } ] ]}
}
},
{ $facet: { items: [{$match: {} }]} },
{
$replaceRoot: { newRoot: { $mergeObjects: "$items" } }
},
])
Result:
[{
"apple" : 1.0,
"bananas" : 2.0
}]

Related

Mongodb aggregation, get expected result on groupBy without hard-coding categories

My objective is to write an efficient query, that with the given input, gives me the expected output. I have some working solution, but all "types" are "manually" written, so I guess I'm looking for help to get the same output but in a different way.
input
reportId
type
weight
A
"fish"
4
A
"fish"
2
A
"cow"
0
B
"fish"
2
B
"tuna"
1
B
"bird"
Expected output
[
{
reportId: "A",
totalCount: 3,
totalWeight: 6,
fishCount: 2,
tunaCount: 0,
cowCount: 1,
birdCount: 0
},
{
reportId: "A",
totalCount: 3,
totalWeight: 2,
fishCount: 1,
tunaCount: 1,
cowCount: 0,
birdCount: 1
},
]
Partial "hard-coded" solution
What I have been doing so far is to create 2 group-by steps: It kind of get's the job done, but in my real use-case there are a lot of types, and therefore the group-stages are very long.
[
{
$group: {
_id: { reportId: "$reportId", type: $type },
count: { $sum: 1 },
totalWeight: { $sum: "$weight" }
}
},
{
$group: {
_id: "$_id.reportId",
totalCount: { $sum: "$totalCount" },
totalWeight: { $sum: "$totalWeight" },
fishCount: {
$sum: {
$cond: {
"if": { $eq: ["$_id.type", "fish"] },
then: "$count",
else: 0
}
}
},
tunaCount: {
$sum: {
$cond: {
"if": { $eq: ["$_id.type", "tuna"] },
then: "$count",
else: 0
}
}
},
// <== And here I have a count blog for each type. Can I get the same result in a better way?
}
}
]
I will focus to the second part, which is the difficult one. I don't know whether there is a shorter and better solution, but this one should work:
db.collection.aggregate([
{
$unset: "_id"
},
{
$set: {
data: {
"$objectToArray": "$$ROOT"
}
}
},
{
$group: {
_id: "$reportId",
data: {
$push: "$data"
}
}
},
{
$set: {
data: {
$reduce: {
input: "$data",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
},
{
$set: {
data: {
$filter: {
input: "$data",
cond: {
$not: {
$in: [
"$$this.k",
[
"totalCount",
"totalWeight"
]
]
}
}
}
}
}
},
{
$unwind: "$data"
},
{
$group: {
_id: "$_id",
data: {
$push: "$data"
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$data"
}
}
}
])
See Mongo playground

Mongoose Summing Up subdocument array elements having same alias

I have a document like this:
_id:'someId',
sales:
[
{
_id:'111',
alias:'xxx',
amount:500,
name: Apple, //items with same alias always have same name and quantity
quantity:2
},
{
_id:'222',
alias:'abc',
amount:100,
name: Orange,
quantity:14
},
{
_id:'333',
alias:'xxx',
amount:300,
name: Apple, //items with same alias always have same name and quantity
quantity:2
}
]
The alias field is here to 'group' items/documents whenever they appear to have same alias i.e to be 'embeded' as one with the amount summed up.
I need to display some sort of a report in such a way that those elements which have same alias they should be displayed as ONE and the others which doesn't share same alias to remain as they are.
Example, For the sample document above, I need an output like this
[
{
alias:'xxx',
amount:800
},
{
alias:'abc',
amount:100
}
]
WHAT I HAVE TRIED
MyShop.aggregate([
{$group:{
_id: "$_id",
sales:{$last :"$sales"}
},
{$project:{
"sales.amount":1
}}
}
])
This just displays as a 'list' regardless of the alias. How do I achieve summing up amount based on the alias?
You can achieve this using $group
db.collection.aggregate([
{
$unwind: "$sales"
},
{
$group: {
_id: {
_id: "$_id",
alias: "$sales.alias"
},
sales: {
$first: "$sales"
},
_idsInvolved: {
$push: "$sales._id"
},
amount: {
$sum: "$sales.amount"
}
}
},
{
$group: {
_id: "$_id._id",
sales: {
$push: {
$mergeObjects: [
"$sales",
{
alias: "$_id.alias",
amount: "$amount",
_idsInvolved: "$_idsInvolved"
}
]
}
}
}
}
])
Mongo Playground
You can use below aggregation
db.collection.aggregate([
{
"$addFields": {
"sales": {
"$map": {
"input": {
"$setUnion": [
"$sales.alias"
]
},
"as": "m",
"in": {
"$let": {
"vars": {
"a": {
"$filter": {
"input": "$sales",
"as": "d",
"cond": {
"$eq": [
"$$d.alias",
"$$m"
]
}
}
}
},
"in": {
"amount": {
"$sum": "$$a.amount"
},
"alias": "$$m",
"_idsInvolved": "$$a._id"
}
}
}
}
}
}
}
])
MongoPlayground

Mongodb - group by value and get count

I have a aggregate query , which returns result like
{
count:1,
status: 'FAILED',
article_id: 1
},
{
count:1,
status: 'DELIVERED',
article_id: 1
}
I want to group by on the article_id and get the count based on the status , something like this:
{
article_id:1,
FAILED:1,
DELIVERED:2
}
How can i archive this?
Thanks in advance.
The other answers may work in principle, however they are limited hard-coded to status FAILED and DELIVERED.
In case you like to have a generic solution for arbitrary status, you can use this one:
db.collection.aggregate([
{ $set: { data: [{ k: "$status", v: "$count" }] } },
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
{ $arrayToObject: "$data" }, { article_id: "$article_id" }
]
}
}
},
{
$group: {
_id: "$article_id",
status: { $push: "$$ROOT" }
}
},
{ $set: { status: { $mergeObjects: ["$status"] } } },
{ $replaceRoot: { newRoot: "$status" } },
])
Mongo playground
Try this code
db.getCollection('artwork').aggregate([
{
$group: {
_id: '$article_id',
FAILED: {
'$sum': {
"$cond": [{ "$eq": ["$status", "FAILED"] }, 1, 0]
}
},
DELIVERED: {
'$sum': {
"$cond": [{ "$eq": ["$status", "DELIVERED"] }, 1, 0]
}
}
}
}
])
mongoplayground
{
$group:{
_id:"$_id",
articleId:{$addToset:"$article_id"},
failed:{$addToset:"$failed"},
delivered:{$addToset:"$delivered"}
}
},
{ $addFields:{
article_id:{$size:articleId},
fail:{$size:"$faild"},
deliver:{$size:"$faild"},
}
In group $addToSet will return array and in addField will return total length of array. you cans earch $size in mongo
}

MongoDB count occurances with group and unwind

I have a MongoDB database with the following document structure:
{
"name": "ServiceA",
"areas": ["X", "Y", "Z"],
"tags": [
{
"name": "Financial",
"type": "A"
},
{
"name": "Consumer",
"type": "B"
}
]
}
There's many entries each with the same structure. Containing the same areas.
There's many predefined tag names, sorted into a few types.
The aim is to group by area and then count the number of occurrences of each tag. So an output like this:
{
"area": "X",
"count": 100, // Total entries with X as an area
"tagNameCount": {
"Financial": 20,
"Consumer": 10,
...
},
"tagTypeCount": {
"A": 70,,
"B: 40
}
}
I've been starting of using $unwind on areas, but it's the next steps from there I'm stuck on. I get that I need to use $group, but I can't work out how to count occurrences.
You may use $facet operator which allows perform several aggregation in one.
Walkthrough
1. We $unwind by area and tags
2. With $facet, we perform 3 parallel aggregations:
2.1 We count unique areas
2.2 We count unique tag names for each area
2.3 We count unique tag type for each area
3. We join 2 parallel arrays by flatten areas
4. We assemble desired output
db.collection.aggregate([
{
$unwind: "$areas"
},
{
$unwind: "$tags"
},
{
$facet: {
areas: [
{
$group: {
_id: "$areas",
count: {
$addToSet: "$_id"
}
}
},
{
$project: {
_id: 0,
area: "$_id",
count: {
$size: "$count"
}
}
}
],
tagNameCount: [
{
$group: {
_id: {
name: "$tags.name",
areas: "$areas"
},
count: {
$addToSet: "$_id"
}
}
},
{
$group: {
_id: "$_id.areas",
tagNameCount: {
$push: {
k: "$_id.name",
v: {
$size: "$count"
}
}
}
}
},
{
$addFields: {
tagNameCount: {
$arrayToObject: "$tagNameCount"
}
}
}
],
tagTypeCount: [
{
$group: {
_id: {
type: "$tags.type",
areas: "$areas"
},
count: {
$addToSet: "$_id"
}
}
},
{
$group: {
_id: "$_id.areas",
tagTypeCount: {
$push: {
k: "$_id.type",
v: {
$size: "$count"
}
}
}
}
},
{
$addFields: {
tagTypeCount: {
$arrayToObject: "$tagTypeCount"
}
}
}
]
}
},
{
$unwind: "$areas"
},
{
$addFields: {
"tagNameCount": {
$filter: {
input: "$tagNameCount",
cond: {
$eq: [
"$areas.area",
"$$this._id"
]
}
}
},
"tagTypeCount": {
$filter: {
input: "$tagTypeCount",
cond: {
$eq: [
"$areas.area",
"$$this._id"
]
}
}
}
}
},
{
$project: {
area: "$areas.area",
count: "$areas.count",
tagNameCount: {
$arrayElemAt: [
"$tagNameCount.tagNameCount",
0
]
},
tagTypeCount: {
$arrayElemAt: [
"$tagTypeCount.tagTypeCount",
0
]
}
}
},
{
$sort: {
area: 1
}
}
])
MongoPlayground
Here's one method:
unwind both areas and tags
for each area collect the applicable tags, and the unique names and types
count the names to get the total number of tags
for each unique name, count the matching values in the tags
do the same for each unique type
project out the unique fields
db.collection.aggregate([
{$unwind: "$areas"},
{$unwind: "$tags"},
{$group: {
_id: "$areas",
names: {$push: "$tags.name"},
uniqueNames: {$addToSet: "$tags.name"},
types: {$push: "$tags.type"},
uniqueTypes: {$addToSet: "$tags.type"}
}},
{$addFields: {
count: {$size: "$names"},
names: {
$arrayToObject: {
$map: {
input: "$uniqueNames",
as: "needle",
in: {
k: "$$needle",
v: {
$size: {
$filter: {
input: "$names",
cond: {$eq: ["$$this","$$needle"]}
}}}}}}},
types: {
$arrayToObject: {
$map: {
input: "$uniqueTypes",
as: "needle",
in: {
k: "$$needle",
v: {$size: {
$filter: {
input: "$types",
cond: { $eq: [ "$$this","$$needle"]}
}}}}}}}}},
{
$project: {
uniqueNames: 0,
uniqueTypes: 0
}}
])
Playground

mognodb aggregation group by actor

I have the following film collection structure:
{
"_id" : ObjectId,
"title" : "movie-1",
"actors" : [
"actor-1",
"actor-2",
"actor-3",
],
"categories" : [
"category-1",
"category-2"
]
}
I want to display result of all actors with associate movies and categories as like as given below:
{
"actor": "actor-1",
"result": {
"category-1": [ "movie-1", "movie-2" ],
"category-2": [ "movie-1", "movie-4" ]
}
}
I have tried aggregation as like as given below:
db.film.aggregate([
{ $unwind: "$actors" },
{ $group: {
_id: "$actors",
data: { $push: { movie: "$title", categories: "$categories" } }
}
},
{
$project: {
_id: 0,
actor: "$_id",
result: {
$reduce: {
input: "$data",
initialValue: {},
in: {
$let: {
vars: { movie: "$$this.movie", categories: "$$this.categories" },
in: {
$arrayToObject: {
$map: {
input: "$$categories",
in: { k: "$$this", v: "$$movie" }
}
}
}
}
}
}
}
}
}
])
But I get all actors list with only one movie with category as like as given below:
{
"actor" : "actor-1",
"result" : {
"category-1" : "movie-1",
"category-2" : "movie-2",
"category-3" : "movie-3"
}
}
How can I solve this problem? Thanks in advance.
You may need to do another $unwind on the categories array after flattening the actors array then group all the flattened docs by the two fields i.e. actor and category fields to create the movie titles list.
Another group to shape the result field is required.
The following pipeline should give you the desired result:
db.film.aggregate([
{ "$unwind": "$actors" },
{ "$unwind": "$categories" },
{ "$group": {
"_id": { "actor": "$actors", "category": "$categories" },
"movies": { "$push": "$title" }
} },
{ "$group": {
"_id": "$_id.actor",
"result": {
"$push": {
"k": "$_id.category",
"v": "$movies"
}
}
} },
{ "$addFields": {
"result": { "$arrayToObject": "$result" }
} }
])
I've used a sledgehammer to crack a nut (c)
Some stages could be replaced by $reduce, done inside $project stage (criticism and suggestions will be welcome)
db.film.aggregate([
{
$unwind: "$actors"
},
{
$group: {
_id: "$actors",
data: {
$push: {
movie: "$title",
categories: "$categories"
}
}
}
},
{
$unwind: "$data"
},
{
$unwind: "$data.categories"
},
{
$group: {
_id: {
actors: "$_id",
categories: "$data.categories"
},
movies: {
$push: "$data.movie"
}
}
},
{
$project: {
_id: 0,
actor: "$_id.actors",
result: {
k: "$_id.categories",
v: "$movies"
}
}
},
{
$group: {
_id: "$actor",
result: {
$push: "$result"
}
}
},
{
$project: {
_id: 0,
actor: "$_id",
result: {
$arrayToObject: "$result"
}
}
},
{
$sort: {
actor: 1
}
}
])
MongoPlayground