variable undefined after findOne operation mongodb - mongodb

I am trying to make an API that makes use of 2 databases to generate a fine. Here is the code:
router.get("/generateFine/:bookingID/:currDate", function (req, res, next) {
var currDate,
returnDate,
fine,
totalFine = 0;
Booking.findOne({ _id: req.params.bookingID }).then(function (booking) {
Car.findOne({ _id: booking.carID }).then(function (car) {
currDate = Date.parse(req.params.currDate) / 1000 / 3600 / 24;
returnDate = Date.parse(booking.bookingDates[1]) / 1000 / 3600 / 24;
fine = car.fine;
if (currDate > returnDate) {
totalFine = fine * (currDate - returnDate);
}
console.log(totalFine);
// res.send(totalFine);
});
console.log("totalFine is " + totalFine);
// res.send(totalFine);
});
});
Here are the two Schemas used in the code:
Booking Schema:
{
"_id" : ObjectId("621bf46602edf12942f0d5c9"),
"carID" : "621b87af70c150da70b1dabf",
"bookingDates" : [
"2022-03-05",
"2022-03-06"
],
}
Car Schema:
{
"_id" : ObjectId("621b87af70c150da70b1dabf"),
"name" : "Toyota",
"rate" : 60,
"fine" : 10,
"datesBooked" : [
{
"from" : "2022-03-05",
"to" : "2022-03-06"
},
{
"from" : "2022-03-07",
"to" : "2022-03-08"
},
{
"from" : "2022-03-09",
"to" : "2022-03-10"
}
],
"__v" : 0
}
I want to return the generated fine to the user. When I am trying to send the result, it throwing an error. The first console log prints the correct result, but the second console log prints 0. Also, how can I send the result without getting an error.
Thanks already!

You could use $lookup aggregation pipeline stage to include the car document that matches on the carID field, create additional computed fields that will aid you in getting the total fine whilst using the necessary aggregation operators.
Essentially you would need to run an aggregate pipeline that follows:
const mongoose = require('mongoose');
router.get('/generateFine/:bookingID/:currDate', async function (req, res, next) {
const currDate = new Date(req.params.currDate);
const [{ totalFine }] = await Booking.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(req.params.bookingID) }},
{ $lookup: {
from: 'cars', // or from: Car.collection.name
let: { carId: { $toObjectId: '$carID' } }, // convert the carID string field to ObjectId for the match to work correctly
pipeline: [
{ $match: {
$expr: { $eq: [ '$_id', '$$carId' ] }
} }
],
as: 'car'
} },
{ $addFields: {
car: { $arrayElemAt: ['$car', 0 ] }, // get the car document from the array returned above
returnDate: {
$toDate: { $arrayElemAt: ['$bookingDates', 1 ]}
}
} },
// compute the overdue days
{ $addFields: {
overdueDays: {
$trunc: {
$ceil: {
$abs: {
$sum: {
$divide: [
{ $subtract: [currDate, '$returnDate'] },
60 * 1000 * 60 * 24
]
}
}
}
}
}
} },
{ $project: { // project a new field
totalFine: {
$cond: [
{ $gt: [currDate, '$returnDate'] }, // IF current date is greater than return date
{ $multiply: ['$car.fine', '$overdueDays'] }, // THEN multiply car fine with the overdue days
0 // ELSE total fine is 0
]
}
} }
]).exec();
console.log("totalFine is " + totalFine);
// res.send(totalFine);
});

Related

MongoDB: How to speed up my data reorganisation query/operation?

I'm trying to analyse some data and I thought my queries would be faster ultimately by storing a relationship between my collections instead. So I wrote something to do the data normalisation, which is as follows:
var count = 0;
db.Interest.find({'PersonID':{$exists: false}, 'Data.DateOfBirth': {$ne: null}})
.toArray()
.forEach(function (x) {
if (null != x.Data.DateOfBirth) {
var peep = { 'Name': x.Data.Name, 'BirthMonth' :x.Data.DateOfBirth.Month, 'BirthYear' :x.Data.DateOfBirth.Year};
var person = db.People.findOne(peep);
if (null == person) {
peep._id = db.People.insertOne(peep).insertedId;
//print(peep._id);
}
db.Interest.updateOne({ '_id': x._id }, {$set: { 'PersonID':peep._id }})
++count;
if ((count % 1000) == 0) {
print(count + ' updated');
}
}
})
This script is just passed to mongo.exe.
Basically, I attempt to find an existing person, if they don't exist create them. In either case, link the originating record with the individual person.
However this is very slow! There's about 10 million documents and at the current rate it will take about 5 days to complete.
Can I speed this up simply? I know I can multithread it to cut it down, but have I missed something?
In order to insert new persons into People collection, use this one:
db.Interest.aggregate([
{
$project: {
Name: "$Data.Name",
BirthMonth: "$Data.DateOfBirth.Month",
BirthYear: "$Data.DateOfBirth.Year",
_id: 0
}
},
{
$merge: {
into: "People",
// requires an unique index on {Name: 1, BirthMonth: 1, BirthYear: 1}
on: ["Name", "BirthMonth", "BirthYear"]
}
}
])
For updating PersonID in Interest collection use this pipeline:
db.Interest.aggregate([
{
$lookup: {
from: "People",
let: {
name: "$Data.Name",
month: "$Data.DateOfBirth.Month",
year: "$Data.DateOfBirth.Year"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$Name", "$$name"] },
{ $eq: ["$BirthMonth", "$$month"] },
{ $eq: ["$BirthYear", "$$year"] }
]
}
}
},
{ $project: { _id: 1 } }
],
as: "interests"
}
},
{
$set: {
PersonID: { $first: "$interests._id" },
interests: "$$REMOVE"
}
},
{ $merge: { into: "Interest" } }
])
Mongo Playground

Mongodb aggregation pass argument to element size of $sample

Hello every body here any one can help me with query below
I want to get quiz list with random amount
the amount of rendom will
base on each lesson
The problem is
mongodb not allow to pass argument to element size of $sample
Any one can give me the solution
lessonModel.aggregate([
{ $match : {'quiz.status':1 } },
{
$lookup : {
from : 'quiz',
let : { 'lesson_id' : '$_id','limit' : '$quiz.amount' },
pipeline : [
{
$match: {
$expr: {
$eq: [ "$lesson_id", "$$lesson_id" ]
}
}
},
{
$project: {
title:1,
check_list:1,
duration:1
}
},
{ $sample: { size: '$$limit' } }
],
as: 'quiz'
}
},
{$unwind: '$quiz'},
{ $replaceRoot: { newRoot: "$quiz" } }
]).exec();
The error said size argument to $sample must be a number
Here is my sample data
UPDATE
I think your main problem is to randomly pick amount number of quizs under each lesson. Since $sample is not helpful use $function (New in version MongoDB 4.4).
Solution
Inside $function operator write some logic to
Shuffle the questions (You can change it to your requirement).
Slice it to return the number(amount) of questions required.
db.lessons.aggregate([
{ $match: { "quiz.status": 1 } },
{
$lookup: {
from: "quiz",
let: { "lesson_id": "$_id" },
pipeline: [
{
$match: {
$expr: { $eq: ["$lesson_id", "$$lesson_id"] }
}
},
{
$project: {
title: 1,
check_list: 1,
duration: 1
}
}
],
as: "questions"
}
},
{
$project: {
quiz: {
$function: {
body: function(questions, amount) {
if (amount == 0) return [];
for (let i = questions.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[questions[i], questions[j]] = [questions[j], questions[i]];
}
return questions.slice(0, amount);
},
args: ["$questions", { $ifNull: ["$quiz.amount", 0] }],
lang: "js"
}
}
}
},
{ $unwind: "$quiz" },
{ $replaceRoot: { newRoot: "$quiz" } }
]);
$sample does not support variables. A number must be specified explicitly like:
{
$sample: { size: 1 }
}
Also replace your let as shown below because last lesson document has no amount filed in the quiz object
let: {
'lesson_id': '$_id',
'limit': { $ifNull: ['$quiz.amount', 0] } // or any other number.
},
Wrong:
{
$sample: { size: "$$limit" } // Wont work!
}

How to use '$let' in MongoDB Aggregation Query in Scala?

I am trying to write a mongoDB aggregation query in Scala.
How do I write Scala code to use "$let" in '$project' stage?
I am wondering if Variable should be used. Not sure how?
'$project': {
'myprojitem' :{
'$let': {
'vars' : { 'myVariable1': { '$or': [...] } }
'in' : {
'$cond': [
'$$myVariable1',
{ ... },
{ ... },
]
}
}
I figured out the answer. Hopefully it helps someone.
val doc : Document = Document("{
'$let': {
'vars' : { 'myVariable1': { '$or': [...] } },
'in' : { '$cond': ['$$myVariable1',{ ... },{ ... } ]
}
}")
var pipeline = mutable.Buffer[Bson]()
pipeline += Aggregates.project(Projections.fields(
Projections.computed("myprojitem",doc)
))
Basically, every { name : expression } can be written as :
Document("name" -> expression)
Or
Document( "{name : expression}")
$let is used to bind variables together to a results obj. The syntax follows the rule:
{
$let:
{
vars: { <var1>: <expression>},
in: <expression>
}
}
for mere details you should take a look at $let (aggregation) definition from mongodb manual
Here is a text book example just to make more sense:
Consider the following data:
{ _id: 1, price: 10, tax: 0.50, applyDiscount: true }
{ _id: 2, price: 10, tax: 0.25, applyDiscount: false }
And imagine that we want to generate a result for the finalTotal in a way that:
Where Disc = 10% if applyDiscount: true and 0 otherwise.
So we need now to create the aggregation on the data to construct this equation. So we can get a results like:
{ _id: 1, finalTotal: 9.45 }
{ _id: 2, finalTotal: 10.25 }
We can do this by doing:
$project: {
finalTotal: {
$let: {
vars: {
total: { $add: [ '$price', '$tax' ] },
discounted: { $cond: { if: '$applyDiscount', then: (0.9, else: 1 } }
},
in: { $multiply: [ "$$total", "$$discounted" ] }
}
}
}
We can break this down:
Step 1. adding price to tax together to a variable called total
total: { $add: [ '$price', '$tax' ] },
Step 2. transforming the condition in numbers (variable discounted)
discounted: { $cond: { if: '$applyDiscount', then: 0.9, else: 1 } }
Step 3. performing the operation $multiply operation between the constructed $$total and $$discounted
in: { $multiply: [ "$$total", "$$discounted" ] }

Mongo db Group by, count with a condition

Using Mongodb, I want to get the count of sensor values above 100 and sensorvalues below 100 for each particular region(group by region).
I have a sensorValue property and it has 4 sub properties namely.
1)sensorValue (the values will be 100, 200 122, 80 etc) - I want to know the count of above 100 and below 100 per region.
2)Latitude
3)Longitude
4)Region (The name of the region) - I want the count with respect to this region.
With the help of stackoverflow, I wrote the below query.
getProximityIntervalRate = (req, res) => {
console.log("entered1")
this.model = ProximityLocation;
const startDate = req.headers.startdate, endDate = req.headers.enddate;
console.log(req.headers, startDate, endDate);
// TODO: server validatoin
this.model.aggregate([
{ $match: { 'observationTimestamp': { $gte: new Date(startDate), $lte: new Date(endDate) } } },
{
$project: {
regoin: 1,
lessthan: {
$cond: [{ $lt: ["$sensorValue.sensorValue", 5] }, 1, 0]
},
morethan: {
$cond: [{ $gt: ["$sensorValue.sensorValue", 5] }, 1, 0]
}
}
},
{
$group: { _id: { regoin: "$sensorValue.regoin" },
countSmaller: { $sum: "$lessThan" },
countBigger: { $sum: "$moreThan" } uh
}
},
], (err, location) => {
console.log('location', location);
if (!location) { return res.sendStatus(404); }
res.status(200).json(location);
});
}
I am not sure how to address the subproperty "sensorValue.regoin" under the "$project" option.Please let me know if I am missing something.
You can try below aggregation to get the result
db.t66.aggregate([
{$group: {
_id : "$sensorValue.region",
lessThan : {$sum : {$cond: [{$lt : [{$toInt : "$sensorValue.sensorValue"}, 50]}, 1,0]}},
greaterThan : {$sum : {$cond: [{$gte : [{$toInt : "$sensorValue.sensorValue"}, 50]}, 1,0]}},
}}
])
you can remove $toInt if the sensorValue is int datatype

MongoDB Update array element (document with a key) if exists, else push

I have such a schema:
doc:
{
//Some fields
visits:
[
{
userID: Int32
time: Int64
}
]
}
I want to first check if a specific userID exists, if not, push a document with that userID and system time, else just update time value. I know neither $push nor $addToSet are not able to do that. Also using $ with upsert:true doesn't work, because of official documentation advice which says DB will use $ as field name instead of operator when trying to upsert.
Please guide me about this. Thanks
You can use $addToSet to add an item to the array and $set to update an existing item in this array.
The following will add a new item to the array if the userID is not found in the array :
db.doc.update({
visits: {
"$not": {
"$elemMatch": {
"userID": 4
}
}
}
}, {
$addToSet: {
visits: {
"userID": 4,
"time": 1482607614
}
}
}, { multi: true });
The following will update the subdocument array item if it matches the userId :
db.doc.update({ "visits.userID": 2 }, {
$set: {
"visits.$.time": 1482607614
}
}, { multi: true });
const p = await Transaction.findOneAndUpdate(
{
_id: data.id,
'products.id': { $nin: [product.id] },
},
{
$inc: {
actualCost: product.mrp,
},
$push: {
products: { id: product.id },
},
},
{ new: true }
);
or
db.collection.aggregate([
{
"$match": {
"_id": 1
}
},
{
"$match": {
"sizes.id": {
"$nin": [
7
]
}
}
},
{
"$set": {
"price": 20
}
}
])
https://mongoplayground.net/p/BguFa6E9Tra
I know it's very late. But it may help others. Starting from mongo4.4, we can use $function to use a custom function to implement our own logic. Also, we can use the bulk operation to achieve this output.
Assuming the existing data is as below
{
"_id" : ObjectId("62de4e31daa9b8acd56656ba"),
"entrance" : "Entrance1",
"visits" : [
{
"userId" : 1,
"time" : 1658736074
},
{
"userId" : 2,
"time" : 1658736671
}
]
}
Solution 1: using custom function
db.visitors.updateMany(
{_id: ObjectId('62de4e31daa9b8acd56656ba')},
[
{
$set: {
visits: {
$function: {
lang: "js",
args: ["$visits"],
body: function(visits) {
let v = []
let input = {userId: 3, time: Math.floor(Date.now() / 1000)};
if(Array.isArray(visits)) {
v = visits.filter(x => x.userId != input.userId)
}
v.push(input)
return v;
}
}
}
}
}
]
)
In NodeJS, the function body should be enclosed with ` character
...
lang: 'js',
args: ["$visits"],
body: `function(visits) {
let v = []
let input = {userId: 3, time: Math.floor(Date.now() / 1000)};
if(Array.isArray(visits)) {
v = visits.filter(x => x.userId != input.userId)
}
v.push(input)
return v;
}`
...
Solution 2: Using bulk operation:
Please note that the time here will be in the ISODate
var bulkOp = db.visitors.initializeOrderedBulkOp()
bulkOp.find({ _id: ObjectId('62de4e31daa9b8acd56656ba') }).updateOne({$pull: { visits: { userId: 2 }} });
bulkOp.find({ _id: ObjectId('62de4e31daa9b8acd56656ba') }).updateOne({$push: {visits: {userId: 2, time: new Date()}}})
bulkOp.execute()
Reference link