Mongo query with date condition and join of second collection - mongodb

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

Related

Mongodb Attendance Collction & Aggregation For Calculating Attendance

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.

How can I use MongoDB's aggregate with $lookup to replace an attribute that holds an id with the whole document?

I have two collections:
user:
{
"_id" : "9efb42e5-514d-44bd-a4b8-6f74e6313ec2",
"name" : "Haralt",
"age" : 21,
"bloodlineId" : "c59a2d02-f304-49a8-a52a-44018fc15fe6",
"villageId" : "foovillage"
}
bloodlines:
{
"_id" : "c59a2d02-f304-49a8-a52a-44018fc15fe6",
"name" : "Tevla",
"legacy" : 0
}
Now I'd like to do an aggregate to replace user.bloodlineId with the whole bloodline document.
This is what I tried to far:
db.getCollection('character').aggregate([
{
"$match": { _id: "9efb42e5-514d-44bd-a4b8-6f74e6313ec2" }
},
{
"$lookup": {
from: "bloodline",
localField: "bloodlineId",
foreignField: "_id",
as: "bloodline"
}
}])
The result is almost where I want it:
{
"_id" : "9efb42e5-514d-44bd-a4b8-6f74e6313ec2",
"name" : "Haralt",
"age" : 21,
"bloodlineId" : "c59a2d02-f304-49a8-a52a-44018fc15fe6",
"villageId" : "foovillage",
"bloodline" : [
{
"_id" : "c59a2d02-f304-49a8-a52a-44018fc15fe6",
"name" : "Tevla",
"legacy" : 0
}
]
}
Only two issues here. The first is that bloodlineId is still there and bloodline was just added to the result. I'd like to have bloodline replace the bloodlineId attribute.
The second problem is that bloodline is an array. I'd love to have it a single object.
I think this pipeline might do the trick:
[
{
"$match": {
_id: "9efb42e5-514d-44bd-a4b8-6f74e6313ec2"
}
},
{
"$lookup": {
from: "bloodlines",
localField: "bloodlineId",
foreignField: "_id",
as: "bloodline"
}
},
{
$project: {
"age": 1,
"bloodlineId": {
$arrayElemAt: [
"$bloodline",
0
]
},
"name": 1,
"villageId": 1
}
}
]
Mongo Playground
If there's anything I'm missing, please let me know!

compare string and objectId in lookup

I foundout that there is no option to compare these two .If there is any alternate please tell me.
As you see i have two collection event and eventuser i want list of user from eventuser on the basis
of event name in event collection.
But i have userId in string format then how to compare userId column
in event with _id column in eventuser collection.
Event collection
{
"_id" : ObjectId("5b5867500be60f139e67c908"),
"userId" : "5b58674e0be60f139e67cfea",
"name" : "Add to Cart",
},
{
"_id" : ObjectId("5b5867500be60f139e67c090"),
"userId" : "5b58674e0be60f139e67cfea",
"name" : "Searched",
},
{
"_id" : ObjectId("5b5867500be60f139e67c098"),
"userId" : "5b58674e0be60f139e67cacd",
"name" : "Add to Cart",
}
EventUser Collection
{
"_id":ObjectId("5b58674e0be60f139e67cfea"),
"name":"jogendra"
},
{
"_id":ObjectId("5b58674e0be60f139e67cfcv"),`
"name":"jogendra singh"
}
mmy query- it return users array as empty list
db.getCollection("event").aggregate([
{$match:{"name":"Add to Cart"}},
{$lookup:{
from:"eventuser",
localField:"userId",
foreignFiled:"_id",
as:"users"
}}
]);
In $lookup $lookup you can't match with string --> _id or _id --> string
Possible case is _id --> _id or string --> string
So you need change your data like this
{
"_id" : ObjectId("5b5867500be60f139e67c908"),
"userId" : ObjectId("5b58674e0be60f139e67cfea"),
"name" : "Add to Cart",
},
{
"_id" : ObjectId("5b5867500be60f139e67c090"),
"userId" : ObjectId("5b58674e0be60f139e67cfea"),
"name" : "Searched",
},
{
"_id" : ObjectId("5b5867500be60f139e67c098"),
"userId" : ObjectId("5b58674e0be60f139e67cacd"),
"name" : "Add to Cart",
}
otherwise, you need to upgrade your MongoDB version 4 and you can use $toObjectId $toObjectId
db.collection.aggregate([
{ $match: { "name": "Add to Cart" } },
{
$addFields: {
convertedId: { $toObjectId: "$userId" }
}
},
{
"$lookup": {
"from": "from_collection",
"localField": "convertedId",
"foreignField": "_id",
"as": "data"
}
}
]);

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
}
}])

Update multiple documents with aggregated values in MongoDB

Problem 1
I have a collection named recipe in which all docs have a array field ingredients. I want to count those array items and write them into a new field ingredient_count.
Problem 2
There is also a collection named ingredient. The docs have a count field which is the total number of uses in all recipes.
My Current Approach
My solution right now is a script that aggregates over the collection and updates all documents one by one:
// PROBLEM 1: update recipe documents
db.recipe.aggregate(
[
{
$project: {
numberOfIngredients: { $size: "$ingredients" }
}
}
]
).forEach(function(recipe) {
db.recipe.updateOne(
{ _id: recipe._id },
{ $set: { incredient_count: recipe.numberOfIngredients } }
)
});
// PROBLEM 2: update ingredient documents
db.ingredient.find().snapshot().forEach(function(ingredient) {
db.ingredient.updateOne(
{ _id: ingredient._id },
{ $set: { count: db.recipe.count({ ingredients: { $in: [ingredient.name] } })) } }
)
});
This is terribly slow. Any idea how to do this more efficiently?
For both problem it's possible to only perform aggregation that output to new collections that would replace existing one :
Problem1
The aggregation contains one $project for counting ingredients with the list of field to keep :
db.recipe.aggregate([{
$project: {
ingredients: 1,
numberOfIngredients: { $size: "$ingredients" }
}
}, {
$out: "recipeNew"
}])
that give you :
{ "_id" : ObjectId("58155bc09c924e717c5c4240"), "ingredients" : [......], "numberOfIngredients" : 5 }
{ "_id" : ObjectId("58155bc19c924e717c5c4241"), "ingredients" : [......], "numberOfIngredients" : 3 }
The result of the aggregation is written to a new collection recipeNew that can replace the existing recipe collection
Problem2
The aggregation contains :
1 $unwind to remove ingredients array
1 $group to sum occurence of each ingredients & group by ingredients _id
1 $lookup that join ingredients collection to the current aggregation to retrieve all fields for specified ingredients
1 $unwind to remove the array of imported ingredients items
1 $project to select fields to keep
1 $out to output the result to a new collection
Query is :
db.recipe.aggregate([{
$unwind: "$ingredients"
}, {
$group: { _id: "$ingredients", IngredientsNumber: { $sum: 1 } }
}, {
$lookup: {
from: "ingredients",
localField: "_id",
foreignField: "_id",
as: "ingredientsDB"
}
}, {
$unwind: { path: "$ingredientsDB", preserveNullAndEmptyArrays: true }
}, {
$project: {
ingredientsNumber: "$IngredientsNumber",
name: "$ingredientsDB.name"
}
}, {
$out: "ingredientsTemp"
}])
That gives :
{ "_id" : ObjectId("5812caaeb4829937f4599b54"), "ingredientsNumber" : 2, "name" : "ingredients5" }
{ "_id" : ObjectId("5812caaeb4829937f4599b53"), "ingredientsNumber" : 1, "name" : "ingredients4" }
{ "_id" : ObjectId("5812caaeb4829937f4599b52"), "ingredientsNumber" : 2, "name" : "ingredients3" }
{ "_id" : ObjectId("5812caaeb4829937f4599b51"), "ingredientsNumber" : 1, "name" : "ingredients2" }
{ "_id" : ObjectId("5812caaeb4829937f4599b50"), "ingredientsNumber" : 2, "name" : "ingredients1" }
The cons of this solution :
It uses $project so you need to specify the fields to keep
you will get a new ingredientsTemp collection containing only ingredients that are actually present in recipes so one additionnal aggregation with a $lookup should be necessary to join the existing one with the one you got from that aggregation :
The following will join the existing ingredients collection with the one we have created :
db.ingredients.aggregate([{
$lookup: {
from: "ingredientsTemp",
localField: "_id",
foreignField: "_id",
as: "ingredientsDB"
}
}, {
$unwind: { path: "$ingredientsDB", preserveNullAndEmptyArrays: true }
}, {
$project: {
name: "$name",
ingredientsNumber: "$ingredientsDB.ingredientsNumber"
}
}])
Then you would have :
{ "_id" : ObjectId("5812caaeb4829937f4599b50"), "name" : "ingredients1", "ingredientsNumber" : 2 }
{ "_id" : ObjectId("5812caaeb4829937f4599b51"), "name" : "ingredients2", "ingredientsNumber" : 1 }
{ "_id" : ObjectId("5812caaeb4829937f4599b52"), "name" : "ingredients3", "ingredientsNumber" : 2 }
{ "_id" : ObjectId("5812caaeb4829937f4599b53"), "name" : "ingredients4", "ingredientsNumber" : 1 }
{ "_id" : ObjectId("5812caaeb4829937f4599b54"), "name" : "ingredients5", "ingredientsNumber" : 2 }
{ "_id" : ObjectId("5812caaeb4829937f4599b57"), "name" : "ingredients6" }
The goods :
It uses only aggregation so it should be quicker