how to perform group by in mongodb - node.js

I'm having this data in MongoDB, I want to perform group by on this data to get all the tracking numbers, under the same id in a single array format.
* 1 */
{
"_id" : ObjectId("597056182a93692b4c7691bf"),
"Sid":1,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "121_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "779591314278"
},
{
"TrackingNo" : "779591314039"
},
{
"TrackingNo" : "779591314231"
}
]
}
]
}
/* 2 */
{
"_id" : ObjectId("597057338c65c002e4285fb3"),
"Sid":2,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "122_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "77959131427"
},
{
"TrackingNo" : "77959131403"
}
]
}
]
}
/* 3 */
{
"_id" : ObjectId("5980ae7ecc71b581b626d20b"),
"Sid":3,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "123_dom"
}
]
},
{
"Tracking" : [
{
"TrackingNo" : "77959131408"
},
{
"TrackingNo" : "779591314059"
},
{
"TrackingNo" : "779591315551"
}
]
}
]
}
I.e on performing group by operation on _id, which shows all tracking numbers under the same _Id in a single array, means the result set will be like this :
{
"_id" : ObjectId("597056182a93692b4c7691bf"),
"Sid":1,
"Carriers" : [
{
"Tracking" : [
{
"TrackingNo" : "121_dom"
}
{
"TrackingNo" : "779591314278"
},
{
"TrackingNo" : "779591314039"
},
{
"TrackingNo" : "779591314231"
}
]
}
]
}

As you have array within array, you first need to unwind twice and then group again. Following query should work. You need to project additional fields in $group and $project as required.
db.shipments.aggregate(
{$unwind:'$Carriers'},
{$unwind: '$Carriers.Tracking'},
{$group: {_id:'$_id', c: {$push: '$Carriers.Tracking'}}},
{$project:{Carriers: {Tracking: '$c'}}}
)

According to description as mentioned in above question please try executing following aggregate query into MongoDB shell as a solution to above mentioned question.
db.shipments.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: '$Carriers'
}
},
// Stage 2
{
$unwind: {
path: '$Carriers.Tracking'
}
},
// Stage 3
{
$group: {
_id: {
_id: '$_id',
Sid: '$Sid'
},
Carriers: {
$addToSet: {
TrackingNo: '$Carriers.Tracking.TrackingNo'
}
}
}
},
// Stage 4
{
$group: {
_id: '$_id',
Carriers: {
$addToSet: {
Tracking: '$Carriers'
}
}
}
},
// Stage 5
{
$project: {
_id: '$_id._id',
Sid: '$_id.Sid',
Carriers: '$Carriers'
}
}
]
);

Related

MongoDB aggregation: FieldPath may not start with $

This MongoDB aggregation is failing:
Attendance.aggregate([
{ $match: { cohort_id: cohort_id} },
{ $unwind: "$absences" },
{
$group: {
_id: {
term: "$absences.term",
$function:
{
body: function (day) {
return day.getDay();
},
args: ["$absences.formatted_date.day"],
lang: "js",
},
},
count: { $sum: 1 },
},
},
{ $sort: { count: 1 } },
])
with this error:
uncaught exception: Error: command failed: {
"ok" : 0,
"errmsg" : "FieldPath field names may not start with '$'. Consider using $getField or $setField.",
"code" : 16410,
"codeName" : "Location16410"
} with original command request: {
"aggregate" : "attendances",
"pipeline" : [
{
"$match" : {
"cohort_id" : "61858e13dc5e0d1ce0238abd"
}
},
{
"$unwind" : "$absences"
},
{
"$group" : {
"_id" : {
"term" : "$absences.term",
"$function" : {
"body" : function (day) { return day.getDay(); },
"args" : [
"$absences.formatted_date.day"
],
"lang" : "js"
}
},
"count" : {
"$sum" : 1
}
}
},
{
"$sort" : {
"count" : 1
}
}
],
"cursor" : {
},
"lsid" : {
"id" : UUID("b4505aa0-e65e-46cd-8e31-03e4ecdbfe3b")
}
}
...
Not the most helpful error message.
Where am I referencing a field name wrong? Looks like it's expecting a field name without $ somewhere, but I can't seem to find where.
I've seen similar posts about this error, but they generally have to do with $project and $sort which does not seem to be the problem here
Thank you!
It considers $function as field name. I think it should be like this:
{
$group: {
_id: {
term: "$absences.term",
day: {
$function: {
body: function (day) {
return day.getDay();
},
args: ["$absences.formatted_date.day"],
lang: "js",
},
},
count: { $sum: 1 },
},
}
Is this a school homework? day.getDay() sounds to be a very simple function which should be available native in MongoDB Query Language.
Found a solution that's simpler and that works:
Attendance.aggregate([
{ $match: { cohort_id: cohort_id} },
{ $unwind: "$absences" },
{
$group: {
_id: {
term: "$absences.term",
day: {
$dayOfWeek: "$absences.formatted_date.day"
},
},
count: { $sum: 1 },
},
},
{ $sort: { count: 1 } },
])

Get Total Count using aggregate + facet using Mongo

I want to group my data based on event date with pagination. However what i am getting is whole record totalcount instead of eventDate count. because of this UI part is not working properly. Here is my collection sample:
{
"_id" : ObjectId("5fc4d0009a25e8cfbe306381"),
"eventDate" : ISODate("2021-11-29T01:00:00.000Z"),
"team1" : {
"tName" : "Chicago Bears",
},
"team2" : {
"tName" : "Green Bay Packers",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063ff"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "Nashville SC",
},
"team2" : {
"tName" : "Columbus Crew",
}
}
{
"_id" : ObjectId("5fc4d0019a25e8cfbe3063f4"),
"eventDate" : ISODate("2021-11-30T01:00:00.000Z"),
"team1" : {
"tName" : "yyyy",
},
"team2" : {
"tName" : "xxxx",
}
}
here is my query:
db.getCollection('game').aggregate([
{ $addFields: { "newEventDate": {$dateToString:{ format: "%Y-%m-%d", date: "$eventDate" }}}},
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$facet": {
"resultData": [
{ "$match": {
"eventDate": { $gte: new Date() }
}
},
{ "$group": {
"_id": "$newEventDate",
"games": {$push: {
team1:"$team1",
team2:"$team2"
}}
}
},
{ $sort: {eventDate: 1} },
{
$limit: 1
}
],
"pageInfo": [
{ "$count": "totalRecords" }
]}
}
]);
After executing this query this is my response:
{
"resultData" : [
{
"_id" : "2021-11-29",
"games" : [
{
"awayTeam" : {
"tName" : "Chicago Bears"
},
"homeTeam" : {
"tName" : "Green Bay Packers"
}
},
]
}
],
"pageInfo" : [
{
"totalRecords" : 3 **[here i want 2 ie total event date]**
}
]
}
$match your condition
move your $group stage outside from $facet, convert your date from string inside group, add you date in group stage because we are going to sort in next stage
$sort by eventDate ascending order
$facet, first get single record using $limit, and second part get total count of the record using $count
db.collection.aggregate([
{ $match: { eventDate: { $gte: new Date() } } },
{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$eventDate"
}
},
eventDate: { $first: "$eventDate" },
games: {
$push: {
team1: "$team1",
team2: "$team2"
}
}
}
},
{ $sort: { eventDate: 1 } },
{
$facet: {
resultData: [{ $limit: 1 }],
pageInfo: [{ $count: "totalRecords" }]
}
}
])
Playground

Count Documents in mongoose in single query

const postsCount = await Post.find(query).countDocuments();
const allPosts = await Post.find(query)
.populate('likes')
.skip(startIndex)
.limit(noOfRecordsPerPage)
.sort({ createdAt: 'desc' });
I am querying two times in Database to find the total document count . How can i do this with a single query to the database ?
//you can use aggregation pipeline and $facet to combine two DB requests into one
//Actual Code output from windows Mongoshell/Command Line Interface(CLI)
> print("mongoDB",db.version())
mongoDB 4.2.6
> db.groups1.aggregate([
... {$facet:{
... "docCount":[
... {$group:{
... _id:null,
... count:{$sum:1}
... }
... }
... ],
... "allDocs":[
... {$match:{}}
... ]
... }
... }
... ]).pretty();
{
"docCount" : [
{
"_id" : null,
"count" : 4
}
],
"allDocs" : [
{
"_id" : ObjectId("5f817d142689512ef0bd3bd7"),
"uid" : 1,
"group_id" : "g1"
},
{
"_id" : ObjectId("5f817d142689512ef0bd3bd8"),
"uid" : 1,
"group_id" : "g2"
},
{
"_id" : ObjectId("5f817d142689512ef0bd3bd9"),
"uid" : 2,
"group_id" : "g2"
},
{
"_id" : ObjectId("5f817d142689512ef0bd3bd1"),
"uid" : 2,
"group_id" : "g3"
}
]
}
>
Use the $facet aggregation stage to combine multiple nested pipelines into your master pipeline.
Here's a working code of the above idea -
const countPipeline = [
{
$match: query
},
{
$group: {
_id: null,
count: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
count: '$count'
}
}
];
const postPipeline = [
{
$match: query
},
{
$skip: startIndex
},
{
$limit: noOfRecordsPerPage
},
{
$sort: {
createdAt: -1
}
}
];
const pipeline = [
{
$facet: {
posts: postPipeline,
count: countPipeline
}
}
];
const results = await Posts.aggreagate(pipeline).exec();

How do I update a field in embedded documents based on another array in MongoDB

I am trying to update an embedded document in MongoDB using mongoose in nodejs. The document is simplified and shown below (The names in friendList is assumed to be unique):
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList" : [
{
"name" : "Alex",
"flag" : false,
},
{
"name" : "Bob",
"flag" : false,
},
{
"name" : "Caleb",
"flag" : true,
},
{
"name" : "Debbie",
"flag" : false,
}
]
}
I would like to update this collection by:
accepting a Patch API with a request body containing a subset of friendList and
update the nested field flag.
For example, if I were to do a patch call from postman with the request body:
{
"friendList":[
{
"name":"Alex",
"flag":true
},
{
"name":"Caleb",
"flag":false
},
{
"name":"Debbie",
"flag":false
}
]
}
then I should expect my document in MongoDB to look like this:
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList":[
{
"name":"Alex",
"flag":true
},
{
"name":"Bob",
"flag":false
},
{
"name":"Caleb",
"flag":false
},
{
"name":"Debbie",
"flag":false
}
]
}
What I have tried on nodejs is updating the entire request body:
function updateUser(req){
User.findOneAndUpdate({'_id':req.params._id},req.body,{new:true});
}
which replaces the entire friendList array:
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList":[
{
"name":"Alex",
"flag":true
},
{
"name":"Caleb",
"flag":false
},
{
"name":"Debbie",
"flag":false
}
]
}
I have also tried using array operators like $:
function updateUser(req){
User.findOneAndUpdate(
{'_id':req.params._id},
{$addToSet:{
"friendList":{
$each:req.body.friendList}
}
},
{new:true}
);
}
which gave me the output:
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList" : [
{
"name" : "Alex",
"flag" : false,
},
{
"name" : "Bob",
"flag" : false,
},
{
"name" : "Caleb",
"flag" : true,
},
{
"name" : "Debbie",
"flag" : false,
},
{
"name" : "Alex",
"flag" : true,
},
{
"name" : "Caleb",
"flag" : false,
},
]
}
which $addToSet considers both name and flag when making a comparison to check if the values exist in the array. It might work if I am able to intercept at this comparison phase such that only the name field is checked.
I have been exploring concepts like $[<identifier>] and arrayFilter but can't seem to make it work.
Simple $addToSet does not work, because your array is not ["Alex","Caleb","Debbie"]. Your array is
[
{name: "Alex", flag: true},
{name: "Caleb", flag: false},
{name: "Debbie", flag: false}
]
Element {name:"Alex", flag: true} is different to element {name: "Alex", flag: false}, that's the reason why your approach failed. I think you have to use aggregation pipeline, e.g. this one:
db.collection.aggregate([
{ $addFields: { newFriends: friendList } },
{
$set: {
friendList: {
$map: {
input: "$friendList",
in: {
name: "$$this.name",
flag: {
$cond: [
{ $eq: [{ $indexOfArray: ["$newFriends.name", "$$this.name"] }, - 1] },
"$$this.flag",
{ $arrayElemAt: [ "$newFriends.flag", { $indexOfArray: ["$newFriends.name", "$$this.name"] } ] }
]
}
}
}
}
}
},
{ $unset: "newFriends" }
])
Or if you like to work with index variable:
db.collection.aggregate([
{ $addFields: { newFriends: friendList } },
{
$set: {
friendList: {
$map: {
input: "$friendList",
in: {
$let: {
vars: { idx: { $indexOfArray: ["$newFriends.name", "$$this.name"] } },
in: {
name: "$$this.name",
flag: {
$cond: [
{ $eq: ["$$idx", - 1] },
"$$this.flag",
{ $arrayElemAt: ["$newFriends.flag", "$$idx"] }
]
}
}
}
}
}
}
}
},
{ $unset: "newFriends" }
])
Note, this will update only existing names. New names are not added to the array, your question is not clear in this regard. If you like to add also new elements then insert
{
$set: {
friendList: { $setUnion: ["$friendList", "$newFriends"] }
}
},
just before { $unset: "newFriends" }
The aggregation pipeline can be used in an update:
User.findOneAndUpdate(
{'_id':req.params._id},
[
{ $addFields: { newFriends: req.body.friendList } },
{
$set: { ...
}
]
);

wrong counting with $filter

My db cofiguration looks like:
{
"_id" : ObjectId("5ece47aa6510a611b47aac5a"),
"boats" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac6e"),
"model" : "Dufour",
"year" : 2019,
"about" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 14,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 200
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 120
},
]
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac71"),
"Capacity" : 8,
"characteristics" : [
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "benzin",
"fuelCap" : 180
},
{
"_id" : ObjectId("5ece47aa6510a611b47aac73"),
"fuel" : "petrol",
"fuelCap" : 100
},
]
},
{...},
{...},
]
}
Now i am trying to count the number of boats which have "fuel" : "petrol", so i use the code bellow:
router.get('/boat', async(req, res)=>{
try{
const fuelData = await Boat.aggregate([
{
$project: {
fuelData: {
$filter: {
input: "$boats",
as: "boats",
cond: {
$filter:{
input:"$$boats.about",
as:"about",
cond:{
$filter:{
input:"$$about.characteristics",
as:"characteristics",
cond:{
$eq:["$$activity1.activity.type", "STILL"]
}
}
}
}
}
}
}
}
},
{
$project: {
boatsCount: {$size : "$fuelData" }
}
}
])
res.status(201).send(fuelData)
}catch(e){
res.send(e)
}
})
The problem is that return wrong number of boatCount. And it seems like it returns the number of the boats which are inside the db. Any help how to count correctly the boats which have "fuel" : "petrol"?
Is there anything wrong in my code?
https://mongoplayground.net/p/D0FEhTMJEJ1
Hope this is what you need.
The sample data you have provided is missing a ] & }.
So I added 1 additional boat with 2 about.
db.collection.aggregate([
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$unwind: "$boats"
},
{
$unwind: "$boats.about"
},
{
$match: {
"boats.about.characteristics.fuel": "petrol"
}
},
{
$group: {
_id: null,
count: {
$sum: 1
}
}
}
])

Resources