I have below collection
[
{
_id: {
giftId: "coin",
userId: "5b839dfeaaafc94ff323da35"
},
count: 1
},
{
_id: {
giftId: "coin",
userId: "5b8390cc4bf35e13fe67d545"
},
count: 2
},
{
_id: {
giftId: "Gold",
userId: "5b8390cc4bf35e13fe67d545"
},
count: 3
},
{
_id: {
giftId: "Gold",
userId: "5b8390cc4be35e13fe67d545"
},
count: 1
},
{
_id: {
giftId: "Silver",
userId: "5b8390cc4bf35e13fe67d545"
},
count: 4
},
{
_id: {
giftId: "Silver",
userId: "5b8390cc4bf35e13ff67d545"
},
count: 2
}
]
I need below output
[{
array1: [{
_id: {
giftId: "coin",
userId: "5b8390cc4bf35e13fe67d545"
},
count: 2
},
{
_id: {
giftId: "Gold",
userId: "5b8390cc4bf35e13fe67d545"
},
count: 3
},
{
_id: {
giftId: "Silver",
userId: "5b8390cc4bf35e13fe67d545"
},
count: 4
}],
array2: [{
_id: {
giftId: "coin",
userId: "5b839dfeaaafc94ff323da35"
},
count: 1
}, {
_id: {
giftId: "Silver",
userId: "5b8390cc4bf35e13ff67d545"
},
count: 2
},{
_id: {
giftId: "Gold",
userId: "5b8390cc4be35e13fe67d545"
},
count: 1
}]
}]
I need count for each giftId in an array of objects.
Suppose in above document for giftId coin we have 2 count and for giftId Gold we have 3 count
and for giftId Silver we have 4 count. So all the values with hight count should be in same array.
Any help would be highly appriciated!!!
I am using latest version of mongodb 4.0
You can use the below aggregation query.
$sort to sort the data by count.
Initial $group to get the max and min doc element for each gift_id followed by $group to push the max and min value documents into array.
db.colname.aggregate([
{"$sort":{"count":-1}},
{"$group":{"_id":"$_id.giftId","maxdoc":{"$first":"$$ROOT"}, "mindoc":{"$last":"$$ROOT"}}},
{"$group":{"_id":null,"array1":{"$push":{"_id":"$maxdoc._id", "count":"$maxdoc.count"}}, "array2":{"$push":{"_id":"$mindoc._id", "count":"$mindoc.count"}}}},
{"$project":{"_id":0}}
])
Related
I want to create a Mongodb aggregation pipeline for a collection named Transaction.
The Transaction collection has values amount, categoryID, description and I also have a Category collection with values type, icon and color.
I want the pipeline to show the top3 categories with their percentage values and a others category with its percentage value.
the transaction type should be Expense which it should get from the Category collection and it should show all transactions having category type Expense. The top3 should then give the results as transaction with category (example)
type : Rent
percentage:45
type: Entertainment
percentage: 30
type: Food
percentage: 20
type: Others
percentage: 5
I tried it with Category collection but I don't want category to store amount, but Transaction should store amount.
Category.aggregate([
{
$match: {
type: 'expense'
}
},
{
$group: {
_id: "$name",
amount: { $sum: "$amount" }
}
},
{
$group: {
_id: null,
totalExpense: { $sum: "$amount" },
categories: {
$push: {
name: "$_id",
amount: "$amount"
}
}
}
},
{
$project: {
_id: 0,
categories: {
$map: {
input: "$categories",
as: "category",
in: {
name: "$$category.name",
percent: { $multiply: [{ $divide: ["$$category.amount", "$totalExpense"] }, 100] }
}
}
}
}
},
{
$unwind: "$categories"
},
{
$sort: { "categories.percent": -1 }
},
{
$limit: 3
}
])
This was the pipeline I used for it.
//edit
Tried the method suggested by Joe
Transaction.aggregate([
// Join the Transaction collection with the Category collection
{
$lookup: {
from: 'Category',
localField: 'categoryID',
foreignField: '_id',
as: 'category',
},
},
// Unwind the category array to separate documents
{
$unwind: '$category',
},
// Filter for transactions where the category type is "Expense"
{
$match: {
'category.type': 'Expense',
},
},
// Group transactions by category type and calculate the percentage
{
$group: {
_id: '$category.type',
total: { $sum: '$amount' },
count: { $sum: 1 },
},
},
{
$project: {
_id: 0,
category: '$_id',
percentage: {
$multiply: [{ $divide: ['$count', { $sum: '$count' }] }, 100],
},
},
},
// Sort the categories by percentage in descending order
{
$sort: { percentage: -1 },
},
// Limit the result to top 3 categories
{
$limit: 3,
},
// group the rest of the categories as others
{
$group: {
_id: null,
top3: { $push: '$$ROOT' },
others: { $sum: { $subtract: [100, { $sum: '$top3.percentage' }] } },
},
},
{
$project: {
top3: 1,
others: { category: 'Others', percentage: '$others' },
},
},
]);
I am getting an empty array rather than the values. I have data in the collections with the correct ID's. What might be the issue?
//Answer
This aggregation worked for me
Transaction.aggregate([
{
$match: {
userID: { $eq: UserID },
type: 'Expense',
},
},
{
$addFields: { categoryID: { $toObjectId: '$categoryID' } },
},
{
$lookup: {
from: 'categories',
localField: 'categoryID',
foreignField: '_id',
as: 'category_info',
},
},
{
$unwind: '$category_info',
},
{
$group: {
_id: '$category_info.name',
amount: { $sum: '$amount' },
},
},
{
$sort: {
amount: -1,
},
},
{
$group: {
_id: null,
total: { $sum: '$amount' },
data: { $push: '$$ROOT' },
},
},
{
$project: {
results: {
$map: {
input: {
$slice: ['$data', 3],
},
in: {
category: '$$this._id',
percentage: {
$round: {
$multiply: [{ $divide: ['$$this.amount', '$total'] }, 100],
},
},
},
},
},
others: {
$cond: {
if: { $gt: [{ $size: '$data' }, 3] },
then: {
amount: {
$subtract: [
'$total',
{
$sum: {
$slice: ['$data.amount', 3],
},
},
],
},
percentage: {
$round: {
$multiply: [
{
$divide: [
{
$subtract: [
'$total',
{ $sum: { $slice: ['$data.amount', 3] } },
],
},
'$total',
],
},
100,
],
},
},
},
else: {
amount: null,
percentage: null,
},
},
},
},
},
]);
i am using aggregate mongodb i have the following order collection:
[
{
_id: '62079dfc71e2b7702c246fc5',
city: 'Uzbakistan',
source: 'instagram',
price: 100000
},
{
_id: '62079e3d71e2b7702c246ffa',
city: 'Pakistan',
source: 'telegram',
price: 12000
},
{
_id: '62079e7571e2b7702c24702c',
city: 'India',
source: 'instagram',
price: 21000
},
{
_id: '6201578e9bb8bd6ec10c5b09',
city: 'Uzbakistan',
source: 'twitter',
price: 18000
},
{
_id: '62020316478928faea9914d8',
city: 'Pakistan',
source: 'telegram',
price: 20000
},
...
]
Now I want to aggregate a group by city and get the total price by source, I want this result:
[
{
_id: 'Uzbakistan',
sources: [
{
_id: 'instagram',
totalPrice: 100000
},
{
_id: 'twitter',
totalPrice: 18000
}
]
},
{
_id: 'Pakistan',
sources: [
{
_id: 'telegram',
totalPrice: 32000
}
]
},
...
]
How to correctly implement the aggregate pipeline to get such a result, thanks in advance!!!
$group - Group by city and source. $sum for price field as totalPrice.
$group - Group by city. Push the documents as a sources array.
db.collection.aggregate([
{
"$group": {
"_id": {
city: "$city",
source: "$source"
},
"totalPrice": {
"$sum": "$price"
}
}
},
{
"$group": {
"_id": "$_id.city",
"sources": {
"$push": {
_id: "$_id.source",
totalPrice: "$totalPrice"
}
}
}
}
])
Sample Mongo Playground
DB Data -
[{
title: "Vivo X50",
category: "mobile",
amount: 35000
},
{
title: "Samsung M32",
category: "mobile",
amount: 18000
},
{
title: "Lenovo 15E253",
category: "laptop",
amount: 85000
},
{
title: "Dell XPS 15R",
category: "laptop",
amount: 115000
}]
Expected Output:
[{
category: "mobile",
qty: 2,
totalAmount: 53000
},
{
category: "laptop",
qty: 2,
totalAmount: 200000
}]
Code I am running (Using mongoose)
let products = await Product.aggregate([
{
$project: { _id: 0, category: 1, amount: 1 },
},
{
$group: {
_id: "$category",
qty: { $sum: 1 },
totalAmount: { $sum: "$amount" },
},
},
]);
Result I am Getting.
[
{
"_id": "laptop",
"count": 2,
"totalSum": 200000
},
{
"_id": "mobile",
"count": 2,
"totalSum": 53000
}
]
As you can clearly see that I am able to get correct data but I want correct name also category instead of _id. Please help me with that. Thanks in advance
You need $project as the last stage to decorate the output document.
{
$project: {
_id: 0,
category: "$_id",
qty: "$qty",
totalAmount: "$totalAmount"
}
}
Meanwhile, the first stage $project doesn't need.
db.collection.aggregate([
{
$group: {
_id: "$category",
qty: {
$sum: 1
},
totalAmount: {
$sum: "$amount"
}
}
},
{
$project: {
_id: 0,
category: "$_id",
qty: "$qty",
totalAmount: "$totalAmount"
}
}
])
Sample Mongo Playground
You can use the following query to get your expected output. cheers~
await Product.aggregate([
{
$group: {
_id: "$category",
qty: {
$sum: 1
},
totalAmount: {
$sum: "$amount"
},
},
},
{
$addFields: {
category: "$_id"
}
},
{
$project: {
_id: 0
},
}
])
I have a product collection in MongoDb which sole fields like _id, category, user_id.
I want to check and count the sum number of each category in collection given the matching the user_id and then sum up all the count again at the end.
my solution is :
return Product.aggregate([
{ $match: { "user_id": "id if user that added the product" } },
{ "$unwind": "$category" },
{
"$group": {
"_id": {
'category': '$category',
},
"count": { "$sum": 1 }
}
},
{ "$sort": { "_id.category": 1 } },
{
"$group": {
"_id": "$_id.category",
"count": { "$first": "$count" }
}
}
])
the code gives me the count of each category without matching the condition of user_id. But when I add the $match it fails.
Product Schema:
const ProductSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
quantity: {
type: Number,
default: -1
},
category:
{
type: String,
required: true
},
manufactured_by: {
type: String,
required: true
},
user_id: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
})
my result if I dont add the condition:
[
{
"_id": "A Tables",
"count": 1
},
{
"_id": "C Tables",
"count": 4
},
{
"_id": "B Tables",
"count": 2
}
]
Not sure what you are trying to achieve from the last stage in your pipeline.
But the following should give you desired output (without any complications that you added)
async getSellerStatistics(seller_id) {
return await Product.aggregate([
{ $match: { user_id: seller_id } }
{ $unwind: "$category" },
{
$group: {
_id: "$category",
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
])
}
I have documents like this
items = [{
owner: [{ name: "user1" }, { name: "user2" }]
},
{
owner: [{ name: "user1" }, { name: "user2" }]
},
{
owner: [{ name: "user2" }, { name: "user3" }, { name: "user4" }]
}];
So I need to count a number of the same name and group by name and rank them like this
results = [{ name: "user2", count: 3 },
{ name: "user1", count: 2 },
{ name: "user3", count: 1 },
{ name: "user4", count: 1 }];
You can use below aggregation:
db.collection.aggregate([
{
$unwind: "$owner"
},
{
$group: {
_id: "$owner.name",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
name: "$_id",
count: 1
}
},
{
$sort: {
count: -1
}
}
])
$unwind can be used to get single name per document, then you need $group to count and $sort for ranking
Mongo Playground