Mongodb Attendance Collction & Aggregation For Calculating Attendance - mongodb

I am trying to create an application for tracking employee attendances.
Using Front End : Angular , Server: Node JS(Express Framework),BackEnd : MongoDB
I succeed to record employee attendence in mongodb, considering employee first swipe is 'IN' and
second swipe 'OUT' for a day( also can have multiple IN & OUT in day,alternate IN & OUT)
Problem am facing is to generate reports on the basis of these attendence logs i stored in the collection.
I need to generate report for an employee work duration he/she is present or absent in a day or for a particular period.
Here is my collections samples.
employee collections
{
"_id" : ObjectId("5f120ba1578d051b20fb53e3"),
"emp_code" : 82140,
"emp_firstname" : "rahul",
"emp_secondname" : "narayanan unni",
"emp_gender" : "Male",
"dept_name" : "office",
"emp_designation" : "system administrator",
"avatar" : "src/assets/images/IMG-20180302-WA0025.jpg",
"createdAt" : ISODate("2020-07-17T20:35:45.546Z"),
"updatedAt" : ISODate("2020-07-19T20:09:43.164Z"),
"__v" : 0
}
{
"_id" : ObjectId("5f120c90578d051b20fb53e4"),
"emp_code" : 82141,
"emp_firstname" : "sanu",
"emp_secondname" : "prakashan",
"emp_gender" : "Female",
"dept_name" : "administraton",
"emp_designation" : "driver",
"avatar" : "src/assets/images/IMG-20180302-WA0014.jpg",
"createdAt" : ISODate("2020-07-17T20:39:44.933Z"),
"updatedAt" : ISODate("2020-07-19T20:33:02.610Z"),
"__v" : 0
}
attendancelogs collection
{
"_id" : ObjectId("5f6a18a2f16b782bfdd8c427"),
"punch_status" : true,
"owner" : ObjectId("5f120ba1578d051b20fb53e3"),
"chekedInTime" : ISODate("2020-09-22T15:30:42.547Z"),
"createdAt" : ISODate("2020-09-22T15:30:42.565Z"),
"updatedAt" : ISODate("2020-09-22T15:30:42.565Z"),
"__v" : 0
}
{
"_id" : ObjectId("5f6a190ef16b782bfdd8c429"),
"punch_status" : false,
"owner" : ObjectId("5f120ba1578d051b20fb53e3"),
"chekedOutTime" : ISODate("2020-09-22T15:32:30.277Z"),
"createdAt" : ISODate("2020-09-22T15:32:30.290Z"),
"updatedAt" : ISODate("2020-09-22T15:32:30.290Z"),
"__v" : 0
}
{
"_id" : ObjectId("5f6b5cb2a76ecf0f77729082"),
"punch_status" : true,
"owner" : ObjectId("5f120c90578d051b20fb53e4"),
"chekedInTime" : ISODate("2020-09-23T14:33:22.728Z"),
"createdAt" : ISODate("2020-09-23T14:33:22.741Z"),
"updatedAt" : ISODate("2020-09-23T14:33:22.741Z"),
"__v" : 0
}
{
"_id" : ObjectId("5f6b5cd8a76ecf0f77729086"),
"punch_status" : false,
"owner" : ObjectId("5f120c90578d051b20fb53e4"),
"chekedOutTime" : ISODate("2020-09-23T14:34:00.123Z"),
"createdAt" : ISODate("2020-09-23T14:34:00.132Z"),
"updatedAt" : ISODate("2020-09-23T14:34:00.132Z"),
"__v" : 0
}
The query i did is, (incompelte)
db.employeemodels.aggregate([
{
$lookup: {
from: "attlogsmodels",
localField: "_id",
foreignField: "owner",
as: "logs",
}
},
{
$project : {
_id:1,
emp_firstname:1,
emp_code:1,
emp_secondname:1,
dept_name:1,
avatar:1,
todayLogs: { $filter:{input:'$logs',as:"log",
cond:
{ $and: [
{ $gte: [ "$$log.createdAt",ISODate("2020-09-23T00:59:59.0Z")]},
{ $lte: [ "$$log.createdAt",ISODate("2020-09-23T24:59:59.0Z")]}
]}
}
}
}
},]).pretty()
Please suggest if any better way to collect data for effient report making if am wrong this approch
I tried different aggregation stages to obtain the result,but i failed
The query i did is (incomplete)
db.employeemodels.aggregate([
{
$lookup: {
from: "attlogsmodels",
localField: "_id",
foreignField: "owner",
as: "logs",
}
},
{
$project : {
_id:1,
emp_firstname:1,
emp_code:1,
emp_secondname:1,
dept_name:1,
avatar:1,
todayLogs: { $filter:{input:'$logs',as:"log",
cond:
{ $and: [
{ $gte: [ "$$log.createdAt",ISODate("2020-09-23T00:59:59.0Z")]},
{ $lte: [ "$$log.createdAt",ISODate("2020-09-23T24:59:59.0Z")]}
]}
}
}
}
},
]).pretty()
Required
Report 1 : EmpDayStatus
List all employees and their status in current day
Emp_Name | department | InTime | OutTime | Duration
Report 2 : Employee Report for a particular period and/or day
Emp_Name , Emp_Code, Department
How many Absences & Presents
Each Day Total Work Duration

You really need to change the structure of attendancelogs collection, because this will not produce accurate and exact report as per in and out logs, and also this is lengthy and heavy to query and processing,
Report 1: EmpDayStatus
$match to get current day logs of createdAt
$sort by createdAt ascending order
$group by user / owner id, store punch in records in pin and store punch out records in pout
db.attendancelogs.aggregate([
{
$match: {
createdAt: {
$gte: ISODate("2020-09-22T00:00:00.000Z"),
$lte: ISODate("2020-09-22T23:59:59.999Z")
}
}
},
{ $sort: { createdAt: 1 } },
{
$group: {
_id: "$owner",
pin: {
$push: { $cond: [{ $eq: ["$punch_status", true] }, "$$ROOT", "$$REMOVE"] }
},
pout: {
$push: { $cond: [{ $eq: ["$punch_status", false] }, "$$ROOT", "$$REMOVE"] }
}
}
},
$project to merge both arrays (pin, pout) element in one document object, using $mergeObjects, $map to iterate loop through 0 to size of pin elements using $range
{
$project: {
logs: {
$map: {
input: { $range: [0, { $size: "$pin" }] },
in: {
$mergeObjects: [
{ $arrayElemAt: ["$pin", "$$this"] },
{ $arrayElemAt: ["$pout", "$$this"] }
]
}
}
}
}
},
$lookup join employee collection with localfield _id and foreignfield _id
$unwind deconstruct user array
{
$lookup: {
from: "employee",
localField: "_id",
foreignField: "_id",
as: "user"
}
},
{ $unwind: "$user" },
$project to show employee name, logs to get required fields and duration of in and out date in minutes
{
$project: {
emp_firstname: "$user.emp_firstname",
logs: {
$map: {
input: "$logs",
in: {
chekedInTime: "$$this.chekedInTime",
chekedOutTime: "$$this.chekedOutTime",
duration: {
$divide: [
{
$subtract: [
"$$this.chekedOutTime",
"$$this.chekedInTime"
]
},
60000 // 1000*60 in minutes
]
}
}
}
}
}
}
])
Playground
Second report is totally lengthy it will take time, you can do it after taking reference of first report query.

Related

Do count with condition inside mongDB aggregation's function

I have 2 collections inside my MongoDB:
Order:
{
"date" : ISODate("2020-07-30T22:00:00.000Z"),
"item" : "banana",
"price": "4$",
"order_type" : "INTERNATIONAL", // There are 2 order types: INTERNATIONAL and MAINLAND
"user" : { // I use a dbref to the User collection
"$ref" : "user",
"$id" : "user_0"
}
}
User:
{
"_id": "user_0"
"login" : "user1",
"password" : "$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG",
"first_name" : "Henry",
"last_name" : "Windsor",
"email" : "hw#gmail.com",
}
Each order contains a DB reference to the corresponding user who made it. This is my mongo code to calculate the total number of orders that each user makes.
db.getCollection('order').aggregate([
{$group: {
_id: '$user',
totalNbOfOrders: {$sum: 1}
}},
{$addFields: {foreign_key: {$objectToArray: "$_id"}}},
{$lookup: {
from: 'user',
localField: 'foreign_key.1.v',
foreignField: '_id',
as: 'userInfo'
}
},
{ $unwind: '$userInfo'},
{ $project: {
'_id': 0,
'first_name': '$userInfo.first_name',
'last_name': '$userInfo.last_name',
'totalNbOfOrders': '$totalNbOfOrders'
}
}
])
And the result is:
/* 1 */
{
"first_name" : "John",
"last_name" : "Kennedy",
"totalNbOfOrders" : 2.0
}
/* 2 */
{
"first_name" : "Peter",
"last_name" : "Parker",
"totalNbOfOrders" : 4.0
}
/* 3 */
{
"first_name" : "Bruce",
"last_name" : "Banner",
"totalNbOfOrders" : 2.0
}
Now, what I also want to calculate is the number of international orders (and eventually of mainland orders) that each user made to have something like this:
{
"first_name" : "Tony",
"last_name" : "Stark",
"totalNbOfOrders" : 10.0,
"totalNbOfInternationalOrders": 4.0
"totalNbOfMainlandOrders": 6.0
}
I haven't figured out how to write the code.
I tried to use "$accumulator" operator (new feature in version 4.4 of MongoDB) inside "$group" but I used MongoDB 4.2.7, I have to use operators from older versions to accomplish this. Does anybody know how to solve this problem?
You can do it inside $group, using $cond and $eq,
{
$group: {
... // skipped
// INTERNATIONAL COUNT
totalNbOfInternationalOrders: {
$sum: {
$cond: {
if: {
$eq: ["$order_type", "INTERNATIONAL"]
},
then: 1,
else: 0
}
}
},
// MAINLAND COUNT
totalNbOfMainlandOrders: {
$sum: {
$cond: {
if: {
$eq: ["$order_type", "MAINLAND"]
},
then: 1,
else: 0
}
}
}
}
},
set in $project
{
$project: {
... // skipped
"totalNbOfInternationalOrders": "$totalNbOfInternationalOrders",
"totalNbOfMainlandOrders": "$totalNbOfMainlandOrders"
}
}
Playground: https://mongoplayground.net/p/_IeVcSFt_nY

Aggregate with lookup and contcat Strings in MongoDb

I have two collections events & members :
events Schema :
{
name : String,
members: [{status : Number, memberId : {type: Schema.Types.ObjectId, ref: 'members'}]
}
events Sample Doc :
"_id" : ObjectId("5e8b0bac041a913bc608d69d")
"members" : [
{
"status" : 4,
"_id" : ObjectId("5e8b0bac041a913bc608d69e"),
"memberId" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"date" : ISODate("2020-04-06T10:59:56.997Z")
},
{
"status" : 1,
"_id" : ObjectId("5e8b0bf2041a913bc608d6a3"),
"memberId" : ObjectId("5e7e2f048f80b46d786bfd67"),
"date" : ISODate("2020-04-06T11:01:06.463Z")
}
],
members Schema :
{
firstname : String
photo : String
}
members Sample Doc :
[{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"photo" : "/users/5e7dbf5b257e6b18a62f2da9/profile/profile-02b13aef6e.png"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"photo" : "/5e7e2f048f80b46d786bfd67/profile/profile-c79f91aa2e.png"
}]
I made a query with aggregate, and lookup to get populated data of members, and I want to concat the photo fields of the members by a string, but I get an error,
How can I do the concat ?
Query :
db.getCollection('events').aggregate([
{ $match: { _id: ObjectId("5e8b0bac041a913bc608d69d")}},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
"Members.firstname" : 1,
"Members.photo": 1,
//"Members.photo": {$concat:["http://myurl", "$Members.photo"]},
"Members._id" : 1,
},
}
])
Result without the concat :
{
"_id" : ObjectId("5e8b0bac041a913bc608d69d"),
"Members" : [
{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"photo" : "/users/5e7dbf5b257e6b18a62f2da9/profile/profile-02b13aef6e.png"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"photo" : "/5e7e2f048f80b46d786bfd67/profile/profile-c79f91aa2e.png"
}
]
}
Error :
$concat only supports strings, not array
You can do that simply by adding pipeline to $lookup stage
db.events.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d"),
},
},
{
$lookup: {
from: "members",
let: { memberId: "$members.memberId" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$memberId"] } } },
{
$project: {
firstname: 1,
photo: { $concat: ["http://myurl", "$photo"] }
}
}
],
as: "Members",
}
},
/** Optional */
{$project : {Members: 1}}
]);
Test : MongoDB-Playground
the alternative of using a pipeline in the above answer
we may use project and group
db.events.aggregate([
{
$match: { _id: ObjectId("5e8b0bac041a913bc608d69d") }
},
{
$unwind: '$members' // to spread the members array into a stream of documents
},
{
$lookup: {
from: "members",
localField: "members.memberId",
foreignField: "_id",
as: "member"
}
},
{
$unwind: '$member' // each document will have array of only one member, so do this unwind to convert it to an object
},
{
$project: { // do the project here to be able to use the $concat operator
'member._id': 1,
'member.firstname': 1,
'member.photo': 1,
'member.photo': { $concat: ['http://myurl', '$member.photo'] } // now we can use the $concat as member is an object, then member.photo exists
}
},
{
$group: { // do that grouping stage to gather all the members belong to the same document in one array again
_id: '$_id',
Members: {
$addToSet: '$member'
}
}
}
])

MongoDB How to Find out the records which are not in between the date range

I have two collections like below,
user Collection
{
"_id" : ObjectId("5af2e946aa546125b5de85cc"),
"name" : "Sudhin",
"email" : "abc#abc.com",
"roles" : [
"Reader",
"Instructor"
],
"createdAt" : ISODate("2018-05-09T12:27:50.651Z"),
"updatedAt" : ISODate("2018-05-16T09:22:07.280Z")
},
{
"_id" : ObjectId("5af2f3a6efb83031faaa3d82"),
"name" : "Rahul",
"email" : "abcd#abc.com",
"roles" : [
"Reader",
"Instructor"
]
"createdAt" : ISODate("2018-05-09T13:12:06.518Z"),
"updatedAt" : ISODate("2018-05-16T09:22:07.281Z")
}
schedulers Collections
{
"_id" : ObjectId("5afd763b8fad29597e1b85ed"),
"title" : "ILT Course",
"type" : "Course",
"ilt" : {
"instructorId" : ObjectId("5af2e946aa546125b5de85cc"),
"type" : "ILT-Offline",
"instructorName" : "Sudhin",
"place" : "*******",
"description" : "******"
},
"startDate" : ISODate("2018-05-10T11:00:00.000Z"),
"endDate" : ISODate("2018-05-15T12:00:00.000Z"),
"createdAt" : ISODate("2018-05-17T12:31:55.574Z"),
"updatedAt" : ISODate("2018-05-17T12:31:55.575Z")
}
In the scheduler collection "ilt.instructorId" is the referenceId for user.
Scheduler collection is having all the details of a particular user schedule.
startDate is the starting date and time of a particular schedule.
endDate is the ending date and time of a particular schedule.
When I pass startDate to endDate(2018-05-05 00:00 - 2018-05-10 00:00), I want to fetch all the users with role instructor and who do not have any scheduled courses in between those dates.
Eg: If I'm passing 2018-05-05 00:00 - 2018-05-10 00:00 it should return
the below document
{
"_id" : ObjectId("5af2f3a6efb83031faaa3d82"),
"name" : "Rahul",
"email" : "abcd#abc.com",
"roles" : [
"Reader",
"Instructor"
]
"createdAt" : ISODate("2018-05-09T13:12:06.518Z"),
"updatedAt" : ISODate("2018-05-16T09:22:07.281Z")
}
I have tried the below query
UserModel.query().aggregate([
{ $match: { roles: { $in: ['Instructor'] } } },
{
$lookup: {
from: "schedulers",
localField: "_id",
foreignField: "ilt.instructorId",
as: "schedule"
}
},
{
$match: {
"schedule.type": "Course",
$and: [
{ 'schedule.endDate': { $not: { $lte: new Date("2018-05-12T12:00:00.000Z") } }},
{ 'schedule.startDate': { $not: { $gte: new Date("2018-05-06T11:00:00.000Z") } }},
]
}
},
{
$project: {
_id: 1,
name: 1,
empId: 1,
startDateTime: "$schedule.startDate",
endDateTime: "$schedule.endDate",
}
}])
Please check below query its help you:
db.getCollection('user').aggregate([
{ $match: { roles: { $in: ['Instructor'] } } },
{
$lookup: {
from: "schedulers",
localField: "_id",
foreignField: "ilt.instructorId",
as: "schedule"
}
},
{
$match: {
"schedule.type": "Course",
$or: [
{ 'schedule.startDate': { $lt: new Date("2018-05-08T11:00:00.000Z") }},
{ 'schedule.endDate': { $gt: new Date("2018-05-14T12:00:00.000Z") }},
]
}
},
{
$unwind: {
path: '$schedule',
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
_id: 1,
name: 1,
empId: 1,
schedule:'$schedule',
startDateTime: "$schedule.startDate",
endDateTime: "$schedule.endDate",
}
}])

Triple relation lookup in MongoDB

I have tried to solve this one but its WAY over my Mongo skill level.
I hope there are some hardcore Mongo wizards who have an idea :-)
I would like to make a result where
db.getCollection('invoice').find({
dueDate: {
$gte:148000000,
$lt: 149000000
}
})
This is the "invoice" table....
invoice
{
"_id" : "KLKIU",
"invoiceNumber" : 1,
"bookingId" : "0J0DR",
"dueDate" : "148100000",
"account" : "aaaaaaaaaa",
"invoiceLines" : [
{
"lineText" : "Booking fee",
"amount" : 1000
},
{
"lineText" : "Discount",
"amount" : -200
},
{
"lineText" : "Whatever extra",
"amount" : 400
}
]
}
this is the result
{
"_id" : "KLKIU",
"invoiceNumber" : 1,
"bookingId" : "0J0DR",
"dueDate" : "148100000",
"account" : "aaaaaaaaaa",
"invoiceLines" : [
{
"lineText" : "Booking fee",
"amount" : 1000
},
{
"lineText" : "Discount",
"amount" : -200
},
{
"lineText" : "Whatever extra",
"amount" : 400
}
],
"propertyName" : "Atlantis Condo",
}
please notice the "propertyName" at the bottom
it needs to lookup and add
"propertyName" : "Atlantis Condo",
which will be done like this
db.getCollection('booking').find({
booking._id: invoice.bookingId
})
and then
db.getCollection('property').find({
property._id: booking:propertyId
})
These are the two tables:
Booking
{
"_id" : "0J0DR",
"propertyId" : "58669471869659d70b424ea7",
}
Property
{
"_id" : "58669471869659d70b424ea7",
"propertyName" : "Atlantis Condo",
}
Hope someone can figure this out - right now im doing some horrible sequential loops, and with big amounts of data thats really slow.
You can try below aggregation.
$lookup's to join to Booking and Property collection.
$unwind to flatten the booking array output from $lookup for joining on local field to Property collection.
$addFields to project the propertyName field.
$project to exclude the fields from referenced collection.
db.getCollection('invoice').aggregate([{
$match: {
"dueDate": {
$gte: 148000000,
$lt: 149000000
}
}
}, {
$lookup: {
from: "Booking",
localField: "bookingId",
foreignField: "_id",
as: "booking"
}
}, {
$unwind: "$booking"
}, {
$lookup: {
from: "Property",
localField: "booking.propertyId",
foreignField: "_id",
as: "property"
}
}, {
$unwind: "$property"
}, {
$addFields: {
"propertyName": "$property.propertyName"
}
}, {
$project: {
"booking": 0
}
}, {
$project: {
"property": 0
}
}])

Mongo query with date condition and join of second collection

So I have my "user" and "userprofile: collection and I need to find a way to get all users which do satisfy my criteria.
First condition is:
db.userprofile.find( { "country":{$ne:""}, "state":{$ne:""} }
).count()
From my user condition I however need to get all users with certain registration date like this:
db.user.find( {created_at: { $gte:
ISODate("2016-04-25T00:00:00.000Z"), $lt:
ISODate("2016-10-27T00:00:00.000Z") }, } ).count()
userprofile collection contains field "user_id" which is basically the same value like "id" auto generated field from user collection.
SO question is how can I join all users who match date criteria with all users who match userprofile criteria ?
I tried with sample similar to this one but without success:
db.user.aggregate([ {
$lookup:
{
from: "userprofile",
localField: "_id",
foreignField: "userid",
as: "user_profile"
} }, {
$unwind:"$user_profile" }, {
$match: { "user_profile":
{
"country":{$ne:""},
"state":{$ne:""} } } } ]).count()
Update:
db.user.findOne()
{
"_id" : ObjectId("561fac55c9368676ac000001"),
"username" : "somexxx3",
"firstname" : "Some",
"lastname" : "User",
"created_at" : ISODate("2015-10-15T13:38:29.954Z"),
"enabled" : false
}
db.userprofile.findOne()
db.userprofiles.findOne()
{
"_id" : ObjectId("56ec222017be1b7c763898a4"),
"user_id" : ObjectId("56a77f6c17be1b0a4393d8ea"),
"email" : "someuser#gmail.com",
"country" : "United Sates",
"state" : "New York"
}
If someone can help me with this, I would really appreciate it. Any hints are welcome.
Thanks.
this query should work :
db.user.aggregate([
{
$lookup:{
from:"userprofiles",
localField:"_id",
foreignField:"user_id",
as:"user_profile"
}
},
{
$unwind:"$user_profile"
},
{
$match:{
"user_profile.country":{
$ne:""
},
"user_profile.state":{
$ne:""
},
created_at:{
$gte: ISODate("2016-04-25T00:00:00.000Z"),
$lt: ISODate("2016-10-27T00:00:00.000Z")
}
}
},
{
$group:{
_id:null,
count:{
$sum:1
}
}
}
])
the "$group" stage is for counting results (you can't use db.col.aggregate().count()).
The output should look like
{ "_id" : null, "count" : 1 }
where "count" is the number of matching results