I'm trying to get a sub gruop from a query using nodejs and mongoose.
The thing I'm trying to do is the following:
I have this collection:
I Need to count and group all the documents with the same 'intent' and make a subgroup with the 'entity' value, so far I have this running:
try {
//We first get the total interactions from all workspace
let workspace = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: "$workspace", data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec();
//We then get the total results from conversations
let results = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: '$intent', data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec()
//workspaceItems = workspace.map(function (Interaction) { return Interaction._id; });
return res.json({
total: workspace,
result: results
})
} catch (err) {
console.log(err);
return res.status(500).send(err)
}
The result look like this:
{
"total": [
{
"_id": "Business",
"data": 23
}
],
"result": [
{
"_id": "N/A",
"data": 2
},
{
"_id": "PRODUCTO_BENEFICIOS",
"data": 3
},
{
"_id": "PRODUCTO_DESCRIPCION",
"data": 10
},
{
"_id": "REPORTE_TARJETA_PERDIDA",
"data": 1
},
{
"_id": "REQUISITOS",
"data": 7
}
]
}
I need the result in this way :
{
"total": [
{
"_id": "Business",
"data": 23
}
],
"result": [
{
"_id": "N/A",
"data": 2
},
{
"_id": "PRODUCTO_BENEFICIOS",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 3
},
{
"_id": "PRODUCTO_DESCRIPCION",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 10
},
{
"_id": "REPORTE_TARJETA_PERDIDA",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 1
},
{
"_id": "REQUISITOS",
"entities: [{"TARJETAS","PROMOCIONES"."ETC..."}],
"data": 7
}
]
}
I Hope to be clear, please let me know if you know how to do this using mongoose.
Thank you in advance.
try changing the 2nd query to following
let results = await Interaction.aggregate([
{ $match: { dateAdded: { $gte: todayStart, $lt: todayEnd }, workspace: workspaceID } },
{ $group: { _id: '$intent', entities: {$push: "$entity"}, data: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]).exec()
if you want a unique list of entities you can use $addToSet instead of $push
Related
I have a Mongo database filled with "Events" records, that look like this:
{
timestamp: 2022-03-15T22:11:34.711Z,
_id: new ObjectId("62310f16b0d71321e887a905")
}
Using a NodeJs server, I need to fetch the last 30 days of Events, grouped/summed by date, and any dates within that 30 days with no records need to be filled with 0.
Using this code I can get the correct events, grouped/summed by date:
Event.aggregate( [
{
$match: {
timestamp: {
$gte: start,
$lte: end,
}
}
},
{
$project: {
date: {
$dateToParts: { date: "$timestamp" }
},
}
},
{
$group: {
_id: {
date: {
year: "$date.year",
month: "$date.month",
day: "$date.day"
}
},
"count": { "$sum": 1 }
}
}
] )
This will return something like this:
[
{
"_id": {
"date": {
"year": 2022,
"month": 3,
"day": 14
}
},
"count": 3
},
{
"_id": {
"date": {
"year": 2022,
"month": 3,
"day": 15
}
},
"count": 8
},
]
I also have this Javascript code to generate the last 30 days of dates:
const getDateRange = (start, end) => {
const arr = [];
for(let dt = new Date(start); dt <= end; dt.setDate(dt.getDate() + 1)){
arr.push(new Date(dt));
}
return arr;
};
const subtractDays = (date, days) => {
return new Date(date.getTime() - (days * 24 * 60 * 60 * 1000));
}
const end = new Date();
const start = subtractDays(end, 30);
const range = getDateRange(start, end);
Which returns something like this:
[
2022-03-09T01:13:10.769Z,
2022-03-10T01:13:10.769Z,
2022-03-11T01:13:10.769Z,
2022-03-12T01:13:10.769Z,
2022-03-13T01:13:10.769Z,
...
]
It seems like I have all the pieces, but I'm having trouble putting all this together to do what I need in an efficient way. Any push in the right direction would be appreciated.
Whenever one has to work with date/time arithmetic then I recommend a library like moment.js
const end = moment().startOf('day').toDate();
const start = moment().startOf('day').subtract(30, 'day').toDate();
In MongoDB version 5.0 you can use $dateTrunc(), which is shorter than $dateToParts and { year: "$date.year", month: "$date.month", day: "$date.day" }
You need to put all data in an array ({$group: {_id: null, data: { $push: "$$ROOT" }}) and then at missing elements with $ifNull:
event.aggregate([
{
$match: {
timestamp: { $gte: start, $lte: end }
}
},
{
$group: {
_id: { $dateTrunc: { date: "$timestamp", unit: "day" } },
count: { $sum: 1 }
}
},
{ $project: {timestamp: "$_id", count: 1, _id: 0} },
{
$group: {
_id: null,
data: { $push: "$$ROOT" }
}
},
{
$set: {
data: {
$map: {
input: { $range: [0, 30] },
as: "i",
in: {
$let: {
vars: {
day: { $dateAdd: { startDate: start, amount: "day", unit: "$$i" } }
},
in: {
$ifNull: [
{
$first: {
$filter: {
input: "$data",
cond: { $eq: ["$$this.timestamp", "$$day"] }
}
}
},
{ timestamp: "$$day", count: 0 }
]
}
}
}
}
}
}
},
{ $unwind: "$data" }
])
$range operator supports only integer values, that's the reason for using $let. Otherwise, if you prefer to use the external generated range, it would be
{
$set: {
data: {
$map: {
input: range,
as: "day",
in: {
$ifNull: [
{
$first: {
$filter: {
input: "$data",
cond: { $eq: ["$$this.timestamp", "$$day"] }
}
}
},
{ timestamp: "$$day", count: 0 }
]
}
}
}
}
}
And for MongoDB version 5.1 you may have a look at $densify
Use aggregation stage densify if you're using MongoDB version 5.1 or later. But for lower version, below query can be used.
db.collection.aggregate([
{
$match: {
timestamp: {
$gte: {
"$date": "2022-03-01T00:00:00.000Z"
},
$lte: {
"$date": "2022-03-31T23:59:59.999Z"
},
}
}
},
{
$project: {
date: {
$dateToParts: {
date: "$timestamp"
}
},
}
},
{
$group: {
_id: {
date: {
year: "$date.year",
month: "$date.month",
day: "$date.day"
}
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": null,
"originData": {
"$push": "$$ROOT"
}
}
},
{
"$project": {
"_id": 0,
"data": {
"$concatArrays": [
{
"$map": {
"input": {
"$range": [
0,
30,
1
]
},
"in": {
"$let": {
"vars": {
"date": {
"$add": [
{
"$date": "2022-03-01T00:00:00.000Z"
},
{
"$multiply": [
"$$this",
86400000
]
}
]
}
},
"in": {
"_id": {
"date": {
"day": {
"$dayOfMonth": "$$date"
},
"month": {
"$month": "$$date"
},
"year": {
"$year": "$$date"
}
}
},
"count": 0
}
}
}
}
},
"$originData"
]
}
}
},
{
"$unwind": "$data"
},
{
$group: {
_id: {
date: {
year: "$data._id.date.year",
month: "$data._id.date.month",
day: "$data._id.date.day"
}
},
"count": {
"$sum": "$data.count"
}
}
},
{
"$sort": {
"_id.date.year": 1,
"_id.date.month": 1,
"_id.date.day": 1
}
}
])
Link to online playground. https://mongoplayground.net/p/5I0I04HoHXm
Orders.aggregate([{$match: { shippingType: "standardShipping" }},
{ $project: { standardShippingCount: { $size: "$products" } } },
])
Orders.aggregate([{$match: { shippingType: "expressShipping" } },
{ $project: { expressShippingCount: { $size: "$products" } } },
])
I need help to find out if it's possible to write these 2 queries in 1. Any help is appericated.
If you only need the number of total products ordered with each shippingType from the whole collection, then you can use this aggregate query
[
{
$unwind: {
path: "$products"
},
},
{
$group: {
_id: "$shippingType",
count: {
$count: {}
}
}
},
]
It will give you a response in this format
[
{
"_id": "expressShipping",
"count": 14
},
{
"_id": "standardShipping",
"count": 6
}
]
Now to convert it into a format that suits your use-case
queryResponse.reduce((acc, curr) => {
acc[`${curr._id}Count`] = curr.count;
return acc;
}, {})
Finally, you'll have this
{
expressShippingCount: 14,
standardShippingCount: 6
}
I am trying to find the latest "order" in "orders" array in the whole collection (Not only in the one object).
Data:
[
{
_id: 1,
orders: [
{
title: 'Burger',
date: {
$date: '2021-07-18T13:12:08.717Z',
},
},
],
},
{
_id: 2,
orders: [
{
title: 'Salad',
date: {
$date: '2021-07-18T13:35:01.586Z',
},
},
],
},
];
Code:
var restaurant = await Restaurant.findOne({
'orders.date': 1,
});
Rather simple:
db.collection.aggregate([
{ $project: { latest_order: { $max: "$orders.date" } } }
])
If you like to get the full order use this:
db.collection.aggregate([
{
$project: {
latest_order: {
$first: {
$filter: {
input: "$orders",
cond: { $eq: [ "$$this.date", { $max: "$orders.date" } ] }
}
}
}
}
},
{ $sort: { "latest_order.date": 1 } },
{ $limit: 1 }
])
Mongo Playground
You have to use aggregation for that
db.collection.aggregate([
{ $unwind: "$orders" },
{ $sort: { "orders.date": -1 } },
{ $limit: 1 },
{
"$group": {
"_id": "$_id",
"orders": { "$first": "$orders" }
}
}
])
Working Mongo playground
Please i am new to node js and MongoDB.
When i want to retrieve a post by id, i want to be able to retrieve the previous post and next post also.
this is my post, it only retrieves the current post by id.
Post.findById(req.params.postId)
.then((existingpost) => {
console.log(Post.find(req.params.postId))
if (existingpost) {
res.send(existingpost);
}
return res.status(404).send({
message: "Post does not exist with id " + req.params.postId,
});
})
.catch((err) => {
if (err.kind === "ObjectId") {
return res.status(404).send({
message: "Post does not exist with id " + req.params.postId,
});
}
return res.status(500).send({
message:
"Some error occurred while retrieving the post with postId " +
req.params.postId,
});
});
};
I currently receive the object with the id like this which is fine.
{
"_id": "6009f3e294d8a033402a76e7",
"title": "Covid 19 in Italy",
"author": "John Doe",
"createdAt": "2021-01-21T21:36:34.514Z",
"updatedAt": "2021-01-21T21:36:34.514Z",
"__v": 0
}
But i will love to receive the object of the current id, the previous object and the next object.
something like this.
[{
"_id": "3230g5e382d8a033402a76e7",
"title": "Effect of Covid on the Economy",
"author": "John Doe",
"createdAt": "2021-01-21T21:36:34.514Z",
"updatedAt": "2021-01-21T21:36:34.514Z",
"__v": 0
},
{
"_id": "6009f3e294d8a033402a76e7",
"title": "Covid 19 in Italy",
"author": "John Doe",
"createdAt": "2021-01-21T21:36:34.514Z",
"updatedAt": "2021-01-21T21:36:34.514Z",
"__v": 0
},
{
"_id": "4567hye294d8a033402a76e7",
"title": "Life after Covid",
"author": "John Doe",
"createdAt": "2021-01-21T21:36:34.514Z",
"updatedAt": "2021-01-21T21:36:34.514Z",
"__v": 0
}]
Since its a UUID, this approach might help you..
$sort to sort the documents by asc
$group and $unwind to get the index
$facet to categorize the incoming data into current and allDocs
We know current is only one object, so we do $unwind to deconstruct the array
We already know the index, so we use $filter to get prev, current and next using index
$unwind to deconstruct the array
$replaceRoot to make the objects to the root
Here is the script
db.collection.aggregate([
$sort: { createdAt: 1 } },
{
$group: {
_id: null,
data: { $push: "$$ROOT"}
}
},
{ $unwind: { path: "$data", includeArrayIndex: "index" } },
{
$facet: {
current: [
{ $match: { "data._id": "3230g5e382d8a033402a76e7" } }
],
allDocs: [
{ $match: {} }
]
}
},
{
$unwind: "$current"
},
{
$project: {
docs: {
$filter: {
input: "$allDocs",
cond: {
$or: [
{ $eq: [ "$$this.index", { $subtract: [ "$current.index", 1 ] } ] },
{ $eq: [ "$$this.index", "$current.index" ] },
{ $eq: [ "$$this.index", { $add: [ "$current.index", 1 ] } ] }
]
}
}
}
}
},
{ "$unwind": "$docs" },
{ "$replaceRoot": { "newRoot": "$docs.data" } }
])
Working Mongo playground
There are many ways to do this, this is one of the way. If you feel you have a lot of document, then try to avoid $unwind which is expensive, in that case you can try using createdDate instead of index
I am not sure is there any straight way to do this, you can try aggregation,
Using UUID and CreatedAt:
$facet to get all documents in all after sorting in ascending order by createdAt
$let to define vars states with start and total documents,
$cond check condition if index of input uuid is zero then return start: 0 and total: 2 documents we have to slice from all array, else get current index and subtract minus 1 and total: 3
in to return slice documents on the base of start and total
Post.aggregate([
{ $facet: { all: [{ $sort: { createdAt: 1 } }] } },
{
$project: {
result: {
$let: {
vars: {
states: {
$cond: [
{ $eq: [{ $indexOfArray: ["$all._id", req.params.postId] }, 0] },
{ start: 0, total: 2 },
{
start: {
$subtract: [{ $indexOfArray: ["$all._id", req.params.postId] }, 1]
},
total: 3
}
]
}
},
in: { $slice: ["$all", "$$states.start", "$$states.total"] }
}
}
}
}
])
Playground
Using ObjectID:
convert your string input id req.params.postId to object id using mongoose.Types.ObjectId
$facet to separate result,
first, $match to get current and next documents, $sort _id in descending order, $limit 2
second, $match to get previous document, $sort _id in descending order, $limit 1
$project to get result after concat both array first and second using $concatArrays
req.params.postId = mongoose.Types.ObjectId(req.params.postId);
Post.aggregate([
{
$facet: {
first: [
{ $match: { _id: { $gte: req.params.postId } } },
{ $sort: { _id: 1 } },
{ $limit: 2 }
],
second: [
{ $match: { _id: { $lt: req.params.postId } } },
{ $sort: { _id: -1 } },
{ $limit: 1 }
]
}
},
{ $project: { result: { $concatArrays: ["$first", "$second"] } } }
])
Playground
I simply want to count the element in array based on the query. I tried the following command but not solved my problem.
I want to count the element whose TimeStamp is in between "2017-02-17T18:30:00.000Z and "2017-02-18T18:29:59.999Z" on DATA2 array, but it returns only 1.
CODE Executed:
CODE 1
db.ABC.aggregate([{
$match: {
$and: [{
DATA2: {
$exists: true
}
}, {
"DATA2.TimeStamp": {
$gte: require('../../modules/getDates').getFromDate(item),
$lte: require('../../modules/getDates').getToDate(item)
}
}, {
Client_id: "123" /*req.query.client_id*/
}]
}
}, {
$project: {
DATASiz: {
$size: "$DATA2"
},
"has bananas": {
$in: ["DATA2.$.TimeStamp"]
}
}
}], function(err, result) {
console.log(result)
callBack();
})
Code 2
db.abc.find({ $and:[{DATA2: {$exists: true}},{Client_id: "123"},{"DATA2": { $elemMatch: { TimeStamp: { $gte: require('../../modules/getDates').getFromDate(item), $lte: require('../../modules/getDates').getToDate(item) } } }}]
}, function(err, result) {
console.log(JSON.stringify(result))
callBack();
})
Code 3
//db.abc.find //also tried
db.abc.count({
$and: [{
DATA2: {
$exists: true
}
}, {
"DATA2.TimeStamp": {
$gte: require('../../modules/getDates').getFromDate(item),
$lte: require('../../modules/getDates').getToDate(item)
}
}, {
Client_id: "123" /*req.query.client_id*/
}]
},{
"DATA2.$":1
}, function(err, result) {
console.log(result)
callBack();
})
JSON Format:
{
"_id": {
"$oid": "57c7404985737e2c78fde6b3"
},
"ABC": "1304258470",
"Status": "Not Found",
"DATA1": [
{123},{123},{123}
],
"Remark": "Not Found",
"DATA2": [
{
"TimeStamp": "2017-02-18T09:01:43.060Z",
"NdrStatus": "Door Locked",
},
{
"TimeStamp": "2017-02-18T08:09:43.347Z",
"NdrStatus": "HOLD",
},
{
"TimeStamp": "2017-02-20T08:09:43.347Z",
"NdrStatus": "HOLD",
}
]
}
Result:
I am getting the first element of DATA2 using CODE 3 but I know that as per the query 2 elements are to return.
I expect 2 as in count.
Also used $unwind $redact
Thanks in advance.
You can use the $filter and $size operators for this:
var start = require('../../modules/getDates').getFromDate(item),
end = require('../../modules/getDates').getToDate(item);
db.ABC.aggregate([
{
"$match": {
"DATA2": { "$exists": true },
"DATA2.TimeStamp": { "$gte": start, "$lte": end },
"Client_id": "123"
}
},
{
"$project": {
"DATASiz": {
"$size": {
"$filter": {
"input": "$DATA2",
"as": "item",
"cond": {
"$and": [
{ "$gte": ["$$item.TimeStamp", start] },
{ "$lte": ["$$item.TimeStamp", end] }
]
}
}
}
}
}
}
], function(err, result) {
console.log(result);
callBack();
});