Getting array of group _id while using MongoDB - mongodb

I need to calculate some record using group in mongoDB but in my case array of _id are coming. I am explaining my query below.
let query = {
$group: {
_id: "$Products.ProductName",
dispatchcount: { $sum: 1 },
totalDispatchValue: {$sum:"$TotalAmount.TotalPayment"},
totalDiscountValue: {$sum: "$TotalDiscount.TotalDiscountAmount"}
}
}
pipeLine.push(query);
const orders = await dispatchOrdersCol.aggregate(pipeLine);
Actual Output:
[
{
"_id": [
"Unisex Navy Blue Belt"
],
"dispatchcount": 1,
"totalDispatchValue": 27922,
"totalDiscountValue": 4084
},
{
"_id": [
"Writing Ruled Note Book",
"Physics Record Book",
"Chemistry Record Book",
"Computer Science Record Book"
],
"dispatchcount": 1,
"totalDispatchValue": 2190,
"totalDiscountValue": 0
},
{
"_id": [
"PU2-Physics Part-1 Text Book",
"PU2-Physics Part-2 Text Book",
"PU2-Mathematics Part - 1 Text Book",
"PU2-Chemistry Part - 1 Text Book",
"PU2-English Text Book",
"PU2-Mathematics Part - 2 Text Book",
"PU2-Chemistry Part - 2 Text Book",
"PU2-English Work Book",
"PU2-TEXT BOOK",
"PU2-Sanskrit Text Book",
"Boys White & Blue Striped Half Shirt",
"Boys Navy Blue Regular Fit Trousers",
"Illume Calf-length Cotton Black Socks  "
],
"dispatchcount": 1,
"totalDispatchValue": 4131,
"totalDiscountValue": 150
},
{
"_id": [
"PU2-TEXT BOOK"
],
"dispatchcount": 1,
"totalDispatchValue": 1679,
"totalDiscountValue": 0
}
]
Here for _id key there are multiple values coming and some of the values also repeating in next record.
Mongoose Model:
const Model = new Schema({
DispatchId: { type: Number, required: true },
OrderNumber: { type: String, unique: true, required: true },
OrderStatus: { type: String },
OrderType: { type: String },
DispatchPriority: { type: String },
Comments: { type: Array },
Products: { type: Array },
Customer: { type: Object },
TotalDiscount: { type: Object },
TotalShipping: { type: Object },
TotalCoupon: { type: Object },
TotalAmount: { type: Object },
PaymentDetails: { type: Object },
ClientDetails: { type: Object },
DispatchCreatedAt: { type: String },
DispatchUpdatedAt: { type: String },
IsActive: { type: Boolean }
},
{
timestamps: {
createdAt: 'CreatedAt',
updatedAt: 'UpdatedAt'
},
collection: DISPATCH_ORDERS_COLLECTION
}
);
Here I need to calculate the total Payment and total Discount as per the product name.But in my case one product is also coming two times in two record.

In your documents Products is an array, (might be an array of Objects with a field ProductName) when you do "$Products.ProductName" it will give you an array of products names. So $group has to group on arrays with exactly same elements inside them(Even a mis-match of one element between two arrays would be treated as two values on _id in group stage). So you need to do $unwind on Products array prior to $group stage :
In-correct Query :
db.collection.aggregate([
/** You don't need this addFields stage, just given to test, you can remove `$group` stage & check the `products` field value */
{
$addFields: {
products: "$products.productName"
}
},
{
$group: {
_id: "$products" // arrays
}
}
])
Test : mongoplayground
Correct Query :
db.collection.aggregate([
{
$unwind: "$products"
},
{
$addFields: {
products: "$products.productName"
}
},
{
$group: {
_id: "$products" // Single value
}
}
])
Test : mongoplayground
Ref : $unwind

Related

filter by subdocument while showing all parent document mongoose mongodb

This is the schema model
const tokoSchema = new mongoose.Schema({
name: String
product: [{display: Boolean}]
}
So, what I want is filtering the data by product.display. When I filtered it using toko.find({"product.display": true}) it only shows list of toko that has product.display: true but what I want is if the product display is false it keep showing the _id and name of toko with empty array of product []. Example query expected
// showing all tokos
[
{
_id: blabla
name: "test",
product: [{_id: blabla, display: true}], // filter display:true
},
{
_id: blabla,
name: "test",
product: [] // no product with display:true
}
]
example of toko.find({"product.display": true}) query
// not showing all tokos
[
{
_id: blabla
name: "test",
product: [{_id: blabla, display: true}]
},
]
any solution for this? should I use aggregate?
You need $filter for an array:
db.toko.aggregate([
{
$addFields: {
product: {
$filter: {
input: "$product",
cond: {
$eq: [ "$$this.display", true ]
}
}
}
}
}
])
Mongo Playground

Update multiple embeded documents in array based on their property values

I have a demo collection. In every document of the collection there is a array which contains several documents.A sample document is:
{
_id: 1,
persons: [{
name: "Jack",
gender: "Male"
}, {
name: "Ma",
gender: "Female"
}, {
name: "Ho",
gender: "Other"
}]
}
Think I want to change only the name property of gender= Male and gender= Other of nested documents in persons array of document _id = 1 and based on the gender's value the name property of each document value will be different.
Think gender= Male's document's name's value will be "Jul" and gender= Other's document's name's value will be "Tisa"
How can I design my single update query?
You want to use arrayfilters
db.collection.updateOne(
{
_id: 1,
},
{
$set: {
"persons.$[elem].name": "new_name",
}
},
{
arrayFilters: [{"elem.gender": {$in: ["Male", "Other"]}}]
});
EDIT:
db.collection.updateOne(
{
_id: 1,
},
{
$set: {
"persons.$[elem].name": "Jul",
"persons.$[elem2].name": "Tisa",
}
},
{
arrayFilters: [
{"elem.gender": "Male"},
{"elem2.gender": "Other"},
]
});

MongoDB sum with match

I have a collection with the following data structure:
{
_id: ObjectId,
text: 'This contains some text',
type: 'one',
category: {
name: 'Testing',
slug: 'test'
},
state: 'active'
}
What I'm ultimately trying to do is get a list of categories and counts. I'm using the following:
const query = [
{
$match: {
state: 'active'
}
},
{
$project: {
_id: 0,
categories: 1
}
},
{
$unwind: '$categories'
},
{
$group: {
_id: { category: '$categories.name', slug: '$categories.slug' },
count: { $sum: 1 }
}
}
]
This returns all categories (that are active) and the total counts for documents matching each category.
The problem is that I want to introduce two additional $match that should still return all the unique categories, but only affect the counts. For example, I'm trying to add a text search (which is indexed on the text field) and also a match for type.
I can't do this at the top of the pipeline because it would then only return categories that match, not only affect the $sum. So basically it would be like being able to add a $match within the $group only for the $sum. Haven't been able to find a solution for this and any help would be greatly appreciated. Thank you!
You can use $cond inside of your $group statement:
{
$group: {
_id: { category: '$categories.name', slug: '$categories.slug' },
count: { $sum: { $cond: [ { $eq: [ "$categories.type", "one" ] }, 1, 0 ] } }
}
}

Unique field values for subset of documents

Here is a MongoDB query and aggregation puzzle. Fist some sample records:
# Document 1
{
items: [
{
type: "X",
id: 123
},
{
type: "Y",
id: 456
}
]
}
# Document 2
{
items: [
{
type: "A",
id: 789
},
{
type: "B",
id: 321
}
]
}
# Document 3
{
items: [
{
type: "P",
id: 987
},
{
type: "X",
id: 654
}
]
}
# Document 4
{
items: [
{
type: "Q",
id: 246
},
{
type: "X",
id: 654
}
]
}
My goal is to find all the distinct id values for documents which contain a type: X where the id is the id associated with the type: X element.
For example, in the above, what I would like my result to be is:
[ 123, 654 ]
These are the unique values of the id field associated with the type: X.
You can start with unwinding your items. Then you can filter out by type. In last step you can utilize $addToSet operator which will eliminate duplicates.
db.collection.aggregate([
{ $unwind: "$items" },
{ $match: { "items.type": "X" } },
{
$group: {
_id: 1,
ids: { $addToSet: "$items.id" }
}
}
])
Grouping with _id: 1 means that I'm grouping by whatever: I know that everything should be a part of only one group, but I need to use $addToSet.
You can use below aggregation to get unique ids.
Query $filters the items where type is input type followed by $arrayElemAt to project matching item and $let to extract the id field.
$group all the items with $addToSet id values to output the unique values.
db.collection_name.aggregate([{"$group":{
"_id":null,
"ids":{
"$addToSet":{
"$let":{
"vars":{
"obj":{
"$arrayElemAt":[
{"$filter":{
"input":"$items",
"cond":{"$eq":["$$this.type","X"]}
}},
0
]
}
},
"in":"$$obj.id"
}
}
}
}}])

Mongoose aggregate issue with grouping by nested field

I have a schema that stores user attendance for events:
event:
_id: ObjectId,
...
attendances: [{
user: {
type: ObjectId,
ref: 'User'
},
answer: {
type: String,
enum: ['yes', 'no', 'maybe']
}
}]
}
Sample data:
_id: '533483aecb41af00009a94c3',
attendances: [{
user: '531770ea14d1f0d0ec42ae57',
answer: 'yes',
}, {
user: '53177a2114d1f0d0ec42ae63',
answer: 'maybe',
}],
I would like to return this data in the following format when I query for all attendances for a user:
var attendances = {
yes: ['533497dfcb41af00009a94d8'], // These are the event IDs
no: [],
maybe: ['533497dfcb41af00009a94d6', '533497dfcb41af00009a94d2']
}
I am not sure the aggegation pipeline will return it in this format? So I was thinking I could return this and modify it easily:
var attendances = [
answer: 'yes',
ids: ['533497dfcb41af00009a94d8'],
},{
answer: 'no',
ids: ['533497dfcb41af00009a94d8']
}, {
answer: 'maybe',
ids: ['533497dfcb41af00009a94d6', '533497dfcb41af00009a94d2']
}];
My attempt is not succesful however. It doesn't group it by answer:
this.aggregate({
$match: {
'attendances.user': someUserId
}
}, {
$project: {
answer: '$attendances.answer'
}
}, {
$group: {
_id: '$answer',
ids: {
$addToSet: "$_id"
}
}
}, function(e, docs) {
});
Can I return the data I need in the first desired format and if not how can I correct the above code to achieve the desired result?
On that note - perhaps the map-reduce process would be better suited?
The below query will help you get close to the answer you want. Although, it isn't exactly in the same format you are expecting, you get separate documents for each answer option and an array of event id's.
db.collection.aggregate([
// Unwind the 'attendances' array
{"$unwind" : "$attendances"},
// Match for user
{"$match" : {"attendances.user" : "53177a2114d1f0d0ec42ae63"}},
// Group by answer and push the event id's to an array
{"$group" : {_id : "$attendances.answer", eventids : {$push : "$_id"}}}
])
This produces the below output:
{
"result" : [
{
"_id" : "yes",
"eventids" : [
ObjectId("53d968a8c4840ac54443a9d6")
]
},
{
"_id" : "maybe",
"eventids" : [
ObjectId("53d968a8c4840ac54443a9d7"),
ObjectId("53d968a8c4840ac54443a9d8")
]
}
],
"ok" : 1
}