Express + Mongoose + Aggregation + Calculate Available Stock - mongodb

I'm using express js and mongoose and i'm new to these platforms. It would be great if someone can help me on this. Please refer the the below data and I'm looking for output like:
itemsizeId: 609578ca23033e55886e7590, AvailableQuantity: 112
itemsizeId: 609578ca23033e55886e758f, AvailableQuantity: 495
Note: Group by movementtype (inward / outward).
Available Stock: inward - outward
[
{
_id: '609fb1a1a7ed990f30d6cae2',
refId: 'Purchase-1',
itemsizeId: '609578ca23033e55886e7590',
itemId: '609578ca23033e55886e758e',
sizeId: '60950c0ba4982390f8dfed79',
movementdate: '2021-05-15T11:33:52.894Z',
movementtype: 'inward',
movementcategory: 'purchase',
quantity: 100,
id: '609fb1a1a7ed990f30d6cae2',
},
{
_id: '609fb1cba7ed990f30d6cae3',
refId: 'Purchase-2',
itemsizeId: '609578ca23033e55886e7590',
itemId: '609578ca23033e55886e758e',
sizeId: '60950c0ba4982390f8dfed79',
movementdate: '2021-05-15T11:34:35.118Z',
movementtype: 'inward',
movementcategory: 'purchase',
quantity: 20,
id: '609fb1cba7ed990f30d6cae3',
},
{
_id: '609fb1fda7ed990f30d6cae4',
refId: 'Sale-1',
itemsizeId: '609578ca23033e55886e7590',
itemId: '609578ca23033e55886e758e',
sizeId: '60950c0ba4982390f8dfed79',
movementdate: '2021-05-15T11:35:25.068Z',
movementtype: 'outward',
movementcategory: 'sales',
quantity: 5,
id: '609fb1fda7ed990f30d6cae4',
},
{
_id: '609fb255a7ed990f30d6cae5',
refId: 'Purchase-3',
itemsizeId: '609578ca23033e55886e758f',
itemId: '609578ca23033e55886e758e',
sizeId: '60950be9a4982390f8dfed78',
movementdate: '2021-05-15T11:36:53.835Z',
movementtype: 'inward',
movementcategory: 'purchase',
quantity: 500,
id: '609fb255a7ed990f30d6cae5',
},
{
_id: '609fb27ea7ed990f30d6cae6',
refId: 'Sale-2',
itemsizeId: '609578ca23033e55886e758f',
itemId: '609578ca23033e55886e758e',
sizeId: '60950be9a4982390f8dfed78',
movementdate: '2021-05-15T11:37:34.066Z',
movementtype: 'outward',
movementcategory: 'sales',
quantity: 8,
id: '609fb27ea7ed990f30d6cae6',
},
]
I have tried till below code and I'm struck to complete it:
const itemStock = await itemStockMovementModel.aggregate([
{
$match: { movementtype: 'inward' },
},
{
$group: {
_id: {
itemsizeId: '$itemsizeId',
},
quantity: { $sum: '$quantity' },
},
},
])

$group by itemsizeId and get quantity sum by condition for inward and outward
$subtract to inward - outward
const itemStock = await itemStockMovementModel.aggregate([
{
$group: {
_id: "$itemsizeId",
inward: {
$sum: { $cond: [{ $eq: ["$movementtype", "inward"] }, "$quantity", 0] }
},
outward: {
$sum: { $cond: [{ $eq: ["$movementtype", "outward"] }, "$quantity", 0] }
}
}
},
{
$project: {
quantity: {
$subtract: ["$inward", "$outward"]
}
}
}
])
Playground
You can use second formula as well, if you don't have third type in movementtype,
$group by itemsizeId
check condition if movementtype is inward then sum quantity otherwise sum negative quantity
const itemStock = await itemStockMovementModel.aggregate([
{
$group: {
_id: "$itemsizeId",
quantity: {
$sum: {
$cond: [
{ $eq: ["$movementtype", "inward"] },
"$quantity",
{ $subtract: [0, "$quantity"] }
]
}
}
}
}
])
Playground

Related

mongoose complex aggregation pipeline question

I am trying to finish up a data aggregation pipeline and having issues getting the data into the correct format. I'm not even sure if this is possible to do in one pipeline.
The original data looks like this:
[
{
answers: {
'question1': 'a',
'question2': 'c',
'question3': ['a','b'],
'question4': 1
},
createdAt: 2022-03-04T07:30:40.517Z,
},
{
answers: {
'question1': 'b',
'question2': 'c',
'question3': ['a','c']
'question4': 2
},
createdAt: 2022-03-04T07:30:40.518Z,
}
]
I've got my pipeline so far with this:
{ $project: {
"answers": { $objectToArray: "$answers" },
"date": { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" }}
}},
{ $unwind: "$answers" },
{ $unwind: "$answers.v" },
{
$group: {
_id: { answers : "$answers", date: "$date"},
c: { $sum: 1 }}
},
and the data now looks like this:
{
_id: {
answers: { k: 'q3', v: 'b' },
date: '2022-03-04'
},
count: 1
},
{
_id: {
answers: { k: 'q3', v: 'a' },
date: '2022-03-04'
},
count: 2
},
{
_id: {
answers: { k: 'q4', v: 1 },
date: '2022-03-04'
},
count: 1
},
{
_id: {
answers: { k: 'q1', v: 'b' },
date: '2022-03-04'
},
count: 1
},
{
_id: {
answers: { k: 'q4', v: 2 },
date: '2022-03-04'
},
count: 1
},
{
_id: {
answers: { k: 'q2', v: 'c' },
date: '2022-03-04'
},
count: 2
},
{
_id: {
answers: { k: 'q3', v: 'c' },
date: '2022-03-04'
},
count: 1
},
{
_id: {
answers: { k: 'q1', v: 'a' },
date: '2022-03-04'
},
count: 1
}
I would like to get a result that looks something like this:
{
'dates': [
{
'date': '2022-03-04',
'q1': { 'a': 1, 'b': 1 }
'q2': { 'c': 2 },
'q3': { 'a': 2, 'b': 1, 'c': 1 },
'q4': { '1': 1, '2': 1 }
}
]
'totals': { // this would be the totals across all the dates
'q1': { 'a': 1, 'b': 1 }
'q2': { 'c': 2 },
'q3': { 'a': 2, 'b': 1, 'c': 1 },
'q4': { '1': 1, '2': 1 }
}
}
any help would be greatly appreciated, even if I can't get both the totals and breakdown in 1 query.
here is the mongoplaygroud I've been working on
Not that simple. An important stage you have to use is $facet in order to get totals and dates
Maybe with $setWindowFields the aggregation pipeline could be a little simpler, but that a quick guess.
db.collection.aggregate([
{
$project: {
_id: 0,
answers: { $objectToArray: "$answers" },
date: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } }
}
},
{ $unwind: "$answers" },
{ $unwind: "$answers.v" },
{
$group: {
_id: {
answer: "$answers.v",
question: "$answers.k",
date: "$date"
},
count: { $sum: 1 }
}
},
{
$facet: {
dates: [
{
$group: {
_id: { question: "$_id.question", date: "$_id.date" },
count: {
$push: {
k: { $toString: "$_id.answer" },
v: "$count"
}
}
}
},
{
$group: {
_id: "$_id.date",
count: {
$push: {
k: "$_id.question",
v: { $arrayToObject: "$count" }
}
}
}
},
{
$replaceWith: {
$mergeObjects: [
{ date: "$_id" },
{ $arrayToObject: "$count" }
]
}
}
],
totals: [
{
$group: {
_id: { answer: "$_id.answer", question: "$_id.question" },
v: { $push: "$count" }
}
},
{
$group: {
_id: "$_id.question",
count: {
$push: {
k: { $toString: "$_id.answer" },
v: { $sum: "$v" }
}
}
}
},
{
$project: {
_id: 0,
k: "$_id",
v: { $arrayToObject: "$count" }
}
}
]
}
},
{ $set: { totals: { $arrayToObject: "$totals" } } }
])
Mongo Playground

How can i do with MonboDB a vlookup between two subdocuments of different collections?

I have two collections : List and ListSnapshot(taken day by day) .
-List
{
_id: { $oid: "5f0d71fc9037e9858c96f131" },
list: [
{
_id: { $oid: "5f0d71fc9037e9858c96f132" },
name: "Item1",
id: "I2WQDFU7Z8C15E",
link: "https://www.ecomm1.heroku.com",
price: 44.99,
},
{
_id: { $oid: "5f0d71fc9037e9858c96f133" },
name: "Item2",
id: "I194JQPIWR0CY5",
link: "https://www.ecomm1.heroku.com",
price: 30.99,
},
],
creationDate: { $date: "2020-07-14T08:51:01.443Z" },
user: "713cdd45df7a6b979643b0a6f4d5be52bef1ea48e7c26088d88c50ac06c1b155",
id: "366c83ffc8be98f4de043629b5dcacb607d80f560cdd5741927c7d5a80513b0f",
__v: 0,
}
-ListSnapshot
{
_id: { $oid: "5f0d824e7e777e44d8115ddc" },
list: [
{
_id: { $oid: "5f0d824e7e777e44d8115ddd" },
name: "Item1",
id: "I2WQDFU7Z8C15E",
price: 54.99,
},
{
_id: { $oid: "5f0d824e7e777e44d8115dde" },
name: "Item2",
id: "I194JQPIWR0CY5",
price: 10.99,
},
],
date: { $date: "2020-07-14T10:00:42.744Z" },
user: "713cdd45df7a6b979643b0a6f4d5be52bef1ea48e7c26088d88c50ac06c1b155",
id: "366c83ffc8be98f4de043629b5dcacb607d80f560cdd5741927c7d5a80513b0f",
__v: 0,
}
I do an aggregation with a match on "List" id and then i group by list.id to calculate some values likes (avg,max,etc..) for each item. I want to add the link field of "List" to my result.
await collection.aggregate(
[
{ $match: { [filter]: group } },
{ $unwind: "$list" },
{ $sort: { date: 1 } },
{
$group: {
_id: { id: "$list.id", name: "$list.name" },
ossNumb: { $sum: 1 },
avgValue: { $avg: "$list.price" },
maxValue: { $max: "$list.price" },
minValue: { $min: "$list.price" },
startValue: { $first: "$list.price" },
lastValue: { $last: "$list.price" },
dev: { $stdDevPop: "$list.price" },
},
},
],
async function (err, queryRes) {
if (err) {
throw err;
return err;
}
const queryResToArray = await queryRes.toArray();
resolve(queryResToArray);
}
);
This is the current output:
{"_id":{"id":"I1PF32XECEDXKQ","name":"Items1"},"avgValue":29.99,"dev":0,"lastValue":"29.99","maxValue":"29.99","minValue":"29.99","ossNumb":1,"startValue":29.99}
I need to add link from "List"

Mongo Facet Aggregation with Sum

Trying to figure out something simple in this aggregation. The field "totalArrests" under metadata is coming back 0. It's not able to sum this field from the previous stage for some reason. Please advise.
const agg = await KID.aggregate([
{
$group: {
_id: "$source", // group by this
title: { "$last": "$title"},
comments: { "$last": "$comments"},
body: { "$last": "$body"},
date: { "$last": "$date"},
media: { "$last": "$media"},
source: { "$last": "$source"},
count: { "$sum": 1},
arrestCount: { "$sum": "$arrested"},
rescuedCount: { "$sum": "$rescued"},
}
},
// sorting
{
$sort: {date: sort}
},
// facets for paging
{
$facet: {
metadata: [
{ $count: "total" }, // Returns a count of the number of documents at this stage
{ $addFields: {
page: page,
limit: 30,
totalArrests: {$sum: "$arrestCount"}
}},
],
kids: [ { $skip: (page-1)*30 }, { $limit: 30 } ]
}
},
]);
Here is a sample document in the collection.
[
{
_id: 5e8b922aaf5ccf5ac588398c,
counter: 4,
date: 2017-01-01T17:00:00.000Z,
name: 'Steven Tucker',
arrested: 1,
rescued: 0,
country: 'US',
state: 'NH',
comments: 'Sex trafficking of a minor',
source: 'https://www.justice.gov/opa/pr/new-hampshire-man-indicted-sex-trafficking-minor-connection-interstate-prostitution',
title: 'New ....',
body: 'Steven Tucker, 31, ....',
__v: 0,
media: {
title: 'New Hampshire Man Indicted for Sex ...',
open_graph: [Object],
twitter_card: [Object],
favicon: 'https://www.justice.gov/sites/default/files/favicon.png'
},
id: 5e8b922aaf5ccf5ac588398c,
text: 'New Hampshire Man Indicted',
utcDate: '2017-01-01T12:00'
}
]
$count will only provide you the count for number of documents and escapes all the other things.
So, You have to use one more pipeline in $facet in order to get the documents.
{ $facet: {
metadata: [
{ $group: {
_id: null,
total: { $sum: 1 },
totalArrested: { $sum: "$arrestCount" }
}},
{ $project: {
total: 1,
totalArrested: 1,
page: page,
limit: 30,
hasMore: { $gt: [{ $ceil: { $divide: ["$total", 30] }}, page] }
}}
],
kids: [{ $skip: (page-1) * 30 }, { $limit: 30 }]
}}

MongoDB - Help needed to make some aggregation

I am having a bad time trying to do an aggregation in MongoDB.
I need to cross some infos from each user and as a final result I want a list of users (where there is only one object for each user) and for each object there is some lists with distinct information.
1 - The createdAtList array must be ordered from the oldest to the newest date. The sumOfTotal means the current position total summed up with the previous sumOfTotal (Exemplified in the code below), not just the sum of the total's
2 - The categotyList must be ordered like: category1, category2, category3 ...
3 - The desired final result must be ordered like: user1, user2, user3 ...
Basically I need some help to do the following:
//List of docs from my collection:
[
{
_id: "doc1",
user: "user1",
category: "category1",
createdAt: "2018-01-01T00:00:00.000Z"
},
{
_id: "doc2",
user: "user1",
category: "category2",
createdAt: "2017-12-12T00:00:00.000Z",
},
{
_id: "doc3",
user: "user1",
category: "category1",
createdAt: "2017-12-12T00:00:00.000Z",
},
{
_id: "doc4",
user: "user1",
category: "category2",
createdAt: "2018-01-01T00:00:00.000Z"
},
{
_id: "doc5",
user: "user1",
category: "category3",
createdAt: "2017-11-11T00:00:00.000Z"
}
]
//Desired result:
{
user: "user1",
createdAtList: [ //list ordered by createdAt
{
createdAt: "2017-11-11T00:00:00.000Z",
total: 1,
sumOfTotal: 0
}
{
createdAt: "2017-12-12T00:00:00.000Z",
total: 2,
sumOfTotal: 3 //summed up with the previous
}
{
createdAt: "2018-01-01T00:00:00.000Z",
total: 2,
sumOfTotal: 5 //summed up with the previous
}
],
categotyList: [ //list ordered by category
{
category: "category1",
total: 2
},
{
category: "category2",
total: 2
},
{
category: "category3",
total: 1
}
]
},
...
Is possible to do this in the same aggregate?
I do not think it really makes sense to have the createdAtList.sumOfTotal field. I do not think the fields in an array should be dependent upon a particular order of the elements. If you want some field to contain the sum of the createdAtList.total field, I think there should only be one field (outside of the array). That being said, here is the query I came up with to give you the desired results (using "users" as the name of the collection):
db.users.aggregate([
{
$group: {
_id: {
user: "$user",
createdAt: "$createdAt"
},
total: { $sum: 1 },
category: { $push: "$category" }
}
},
{
$project: {
_id: 0,
user: "$_id.user",
createdAt: "$_id.createdAt",
total: "$total",
category: 1
}
},
{ $unwind: "$category" },
{
$group: {
_id: {
user: "$user",
category: "$category"
},
catTotal: { $sum: 1 },
createdAtList: {
$push: {
createdAt: "$createdAt",
total: "$total"
}
}
}
},
{
$project: {
_id: 0,
user: "$_id.user",
createdAtList: 1,
category: "$_id.category",
catTotal: 1
}
},
{ $unwind: "$createdAtList" },
{
$group: {
_id: "$user",
createdAtList: {
$addToSet: "$createdAtList"
},
categoryList: {
$addToSet: {
category: "$category",
total: "$catTotal"
}
}
}
},
{ $unwind: "$createdAtList" },
{ $sort: { "createdAtList.createdAt": 1 } },
{
$group: {
_id: "$_id",
createdAtList: {
$push: "$createdAtList"
},
categoryList: {
$first: "$categoryList"
}
}
},
{ $unwind: "$categoryList" },
{ $sort: { "categoryList.category": 1 } },
{
$group: {
_id: "$_id",
createdAtList: {
$first: "$createdAtList"
},
categoryList: {
$push: "$categoryList"
}
}
},
{
$project: {
_id: 0,
user: "$_id",
createdAtList: 1,
sumOfTotal: { $sum: "$createdAtList.total" },
categoryList: 1
}
},
{ $sort: { user: 1 } },
]).pretty()

Mongo group inside $addToSet

I have the following set of objects:
[
{
id: 1,
clientId: 1,
cost: 200
},
{
id: 1,
clientId: 2,
cost: 500
},
{
id: 1,
clientId: 2,
cost: 800
},
{
id: 2,
clientId: 1,
cost: 600
},
{
id: 2,
clientId: 2,
cost: 100
}
]
And I made a group of that with:
db.collection.aggregate(
{
'$group': {
'_id': '$id',
'clients': {
'$addToSet': {
'id': '$clientId',
'cost': '$cost'
}
}
}
}
)
So I obteined the following:
[
{
'_id': 1,
'clients': [
{
id: 1,
cost: 200
},
{
id: 2,
cost: 500
},
{
id: 2,
cost: 800
}
],
'_id': 2,
'clients': [
{
id: 1,
cost: 600
},
{
id: 2,
cost: 100
}
]
}
]
As you can see in the array of clients of the first value, I have 2 repeated and what I want is to have 1 with the cost added. So instead of have:
'clients': [
{
id: 1,
cost: 200
},
{
id: 2,
cost: 500
},
{
id: 2,
cost: 800
}
]
I need:
'clients': [
{
id: 1,
cost: 200
},
{
id: 2,
cost: 1300
}
]
So my question is: how can I do that? Because $addToSet nor $push allow $sum.
You can use aggregation operators to get expected output like following:
db.collection.aggregate({
"$group": {
"_id": {
"mainId": "$id",
"client": "$clientId"
},
"cost": {
"$sum": "$cost"
}
}
}, {
"$project": {
"mainId": "$_id.mainId",
"clients": {
"clientId": "$_id.client",
"cost": "$cost"
},
"_id": 0
}
}, {
"$group": {
"_id": "$mainId",
"clients": {
"$push": "$clients"
}
}
})