Using SQL for an auto scheduler? - sql

I had a question regarding SQL and if you could code it properly to do auto scheduling.
Example
A shipment could come to a store Monday-Friday, but there could only be a certain amount of shipments during this week and it depends which worker was available. I'll run down things in order that way it's not too confusing.
A) A shipment can only stop twice a day, but it cannot stop on a day that has any shipments the day before or the day after. So if Monday had 2 shipments, there would be no stops on Tuesday. If Wednesday has 2 shipments, you could not have any stops Tuesday/Thursday.
B) A shipment can only be so big on these days. If two shipments are both UNDER 1000 pounds, then we can schedule both, but if a shipment is OVER 1000 pounds, then we can only schedule one for that day.
C) One of the two workers must unload each shipment. So lets say that A already has 1 shipment on Monday, I need it to go to B for the 2nd shipment on that Monday.
I understand that SQL cannot accept user input directly, so don't worry about this. Using getdate() would be sufficient enough.
I started on this code and got a little bit in, but the while loop was running for forever (>30 seconds) and I thought it was almost impractical at that point.
Curious if this is doable and the best approach to do this. I'm assuming that C# or something would be loads easier to do with, but wondering about SQL.
Thanks in advance.

Related

vb.net calculating booked appointment times from a list of available times?

I'm trying to find an efficient way to calculate the booked times for a user(object), given a list of free/available times for the same user\object.
I have an object that will return the "available" times for a given specific day. The duration/end time is fixed to 10 minutes.
Example Starting data:
12/23/2020 8:00 AM
12/23/2020 9:00 AM
12/23/2020 1:00 PM
In this case I need to generate the "unavailable" times and insert them into a database with a fairly simple schema:
start_date | end_date | start_time | end_time
The inserting is fairly trivial, i'm having a hard time determining the best way to calculate the unavailable timespans.
Using the example above i would need to generate the following timespans:
12/23/2020 12:00 AM - 7:59 AM
12/23/2020 08:11 AM - 8:59 AM
12/23/2020 09:11 AM - 12:59 PM
12/23/2020 1:11 PM - 11:59 PM
Any frameworks or libraries that can do the heavy lifting on this for me? Is it possible to solve this problem without looping through the results and calculating all of the offsets?
To anyone asking "why" - hooking together two legacy systems, one system returns the available appointments for a given date this needs to be plumbed into a system that needs the un-available appointments for a given date.
Well, first I written more tour booking systems then I can shake a stick at.
The one Rosetta stone that holds true?
You don't want to generate or have booking slots that are NOT being used in the system PERIOD!!!
Thus you ONLY ever enter into the system a valid booking (starttime, and end time). And that startTime should be a datetime column - this will VAST reduce the complexity of your queries. Given you have date and separate time? Well, then your queries will be more complex - I'll leave that to you.
Given the above? The simple logic to find a booking collision in ALL cases is this:
A collision occurs when:
RequestStartDate <= EndDate
and
RequestEndDate >= StartDate
Now in above, I assume date values, or datetime values.
So if I want a list of any booking for today?
RequestDDTStart = 2020-12-23 9 AM
RequestDTEnd = 2020-12-23 5 PM
And thus any collision can be found with this:
strWhere= dtRequestStartDate <= BookingEndDate" & _
" and dtRequestEndDate >= BookingStartDate"
Now, assumging .net, then above would be something like this as parameters
strWhere= #dtRequestStart <= BookingEndDate" & _
" and #dtRequestEnd >= BookingStartDate"
So, above would return all bookings for today 9 am to 5 pm
A REMARKABLE simple query! Now of course the above query could/would include the exam room, or hotel room or whatever as an additional criteria. But in ALL cases the above simple query returns ANY collision for that 9 am to 5 pm.
And the beauty of this system? As long as you never allow a over-lap into the booking system, then you can book a 10 minute or a 20 minute or a 30 minute session as ONE entry into the database. I would thus not need to create 3x 10 minute slots.
So, this means you NEVER have to create booking slots. The whole system will and can be driver with a simple start + end booking record. And as noted, then you can book 1 hour, or 40 minutes. Your input (UI) can simple limit the time span to increments of 10 minutes - but that's the UI part.
Now I suppose to display things in 10 minute increments on a screen? Well, then you would have to submit 6 requests per hour to "display" the time slots. For a whole day, that suggest for 9 am to 5 pm, you would have to run 8 x 6 = 48 requests to get a list of 10 minute increments. But then again, you COULD just show the existing bookings for a day, and allow new bookings to be added - but don't allow if there is a over lap.
So, as noted, the concept here is you don't really need "slots" in the database. I suppose you could try slots, but it makes the code a HUGE mess to deal with. if you ONLY ever store the start + end? Then I can say move the booking to another day by JUST changing the date. Or I can extend a booking from 10 minutes to say 20 or 40 minutes - and ONLY have to change the end time. As long as no overlap occurs with the above simple "test", then I can simple change the booking to be 40 minutes in length - and ZERO code to update multiple slots is required. And same goes for reducing a booking from 40 minutes to 10 minutes. Again ONLY the end time need be reduced - a ONE row update into the database.
So if at all possible, I would dump the concept of having "slots" in the database. I might consider such a design if a booking was only ever 10 minutes. But if 10 or 20 or 30 is allowed, then you don't need to store ANY un-used slots in the database, but ONLY ever store a valid booked slot. Empty un-used time can thus ALSO be found with the above query. (if the query returns records - then you can't book).
So display of free time in some UI becomes more of a challenge, but showing bookings that span 10 or 20 or whatever minutes is far more easy, and as noted, you can even change a whole booking to a different room by a ONE row update of the room ID. If no collision occurs, then you allow this booking - and you achieve this result by ONLY updating one simple booking record that represents that start + end time.
and this means you also NEVER store the booking totals in the database - you query them!
I also found that if I say store any booking totals in the database? Well, with complex code, we always found that the totals often don't match perfect. So then we wind up writing a routine to go though the data, sum up the totals and write those out.
But, if you never store any booked totals (say people on a bus, or people in a given hotel), then while the query for such display is somewhat more difficult, it becomes dead simple to remove a person from say a tour by simple null out of the tourID.
So, this display shows the above concepts in Action. And the available rooms in the hotel, people booked on bus, and even totals for "group tours" are ALL values NOT stored in the database:
So in above, people booked on bus, booked in rooms, and rooms used? All those values are NOT stored in the database. And no slots exist either. So if we have a bus, then we set the capacity of 46, but we do NOT create 46 slots to book into. So be it a bus, a hotel, a medical exam room? You don't create booking slots ahead of time, but simply insert bookings with a start + end, and then query against that concept.
So, to find a total on a bus (or say in a exam room), I query to find the total for that day. And if I want to move a group booking of 4 people from one bus to another? Then one FK update to the given bus they are on allows the whole system to "cascade" the existing values in the system. And same goes for moving a person from exam room #1 to #5. You only have to update the FK value of the exam room. If no collisions occur, then this again is a one row update. If you have multiple exam rooms, and multiple slots, then what should be a simple one row update in the database becomes a whole hodge podge of now having to update multiple booking slots with whacks of code.
So you book "use" of resources "into" a "day" a "bus" a room, but it is the act of that booking that consumes the time slots - not that you pre-create records or timeslots for each "range". This thus allows you to leverage the relatonal database model, and reduce huge amounts of code - since you not coding against "slots", but only that a exam room is open from 10 am to 4 pm. That available room for that day is thus ONLY ONE record you create in the system, and then you are now free to book into that one day given room range. The bookings into that one room for the day can be 10 minutes, or 40 minutes - but it ONLY one record being added into the database to achieve this goal (booking).
Regardless of the above, that simple collision query works for any collision (including a whole overlap, inside a existing span, or even the end or start overlaps any booking. And that query is dead simple - and it works for all collisions. So I don't have a library to share, but that simple booking collision finder query can thus drive the whole system based on that kind of simple query.

Organizing per-week and daily data in SQL

Problem overview
I'm working on a simple app for reminding the user of weekly goals. Let's say the goal is to do 30 minutes of exercise on specific days of the week.
Sample goal: do exercise on Mon, Wed, Fri.
The app also needs to track past record, i.e. dates when the user did exercise. It could be just dates, e.g.: 2019-09-02, 2019-09-05, 2019-09-11 means the user did exercise on these days and did not on the others (doesn't need to be on "exercise goal" days of the week).
The goal can change in time. Let's say today is 2019-09-11 and the goal for this week ([2019-09-09, 2019-09-15]) is Mon, Wed, Fri but from 2019-08-05 to 2019-09-08 it was Mon, Thu (repeatedly for all these weeks).
I need to store these week-oriented goals and historic exercise of data and be able to retrieve the following:
The goal days for the current week (or any week, let's say I can compute start and end day for any week given a date).
Exercise history for a larger range of days together with goal days for that range (e.g. to show when the user was supposed to exercise and when they actually did in the last month).
Question
How to best store this data in SQL.
This is a little bit academic because I'm working on a small Android app and the data is just for a single user. So there will be little data and I can successfully use any approach, even a very clumsy one will be efficient enough.
However, I'd like to explore the topic and maybe learn a thing or two.
Possible solutions
Here are two approaches that come to my mind.
In both cases I would store exercise history as a table of dates. If there is an entry for that date it means the user did exercise on that day.
It's the goal storage that is interesting.
Approach 1
Store the goals per-week (it's SQLite so dates are stored as strings - all dates are just 'YEAR-MONTH-DAY'):
CREATE TABLE goals (
start_date TEXT,
exercise_days TEXT);
"start_date" is the first day of the week,
"exercise_days" is a comma-separated list of weekdays (let's say numbers 1-7).
So for the example above we might have two rows:
'2019-08-05', '1,4'
'2019-09-09', '1,3,5'
meaning that since 2019-08-05 the goal is Mon, Thu for all weeks until 2019-09-09, when the goal becomes Mon, Wed, Fri. So there is a gap in the data. I wouldn't want to generate data for weeks starting on 2019-08-12, 2019-08-19, 2019-08-26.
With this approach it is easy to work with the data week-wise. The current goal is the one with MAX('start_date'). The goal for a week for a given date is MAX('start_date') WHERE 'start_date' <= :date.
However it gets cumbersome when I want to get data for the last 3 months and show the user their progress.
Or maybe I want to show the user the percentage of actual exercise days to what they set as their goal in a year.
In this case it seems the best approach is to fetch the data separately and merge it in the application (or maybe write some complex queries), processing week by week. This is ok performance-wise because the amount of data is small and I rarely need more than a handful of weeks.
Approach 2
Store goals in such a way that each goal day is a record:
CREATE TABLE goals (
day TEXT,
);
"day" is a day when the user should exercise. So for the week starting 2019-09-09 (Mon, Wed, Fri) we would have:
'2019-09-09'
'2019-09-11'
'2019-09-13'
and for the week starting 2019-08-05 (Mon, Thu) we would have:
'2019-08-05'
'2019-08-09'
but what for the weeks in-between?
If my app could fill all the weeks in-between then it would be easy to merge this data with the exercise history and display days when the user was supposed to exercise and when they actually did. Extracting the goal for any given week would also be easy.
The problem is: this requires the app to generate data for the "gap" weeks even if the user doesn't tweak the goal. This can be implemented as a transaction that is run each time the app process starts. In some cases it could take noticeable time for occasional users of the app (think progress bar for a second).
Maybe there a smart way to generate the data in-between when making a SELECT query?
I don't like the fact that it requires generating data. I do like the fact that I can just join the tables and then process that (e.g. compute how many exercise days there were supposed to be in August and how many days the user did actually exercise and then show them percentage like "you did 85% of your goal" - in fact I can do this without joining the tables).
Also, it seems this approach gives me more flexibility for analysis in the future.
But is there a third way? Or maybe I am overthinking this? :)
(I am asking mostly for the way of organizing the data, there's no need for exact SQL queries)
Perhaps I'm over-thinking this, but if a goal can have multiple components to it, and can change over time I'd have a goal header record, with the ID, name and other data about the goal as a whole, and then a separate table linked with the components of that goal which are time-boxed, for example:
CREATE TABLE goal_days (goal_day_ID INT,
goal_ID INT,
day_ID INT,
target_minutes INT,
start_date TEXT,
end_date TEXT)
I'd have thought that allows you to easily check against the history to map against each day of the goal - e.g. they got 100% of the Mondays, but kept missing Thursday - however when the goal was changed to Friday instead they got better.

How to store availability information in SQL, including recurring items

So I'm developing a database for an agency that manages many relief staff.
Relief workers set their availability for each day in one of three categories (day, evening, night).
We also need to be able to set some part-time relief workers as busy on weekly, biweekly, and in one instance, on a 9-week rotation. Since we're already developing recurring patterns of availability here, we might as well also give the relief workers the option of setting recurring availability days.
We also need to be able to query the database, and determine if an employee is available for a given day.
But here's the gotcha - we need to be able to use change data capture. So I'm not sure if calculating availability is the best option.
My SQL prototype table looks like this:
TABLE Availability Day
employee_id_fk | workday (DATETIME) | day | eve | night (all booleans)| worksite_code_fk (can be null)
I'm really struggling how to wrap my head around recurring events. I could create say, a years worth, of availability days following a pattern in 'x' day cycle. But how far ahead of time do we store information? I can see running into problems when we reach the end of the data set.
I was thinking of storing say, 6 months of information, then adding a server side task that runs monthly to keep the tables updated with 6 months of data, but my intuition is telling me this is a bad fix.
For absolutely flexibility in the future and keeping data from bloating my first thought would be something like
Calendar Dimension Table - Make it for like 100 years or Whatever you Want make it include day of week information etc.
Time Dimension Table - Hour, Minutes, every 15 what ever but only for 24 hour period
Shifts Table - 1 record per shift e.g. Day, Evening, and Night
Specific Availability Table - Relationship to Calendar & Time with Start & Stops recommend 1 record per day so even if they choose a range of 7 days split that to 1 record perday and 1 record per shift.
Recurring Availability Table - for day of week (1-7),Month,WeekOfYear, whatever you can think of. But again I am thinking 1 record per value so if they are available Mondays and Tuesday's that would be 2 rows. and if multiple shifts then it would be multiple rows.
Now and here is the perhaps the weird part, I would put a Available Column on the Specific and Recurring Availability Tables, maybe make it a tiny int and store something like 0 not available, 1 available, 2 maybe available, 3 available with notice.
If you want to take into account Availability with Notice you could add columns for that too such as x # of days. If you want full flexibility maybe that becomes a related table too.
The queries would be complex but you could use a stored procedure or a table valued function to handle it fairly routinely.

SQL Filtering based on Calculated Time Slots

Im making a simple booking system for our projectors at work.
Here is the scenario. Each projectors can have its availability set to quarter hour segments throughout the entire day. i.e projector 1 is available between 8:15am - 1:45pm and 3pm-5:15pm each day (can also be changed to have different availabilities set for each day). A projector can be booked for anytime time segment during the day as long as it is available. So ive got that setup in my sql database (with my asp.net mvc front end).
The question i have is what is the best way to search on this scenario. i.e. UserA comes in and says find me the projectors that are available this friday between 12pm-3pm. Im struggling to write an efficient sql query that will filter this. My best option so far is to pull back all projectors and than programatically work out if they are available and not booked between this time. It works but it is incredibly inefficient. I stumbled an idea of using a temp table generated by a stored proc that can than be filtered but it isnt quite there.
Has anyone got any ideas how i could approach this?
Thanks in advance
I would probably have a table called ProjectorReservations which contained a start time and end time (amongst other fields you might care about i.e. who is renting the projector).
Searching a projector would look something like this:
SELECT projectorName
FROM Projectors
WHERE NOT EXISTS
(SELECT 1 FROM ProjectorReservations
WHERE Projectors.projectorName = ProjectorReservations.projectorName
AND (ProjectorReservations.startTime < {end_time}
OR ProjectorReservations.endTime > {start_time}))
That pretty much checks to make sure no reservations start before the one you are looking for ends and vice versa. Obviously you will need to swap in your fields accordingly but that should give you the general idea

Opening Hours Database Design

We are currently developing an application in which multiple entities have associated opening hours. Opening hours may span multiple days, or may be contained within a single day.
Ex. Opens Monday at 6:00 and closes at Friday at 18:00.
Or
Opens Monday at 06:00 and closes Monday at 15:00.
Also, an entity may have multiple sets of opening hours per day.
So far, the best design I have found, is to define an opening hour to consist of the following:
StartDay, StartTime, EndDay and EndTime.
This design allows for all the needed flexibility. However, data integrity becomes an issue. I cannot seem to find a solution that will disallow overlapping spans (in the database).
Please share your thoughts.
EDIT: The database is Microsoft SQL Server 2008 R2
Consider storing your StartDay and StartTime, but then have a value for the number of hours that it's open. This will ensure that your closing datetime is after the opening.
OpenDate -- day of week? e.g. 1 for Monday
OpenTime -- time of day. e.g. 08:00
DurationInHours -- in hours or mins. e.g. 15.5
Presuming a robust trigger framework
On insert/update you would check if the new start or end date falls inside of any existing range. If it does then you would roll back the change.
CREATE TRIGGER [dbo].[mytable_iutrig] on [mytable] FOR INSERT, UPDATE AS
IF (SELECT COUNT(*)
FROM inserted, mytable
WHERE (inserted.startdate < mytable.enddate
AND inserted.startdate > mytable.startdate)
OR (inserted.enddate < mytable.enddate
AND inserted.enddate > mytable.startdate)) > 0
BEGIN
RAISERROR --error number
ROLLBACK TRANSACTION
END
Detecting and preventing overlapping time periods will have to be done at the application level. Of course you can attempt to use a trigger in the database but in my opinion this is not a database issue. The structure that you came up with is fine, but your application logic will have to take care of the overlap.
There's an article by Joe Celko on the SimpleTalk website, over here, that discusses a similar issue, and presents am elegant if complex solution. This is probably applicable to your situation.
A table with a single column TimeOfChangeBetweenOpeningAndClosing?
More seriously though, I would probably not worry too much about coming up with a single database structure for representing everything, eventually you'll probably want want a system involving recurrences, planned closures etc. Persist objects representing those, and then evaluate them to find out the closing/opening times.
This looks like a good solution, but you'll have to write a custom validation function. The built in database validation (i.e. unique, less than x, etc.) isn't going to cut it here. To ensure you don't have overlapping spans, every time you insert a record into the database, you're going to have to select existing records and compare...
First the logic, two spans will overlap if the start value of one falls between the start/end of the other. This is much easier if we have datetimes combined, instead of date1,time1 and date2,time2. So a query to find an overlap looks like this.
select openingId
from opening o1
join opening o2 on o1.startDateTime
between o2.startDateTime
AND o2.endDateTime
You can put this into a trigger and throw an error if a match is found.