Check if date range conflicts with current documents MongoDB - node.js

This problem has a layer of complexity and I'm not sure best practices to work around.
Attempting to create a booking calendar that serves the next availability date for their selected range. We have had cancellations that open up gaps.
Current functionality just takes the furthest end date and serves the next day, this has become very inaccurate and is leaving gaps due to cancellations.
Ex. User is looking to book for 10 days at the nearest available date. In the database there is a gap between 2 documents that would suit this user, how do I query to verify this availability?
Database document example
{
"start_date" : "2020-02-06T19:58:25.430Z",
"end_date" : "2020-02-16T19:58:25.430Z"
},
{
"start_date" : "2020-02-17T19:58:25.430Z",
"end_date" : "2020-02-27T19:58:25.430Z"
},
{
"start_date" : "2020-03-21T19:58:25.430Z",
"end_date" : "2020-03-31T19:58:25.430Z"
},
In between the second and third document there is a 2 week period that the user could claim. Currently this user would receive 2020-04-01 as the next availability. I would like to give them the option to claim a cancellation date by sending 2020-02-28 as the next availability.
How do I query mongodb to see if there is an x day amount availability in between document ranges before defaulting to the furthest end_date?

One idea: create a separate cancelled collection, and move any cancelled appointments into this collection.
If a new appointment is to be made, search this cancelled collection first for any open spots due to cancellations. If nothing suitable is found, then proceed to the furthest end date as per your current method.
An illustration:
> db.appointments.find()
{ "_id" : 0, "start" : 1, "end" : 2, "duration" : 1, "name": "John" }
{ "_id" : 1, "start" : 2, "end" : 4, "duration" : 2, "name": "Jane" }
{ "_id" : 2, "start" : 4, "end" : 5, "duration" : 1, "name": "Jack" }
// Jane cancelled the appt with _id 1
> c = db.appointments.findOne({_id: 1})
> db.cancelled.insert(c)
> db.appointments.deleteOne({_id: 1})
// current state of appointments
> db.appointments.find()
{ "_id" : 0, "start" : 1, "end" : 2, "duration" : 1, "name": "John" }
{ "_id" : 2, "start" : 4, "end" : 5, "duration" : 1, "name": "Jack" }
// Bob wanted to make an appointment spanning two "time units". Is it available?
> db.cancelled.findOne({duration: 2})
{ "_id" : 1, "start" : 2, "end" : 4, "duration" : 2, "name" : "Jane" }
// If Bob agreed to take over Jane's time slot
// reinsert the document from "cancelled" to "appointments", changing the name
> a = db.cancelled.findOne({duration: 2})
> a.name = "Bob"
> db.appointments.insert(a)
> db.cancelled.deleteOne({_id:1})
// end state of appointments
> db.appointments.find().sort({start: 1})
{ "_id" : 0, "start" : 1, "end" : 2, "duration" : 1, "name": "John" }
{ "_id" : 1, "start" : 2, "end" : 4, "duration" : 2, "name": "Bob" }
{ "_id" : 2, "start" : 4, "end" : 5, "duration" : 1, "name": "Jack" }
Note in this illustration, I also recorded the duration of each appointment so they can be searched efficiently. The end state is _id: 1 from Jane was replaced by Bob, with nothing in the cancelled collection.
To make sure that both appointments and cancelled collections are both updated correctly, you might want to use Transactions that's available since MongoDB 4.0.

I figured out a solution after letting it ferment in my brain for a week. I couldn't get the wanted results out of querying mongodb. I had to use javascript to compare some of the documents. Not sure about how the run time will hold up, for me that's a problem for a different day.
Thought I would add my solution as I came across my own question in google searches for the last week.
//Find all the documents that have an end date greater than today, sort them based // on the start date to order documents
let sort = await db.collection(COLLECTION)
.find({end_date : { $gte:new Date() }
})
.sort({start_date: 1 })
.toArray();
//Set the nextDate variable
let nextDate;
if(sort.length >= 2){
//Javascript iteration if there are more than 2 documents returned from sorting
for (let i = 0; i < sort.length; i++) {
var startDate = Date.parse(sort[i].end_date);
//endDate is ternary incase the amount of documents is odd number
var endDate = sort[i + 1] ? Date.parse(sort[i + 1].start_date) : new Date()
var timeDiff = endDate - startDate;
var daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24))
//Get the day difference between the date if the difference is greater than the //days alloted by user return the end date
if(daysDiff > days) {
return nextDate = sort[i].end_date
}
}
//If no days have room for the decided days, return the last document in array
return nextDate = sort[sort.length - 1].end_date
//If only one document, return end date
} else if (sort.length === 1) {
nextDate = sort[0].endDate
//No documents found, return todays date as it is available.
} else {
nextDate = new Date()
}
This actually works perfect for what I need it to do. Hopefully will get a good refactor and can be simplified.

Related

How to apply query on time in mongodb Or Time difference in mongodb

my database is like as follows:
{
"riderId" : ObjectId("59fac6f760c247118c16539f"),
"pickupDateTime" : ISODate("2017-11-19T07:51:57.491Z"),
"fare" : 15,
"quotes" : [],
"status" : "OPEN",
"closed" : false,
"closeBy" : null
}
if i want to fetch data of given pickupDateTime to increase half an hour pickupDateTime n here is my query:
db.getCollection('auctions').find( {
pickupDateTime:{
$gte: new Date(givenTime),
$lt: new Date(intervalPickupByTime)
}
})
but it doesn't work it fetch no records and in node i calculate the half hour difference by using moment as follows:
const intervalPickupByTime =
moment(filter.pickupDateTime).add(30,
'minutes');
I didn't understand why it not works

mongodb find where field lower than another field plus parameter value

I have the following collection in mongodb, lets call it "products":
[
{
"name" : "Product X",
"warehouseStock" : 50,
"reservedStock" : 41
},
{
"name" : "Product Y",
"warehouseStock" : 50,
"reservedStock" : 10
}
]
I want to have a find() query that returns the documents in this collection where 'warehouseStock' is less than ('reservedStock' + threshold), where threshold is a parameter passed into the find() query.
I am trying to do the following find() query in node:
var threshold = 10;
mongo.getDb().collection('products').find({warehouseStock: {$lt:(threshold+'$reservedStock')}})
but this does not seem to work. I know I can do the following:
mongo.getDb().collection('products').find({warehouseStock: {$lt:threshold}})
but how can I query where
warehouseStock < (reservedStock + threshold)
So, if threshold was to be =10, then I would only get the first of the two items in the collection example above.
Thank you!
You can use $where :
var threshold = 10;
mongo.getDb().collection('products').find({
"$where": "this.warehouseStock < (this.reservedStock + " + threshold + ")"
}).toArray(function(err, items) {
console.log(items);
});

Sort descending String with Number in Mongo DB

I have currently a DB with two fields. Only one of them matter for the purpose of this question.
Imagine a DB with a single String field (let's call it "Tags"), and the following pattern: [a-z]*[0-9]*, like:
test129
test130
some43
some44
some45
...
My application needs to generate new "Tags", given the prepend "identifier" (like test or some).
So let's say I input test as the prepend name, and 100 as the number of "Tags" to generate.
He finds the LAST tag with the prepended name test on it.
Parses the number after the prepended name.
Sum +1 on that number, and generate 100 tags with the sequence.
Output in this specific case, would be: test131, test132, ..., test230.
I implemented this, and it was working just great with Mongoose. However, when I tried to generate a "Tag" from a already existent Tag with more than 1000, I found that the first step was a flaw. It was returning, let's say test999 instead of test1200, and causing the iteration to start from 999, and getting errors since it needs to be unique.
This is because, sorting a String differs from sorting a Number. I know the problem, but how can I solve this in a simple way, without having to create extra fields?
UPDATE: Part of the code where I find the tag:
lastAliasNumber: function (next){
console.log('process.lastAliasNumber');
// Skip if prefix is not set (tags already have name)
if(!prefix) return next();
// Build RegExp to find tags with the prefix given
var regexp = new RegExp('^'+prefix+'[0-9]+$', 'i');
Models.Tag
.findOne()
.where({
alias: regexp
})
.sort('-alias')
.exec(function (err, tag){
if(err) return next(err);
// Remove prefix and try parsing number
var lastId = 100;
if(tag){
// Remove prefix
var number = tag.alias.toLowerCase().replace(prefix, '');
// Get number from it
number = parseInt(number);
if(number) lastId = number;
}
console.log('lastAliasNumber', lastId);
next(null, lastId);
});
},
There is no ready way to do this kind of sorting within MongoDB. As your field is a string field, it will be sorted by the rules of string sorting and there is no way to do variable type sorting on one field.
Your best bet (assuming you cannot simply use an integer type and wish to keep only one field) would be to work out the theoretical maximum number of entries and pad your strings with the relevant leading number of 0's accordingly.
EG. assuming a maximum of 1,000,000 entries your strings would be:
test000999
test001200
test000131
Another option would be to have these entries become whole subdocuments with two distinct datatypes.
Consider my quick example documents below
> db.bar.insert({x:{text:"test",num:1}})
WriteResult({ "nInserted" : 1 })
> db.bar.insert({x:{text:"test",num:100}})
WriteResult({ "nInserted" : 1 })
> db.bar.insert({x:{text:"test",num:2}})
WriteResult({ "nInserted" : 1 })
> db.bar.insert({x:{text:"sweet",num:2}})
WriteResult({ "nInserted" : 1 })
> db.bar.insert({x:{text:"sweet",num:1}})
WriteResult({ "nInserted" : 1 })
> db.bar.find().sort({x:1})
{ "_id" : ObjectId("55fa469d695632545d3aff1f"), "x" : { "text" : "sweet", "num" : 1 } }
{ "_id" : ObjectId("55fa469b695632545d3aff1e"), "x" : { "text" : "sweet", "num" : 2 } }
{ "_id" : ObjectId("55fa468a695632545d3aff1b"), "x" : { "text" : "test", "num" : 1 } }
{ "_id" : ObjectId("55fa4695695632545d3aff1d"), "x" : { "text" : "test", "num" : 2 } }
{ "_id" : ObjectId("55fa468f695632545d3aff1c"), "x" : { "text" : "test", "num" : 100 } }
> db.bar.find().sort({x:-1})
{ "_id" : ObjectId("55fa468f695632545d3aff1c"), "x" : { "text" : "test", "num" : 100 } }
{ "_id" : ObjectId("55fa4695695632545d3aff1d"), "x" : { "text" : "test", "num" : 2 } }
{ "_id" : ObjectId("55fa468a695632545d3aff1b"), "x" : { "text" : "test", "num" : 1 } }
{ "_id" : ObjectId("55fa469b695632545d3aff1e"), "x" : { "text" : "sweet", "num" : 2 } }
{ "_id" : ObjectId("55fa469d695632545d3aff1f"), "x" : { "text" : "sweet", "num" : 1 } }

Complex search and extendable query on two and more collections

I am working on Node.js + mongodb application and my question related to design of my database and queries. Firstly, describe my db structure. I have in my database two collections users and values. Document in users collection looks like this:
{
"_id" : 31450861,
"first_name" : "Jon",
"last_name" : "Doe",
"sex" : 2,
"bdate" : ISODate("1981-08-01T21:00:00Z"),
"city" : {
"id" : 282,
"title" : "Minsk"
},
"country" : {
"id" : 3,
"title" : "Belarussia"
},
"photo_max" : "https://foto.ru/RnhOKp2YJE4.jpg",
"relation" : 4,
"interests" : "science",
"music" : "pop",
"lang" : "RU",
}
This collection filled with users data (language, birthday, etc).
Documents from collection values looks like this:
{
"_id" : ObjectId("548023e5c16f310c23b75075"),
"userId" : "31450861", //reference to _id in users collection
"group1" : {
"value1" : 13,
" value2" : 7,
" value3" : 3,
" value4" : 14,
" value5" : 17
},
"group2" : {
" value1" : 9,
" value2" : 6,
" value3" : 17,
" value4" : 12,
" value5" : 13
}
So I need to make search for users on this database with lots of parameters from this two collection (their values (from collection values), sex, city, language etc.). I didn’t embed document values into users because I do a lot queries separately on them (but may be it’s anyway wrong design I need help on this). In future there will be more collections with similar structure like values (or at least there will be reference to userId), which I’ll have to include in search query, and I’ll need agility to extend my query on more collections.
So I need to run complex query on this collections (I know there is no JOINs in mongodb, so I know I have to query twice or use mapreduce).
So far my thoughts on this issue (aren’t tested in code just thoughts).
I need to write search function which performs 2 queries (and more in future):
Find users with same values and getting their ids.
var values = db.collection('values');
var ids = values.find({ value1: 1, value2: 2, value3: 3 }, {userId: 1 } ) //then transform ids so it became array with userId
Then in this found set find users on some more parameters (sex, birthday, language, etc)
var users = db.collection('users');
users.find({ $and: [{ _id: { $in: ids } }, {sex: 2 }, {lang: “RU” } ] });
My questions are:
1. Is it normal approach or I’ll end up with very slow performance and mess in code when adding new collections and queries?
2. If is it normal, how to easily add one more query to one more collection?
Any help, any thoughts are welcome! Thanks in advance!

MongoDB: Find items where two specified key values are equal

I have a mongoDB collection and an item in the collection looks like below:
{
"_id": "52f535b56268a019bd11cc2a",
"description": "Some description",
"entry_date": "2014-02-07T19:36:21.430Z",
"last_update": "2014-02-07T19:36:21.430Z",
"r": "samestring",
"s": "samestring"
}
Dates are ISODate objects.
This query returns items correctly
db.myCollection.find({$where : "this.entry_date < this.last_update"});
Below query returns nothing (I expect that it returns the above item):
db.myCollection.find({$where : "this.entry_date == this.last_update"});
And this query returns all items (I expected again it returns the above item):
db.myCollection.find({$where :"this.r == this.s"});
What am I doing wrong?
Thanks!!
---EDIT----
So I tried to test with a small data like below:
> db.myCollection.find({},{ _id: 0, start_date: 1, end_date: 1});
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T19:36:21.430Z")
}
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T22:39:02.114Z")
}
It didn't work for Date as you can see:
> db.myCollection.find(
{$where: "Date(this.start_date) == Date(this.end_date)"},
{ _id: 0, start_date: 1, end_date: 1 }
);
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T19:36:21.430Z")
}
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T22:39:02.114Z")
}
Works for string values:
> db.myCollection.find({$where: "this.title == this.description"},{ _id: 0, title: 1 });
{ "title" : "samedescription" }
You have to be really careful when comparing dates in JavaScript - use valueOf() or getTime():
> db.myCollection.find({$where: "this.start_date.getTime() == this.end_date.getTime()"});
{ "_id" : ObjectId("52f5b7e316d795f0a076fbdf"), "description" : "a description", "title" : "a title", "start_date" : ISODate("2014-02-07T19:36:21.430Z"), "end_date" : ISODate("2014-02-07T19:36:21.430Z") }
Here is why your other queries didn't work.
db.myCollection.find({$where: "Date(this.start_date) == Date(this.end_date)"});
This didn't work because you didn't use new when initializing the dates. This generally has hilarious results with all dates being equal to each other:
> Date(2014,2,8) == Date(1941,12,7)
true
> Date(2000,1,1) == Date(1995,2,8)
true
But even if you properly instantiate the date using new, you still get hilarious results when comparing dates using ==, as demonstrated in this gist:
var dateValue = 504001800000; // Saturday, December 21st, 1985 at 3:30am EST
var date1 = new Date(dateValue);
var date2 = new Date(dateValue);
console.log(date1 == date2); // false (different instances)
console.log(date1 === date2); // false (different instances)
console.log(date1 > date2); // false (date1 is not later than date2)
console.log(date1 < date2); // false (date1 is not earlier than date2)
console.log(date1 >= date2); // true (rofl)
console.log(date1 <= date2); // true (ahahahaha)
As for your other query:
It didn't work if I consider them as strings either:
db.myCollection.find({$where: "this.start_date == this.end_date"});
You're not actually comparing them as strings, you're comparing ISODate objects, which is how they're stored. For ISODate, similar to Date, the == operator will return false unless you're comparing the exact same instance. Using getTime should work, however, which is what I did up above.
Hopefully, none of this makes any sense whatsoever, because if it does, I'm worried about your sanity.
--EDITED--
You were looking for the $where operator. Your query must be in a valid JSON notation and outside of this operator there is no other access to that kind of raw JavaScript notation.
Dates
finally:
{$where: "this.start_date.valueOf() == this.end_date.valueOf()"}
Also be careful not to run into reserved words and other traps.
Be very careful to read the documentation on this operator and make sure you absolutely need it. It will slow things down considerably as your find will scan the entire collection. It cannot use an index.

Resources