I have three fields, id, date and qty in my DB. I want to group them by the id and find the qty which is the latest quantity of every month. So for every month, the date with the latest day of every month, the qty will be returned for it.
If the input is
[
{
"id": "ABC",
"date": "2020-10-02 15:03:00.00",
"qty": 500,
},
{
"id": "ABC",
"date": "2020-10-31 20:22:00.00",
"qty": 100,
},
{
"id": "ABC",
"date": "2020-11-03 04:22:00.00",
"qty": 200,
},
{
"id": "ABC",
"date": "2020-11-18 04:22:00.00",
"qty": 50,
},
{
"id": "ABC1",
"date": "2020-11-05 04:22:00.00",
"qty": 5000,
},
{
"id": "ABC1",
"date": "2020-11-15 04:22:00.00",
"qty": 4580,
},
]
then the output should be
[
{
"id": "ABC",
"qtys": [
{
"date": "2020-10-31 20:22:00.00",
"qty": 100
},
{
"date": "2020-11-18 04:22:00.00",
"qty": 50
}
]
},
{
"id": "ABC1",
"qtys": [
{
"date": "2020-11-15 04:22:00.00",
"qty": 4580
}
]
},
]
$addFields to convert date field from string type to date type, if its already date type then ignore this stage
$sort by date in descending order
$group by id, month and year after extracting from date field
using $year and $month to get first document
$group by only id and construct array of quantities in qtys
db.collection.aggregate([
{ $addFields: { date: { $toDate: "$date" } } },
{ $sort: { date: -1 } },
{
$group: {
_id: {
id: "$id",
month: { $month: "$date" },
year: { $year: "$date" }
},
qtys: { $first: { date: "$date", qty: "$qty" } }
}
},
{
$group: {
_id: "$_id.id",
qtys: { $push: "$qtys" }
}
}
])
Playground
Related
How can I get all the orders from the seller and then sort them by the previous date to the present?
This is what I get for all transactions, when I Order.find(),
[
{
"_id": "636357777877c919bb2cfa45",
"products": [
{
"productId": "636355a07877c919bb2cdfdc",
"quantity": 1,
"sellerId": "636355187877c919bb2cdf1f",
"_id": "636357777877c919bb2cfa46"
}
],
"amount": 100,
"createdAt": "2022-10-03T05:53:59.997Z",
},
{
"_id": "636357da7877c919bb2d035f",
"products": [
{
"productId": "636357387877c919bb2cf494",
"quantity": 1,
"sellerId": "636355187877c919bb2cdf1f",
"_id": "636357da7877c919bb2d0360"
}
],
"amount": 100,
"createdAt": "2022-11-03T05:55:38.858Z",
"updatedAt": "2022-11-03T05:55:38.858Z",
"__v": 0
},
{
"_id": "636367d2407816df5f589bc6",
"products": [
{
"productId": "63635acf7877c919bb2d3d95",
"quantity": 1,
"sellerId": "636355187877c919bb2cdf1f",
"_id": "636367d2407816df5f589bc7"
}
],
"amount": 20,
"createdAt": "2022-11-03T07:03:46.350Z",
}
]
So what I'm trying to get here is the total amount of the seller during a certain month. For example, I have 3 orders here, one of them is from October, while the other two are from November.
So the output should be.
October = 100
November = 120
I tried the code below, but I received an empty array
This is my URL
http://localhost:5000/api/order/previousSales/636355187877c919bb2cdf1f
router.get('/previousSales/:id', async (req,res) => {
const {id} = req.params
const date = new Date();
const lastMonth = new Date(date.setMonth(date.getMonth() - 1));
const previousMonth = new Date(new Date().setMonth(lastMonth.getMonth() - 1));
try {
const order = await Order.aggregate([
{
$match: {
createdAt: {gte: previousMonth}, ...(id && {
products: {$elemMatch: {id}}
})
}
},
{
$project: {
month: { $month: "$createdAt" },
sales: "$amount",
},
},
{
$group: {
_id: "$month",
total: { $sum: "$sales" },
},
},
])
res.status(200).json(order)
} catch (error) {
res.status(400).json({message: error.message})
}
})
EDIT
I forgot to mention that the Object Id in URL belongs to the sellerId inside the product array.
You can change your $match step to this:
{$match: {
createdAt: {$gte: previousMonth},
products: {$elemMatch: {sellerId: id}}
}},
Since currently you are looking for the field id but you want to look for the field sellerId.
See how it works on the playground example
I am new to MongoDB, trying to write an aggregation function such that my output for the input should be same as below
[
{
"_id": {
"month": 1,
"year": 2022
},
"childServices": [
{"service":"MCT Latency", "sli":99.9},
{"service":"MCT Packet Loss", "sli":99.9}
],
"service": "Network"
},
{
"_id": {
"month": 2,
"year": 2022
},
"childServices": [
{"service":"MCT Latency", "sli":98.9},
{"service":"MCT Packet Loss", "sli":99.9}
]
"service": "Network",
}
]
Tried with below, but it's not grouping each childService by date.
[{
$unwind: {
path: '$childServices'
}
}, {
$group: {
_id: {
month: {
$month: '$date'
},
year: {
$year: '$date'
}
},
service: {
$first: '$service'
},
childServices: {
$first: '$childServices.service'
},
sli: {
$avg: '$childServices.availability'
}
}
}, {
$sort: {
'_id.month': 1,
'_id.year': 1
}
}]
SAMPLE DATA
[{
"_id": {
"$oid": "62fc99c00f5b1cb61d5f1072"
},
"service": "Network",
"date": "01/02/2022 00:32:51",
"childServices": [
{
"service": "MCT Latency",
"availability": 99.9,
},
{
"service": "MCT Packet Loss",
"availability": 99.9,
}
},
{
"_id": {
"$oid": "62fc99df0f5b1cb61d5f1073"
},
"service": "Network",
"date": "02/02/2022 00:32:51",
"childServices": [
{
"service": "MCT Latency",
"availability": 98.3,
},
"service": "MCT Packet Loss",
"availability": 99.9,
}
}
]
Basically, I want to get into the childService > pick each service > group them by month+year and get their monthly avg.
Convert the date from a string to a date type, before grouping, like this:
db.collection.aggregate([
{
$unwind: {
path: "$childServices"
}
},
{
$addFields: {
date: {
"$toDate": "$date"
}
}
},
{
$group: { <---- Here we are grouping the data for each distinct combination of month, year and child service. This needs to be done because we are using $first accumulator
_id: {
month: {
$month: "$date"
},
year: {
$year: "$date"
},
service: "$childServices.service"
},
service: {
$first: "$service"
},
childServices: {
$first: "$childServices.service"
},
sli: {
$avg: "$childServices.availability"
}
}
},
{
"$group": { <-- In this group, we groupBy month and year, and we push the child services record into an array, using $push. This gives us, for every month and year, the average of all distinct childservices
"_id": {
month: "$_id.month",
year: "$_id.year"
},
"childServices": {
"$push": {
service: "$childServices",
sli: "$sli"
}
}
}
},
{
$sort: {
"_id.month": 1,
"_id.year": 1
}
}
])
Playground link.
I want to retrieve all the users who have communicated with the page on the date entered from the front, the data structure below,
[
{
"_id": "1",
"createdAt": "2019-02-20T21:34:17.634Z",
"updatedAt": "2022-03-01T20:47:55.100Z",
"fullName": "Jennifer Lieutard",
"firstName": "Jennifer",
"lastName": "Lieutard",
"conversations": [{
"page": "1000",
"messages": [
{
"content": "lorem ipsum",
"date": "2019-09-23T10:40:59.394Z"
},
{
"content": "lorem not ipsum",
"date": "2019-09-23T10:51:56.165Z"
},
{...}
]
}]
},
{
"_id": "2",
"createdAt": "2019-02-20T21:34:17.634Z",
"updatedAt": "2022-03-01T20:47:55.100Z",
"fullName": "Peter Pan",
"firstName": "Peter",
"lastName": "Pan",
"conversations": [{
"page": "1001",
"lastMessage": "Yes they can",
"messages": [
{
"content": "lorem ipsum",
"date": "2019-09-23T10:40:59.394Z"
},
{
"content": "lorem not ipsum",
"date": "2019-09-23T10:51:56.165Z"
},
{...}
]
}]
}
]
/*PAGES collections*/
{
"_id": "5dfb43c3bc1ca6634d559685",
"name": "All mighty",
"updatedAt": "2022-03-20T11:29:30.049Z"
}
what i did
$match: {
$expr: {
$and: [
{ $eq: [{ $month: "$createdAt" }, { $month: searchByDate == 'undefined' ? new Date() : new Date(searchByDate)}] },
{ $eq: [ { $year: "$createdAt"}, { $year: searchByDate == 'undefined' ? new Date() : new Date(searchByDate)}] }
]
},
"conversations": {
$elemMatch: {
"page": ObjectId(pageId),
"$and": [
{formatedDate: {$dateToString: { format: "%Y-%m", date: "$createdAt" }}},
"2019-02"
]
}
}
}
},
{
$group: {
_id: {
$dayOfMonth: "$createdAt"
},
"users": {
$push: {
fullName: "$fullName",
createdDate: "$createdAt",
formatedDate: {$dateToString: { format: "%Y-%m", date: "$messages.0.date" }},
}
},
"dates": {
$push: {
"createdAt": "$createdAt",
"month": {
$month: "$createdAt"
},
"day": {
$dayOfMonth: "$createdAt"
}
}
},
count:{$sum:1}
}
}
searchByDate is (YYYY-MM) value input from the frontend, example : 2019-20,
therefore must convert the date in base of the type 2020-12-08T11:05:54.609+00:00 in 2020-12 and compared to searchByDate, and here is what I got as an error
MongoError: unknown operator: $dateToString
Basically the task that I want to achieve is the following
filter to retrieve only the users who spoke to the page in the
desired month, that is to say the conversations.page == pageId
Retrieve the first message in this conversation of the page, ie the message.0
Convert the date of this message to a Year-Month format (YYYY-MM)
compare this converted date to the entered value 2019-02
I have data like this:
[
{
"channel": "abc",
"date": "2019-01-01",
"revenue": 100,
"quantity": 100,
},
{
"channel": "xyz",
"date": "2019-02-10",
"revenue": 100,
"quantity": 100,
},
{
"channel": "def",
"date": "2020-01-01",
"revenue": 100,
"quantity": 100,
},
{
"channel": "abc",
"date": "2021-06-01",
"revenue": 100,
"quantity": 100,
},
{
"channel": "abc",
"date": "2021-06-12",
"revenue": 100,
"quantity": 100,
}
]
I want to group by channel and push data and again group by date (in month and year only) and push data and add a field after these pushes. The dates are all Date objects, not Strings. The avg_revenue is tot_revenue divided by tot_quantity.
[
{
"channel": "abc",
"dates": [
{
"date": "2019-01",
"totals": {
"tot_revenue": 100,
"tot_quantity": 100,
"avg_revenue": 1,
}
},
{
"date": "2019-01",
"totals": {
"tot_revenue": 200,
"tot_quantity": 200,
"avg_revenue": 1,
}
}
]
},
{
"channel": "def",
"dates": [
{
"date": "2020-01",
"totals": {
"tot_revenue": 100,
"tot_quantity": 100,
"avg_revenue": 1,
}
}
]
},
{
"channel": "xyz",
"dates": [
{
"date": "2019-02",
"totals": {
"tot_revenue": 100,
"tot_quantity": 100,
"avg_revenue": 1,
}
}
]
},
]
My attempt:
db.collection.aggregate([
{
"$set": {
"date": {
"$dateFromString": {
"dateString": "$date",
"format": "%Y-%m-%d"
}
}
}
},
{
$group: {
_id: {
channel: "$channel",
month: {
$month: "$date"
},
year: {
$year: "$date"
}
},
report_dates: {
$push: {
report_date: "$date",
revenue: "$revenue",
quantity: "$quantity",
}
},
}
},
{
$group: {
_id: {
month: "$month",
year: "$year",
},
values: {
$push: {
revenue: "$revenue",
quantity: "$quantity",
}
},
}
}
])
You need to create an aggregation pipeline that consists of two $group steps, the first to group all the documents by the channel and date fields whilst accumulating the tot_revenue and tot_quantity aggregates. The other $group stage will compute the dates list with the totals.
The following pipeline should give the desired output:
db.collection.aggregate([
{ '$group': {
'_id': {
'channel': '$channel',
'date': {
'$dateToString': {
'format': "%Y-%m", 'date': {
"$dateFromString": {
"dateString": "$date",
"format": "%Y-%m-%d"
}
}
}
}
},
'tot_revenue': { '$sum': '$revenue' },
'tot_quantity': { '$sum': '$quantity' },
} },
{ '$group': {
'_id': '$_id.channel',
'dates': {
'$push': {
'date': '$_id.date',
'totals': {
'tot_revenue': '$tot_revenue',
'tot_quantity': '$tot_quantity',
'avg_revenue': { '$divide': ['$tot_revenue','$tot_quantity'] }
}
}
}
} }
])
I have a Mongo collection that looks like this with a bunch of months, days, years:
[
{
"Date": ISODate("2021-08-05T04:59:54.000Z"),
"Amount": 999,
"Business": "Business 1",
},
{
"Date": ISODate("2021-08-05T04:59:54.000Z"),
"Amount": 5.99,
"Business": "Business 2",
},
{
"Date": ISODate("2021-07-17T21:41:56.000Z"),
"Amount": 20000,
"Business": "Business 2",
},
{
"Date": ISODate("2021-06-17T21:41:56.000Z"),
"Amount": 200,
"Business": "Business 5",
}
]
I have done an aggregation like this
db.collection.aggregate({
$group: {
_id: {
year: {
$year: "$Date"
},
month: {
$month: "$Date"
}
},
sum: {
$sum: "$Amount"
}
}
})
...which partially gives me what I want which is a sum of amounts per year and month.
[
{
"_id": {
"month": 6,
"year": 2021
},
"sum": 200
},
{
"_id": {
"month": 7,
"year": 2021
},
"sum": 20000
},
{
"_id": {
"month": 8,
"year": 2021
},
"sum": 1004.99
}
]
What I would like however is to have something like the below where the year is at the top and the months are aggregated in a sum so that it's easier to iterate in the frontend but I have not been able to get it no matter what I have tried:
[
{
"year": 2021,
"sumAmount": 21204.99,
"months": [
{
"month": 7,
"amount": 20000
},
{
"month": 6,
"amount": 200
},
{
"month": 8,
"amount": 1004.99
}
]
},
{ "year" : 2020,
....
}
]
I have been pretty close in using another $group and $push but I have not been able to get what in my mind is a second group by month. Any help will be appreciated!
You just need one more $group to get your expected result. For another sorting, you can put an $sort after the $group stage. You will need to use $push to keep the ordering in the final array.
db.collection.aggregate([
{
$group: {
_id: {
year: {
$year: "$Date"
},
month: {
$month: "$Date"
}
},
sum: {
$sum: "$Amount"
}
}
},
{
"$sort": {
"_id.year": 1,
"_id.month": 1
}
},
{
"$group": {
"_id": "$_id.year",
"sumAmount": {
$sum: "$sum"
},
"months": {
"$push": {
"month": "$_id.month",
"amount": "$sum"
}
}
}
}
])
Here is the Mongo playground for your reference.