Query to find a block of time in a schedule - sql

Imagine I had a work schedule from 9am to 6pm. It is divided into 15 minute blocks and appointments (increments of 15 minutes) can be fitted into the times available.
Now, if I need to insert a new appointment that is 45 minutes long is there an easy query to find a block of time that is available to fit the appointment in for a given date
The basic table design is
AppointmentId
Date
StartTime
Length - 15 minute incremenents
I would like to get a list of available times to choose from, so if the only appointment for the given day is a 30 minute one at 9:30 then the list of times would be
(No times before 9:30 as the 45 minute appointment wont fit)
10:15
10:30
10:45
...
5:15pm (last time of the day the appointment will fit)

By using ranking function (i.e Row_Number()) set number for each row in each day (let say it's name is rn), then join this query with it self by this condition q2.rn = q1.rn-1 then you have end of appointment beside start of next appointment, then calculate datediff(mi) on this end and start, so this value is the gap, then write another query wrapping this query to filter records that have gap >= yourNeededTime. Also for start of day and end of day you can create 2 dummy records one for 9am and one for 6pm so that yo can handle gap of start of day to the first appointment and last appointment to the end of day.
I hope this helps

Related

How does one get number of full days and half days from a start and end date

Background
I've been working on some reporting views that get a multi-day work shift and are supposed to do some calculations based on data, but I'm a bit stuck here.
A typical shift is either 3 calendar days usually 1 half-day and two full days, or a whole week consisting of 2 half-days (end and start) and 5 full days.
Specifications
I have the following specifications for what is a full day and half-day. These rules are based on regulation and can't be changed.
2 half-days != 1 full-day, the 2 halves is more "valuable"
Given a started_at iso datetime and end_at iso datetime
I want to get two numbers, full_days, and half_days
A half day is
A day at the start of the range starting at or after 12.00
A day at the end of the range which ends before 19.00
A full day is
A day within the range (traditional 24hours)
A day at the start of the range starting before 12.00
A day at the end of the range which ends at or after 19.00
I'm thinking either a row per full-day and half-day or an aggregated row with half_days and full_days as two separate columns would be ideal in the view to connect it with my other views.
Simplified model
I simplified the data model to leave out unnecessary columns.
create table if not exists [trip]
(
trip_id integer
constraint trip_pk
primary key,
started_at text default (datetime('now')),
end_at text default (datetime('now'))
);
And I'm a bit stuck with how I should design this query. A simple time delta doesn't work.
SQLFiddle with sample data and answers: http://sqlfiddle.com/#!5/de7551/2
You can solve this with a CTE which calculates the day span (number of days the shift spans). Since half days are always 1, 2 or 0 (only occur on end and start) we don't actually need to consider each day by itself.
You can use julianday to get the day as a number, however julian days start at noon so you'll need to subtract 0.5 to get the "actual" day for your calculation. Floor the ending day to avoid a to long span if the end time is later then the start time on each respective day, and round up the result to include partial days as a spanned day.
At this point we can calculate number of half days by checking the end and start. To get the number of full days we simply subtract the half days from the result.
with trip_spans as (
select
ceil(julianday(end_at)-0.5 - floor(julianday(started_at)-0.5)) day_span
, t.*
, (
iif(time(started_at) > time('12:00'), 1, 0)
+
iif(time(end_at) <= time('19:00'), 1, 0)
) half_days
from trip t
)
select
trip_spans.*
, day_span-half_days full_days
from trip_spans

Calculate the working hours between two dates in sql server excluding weekend

I am working on a rent system database where a car is given on rent. I need to calculate the (sum of time difference) total time of loan for a car in the given date time range for only working hours and excluding weekend.
Examples to clarify the scenarios
our given date range in where clause is 1/02/17 and 5/2/17
Joe books a car on 01/02/2017 at 9am and returns it at 5pm on 01/02/2017 … 8 hour booking included.
our given date range in where clause is 19/02/17 and 20/2/17
Trish books car on 19/02/2017 at 9am and returns it at 5pm on 21/02/2017 … 16 hour booking included only (ie. 19th & 20th), as the 3rd day falls outside our Date Range end point of 20/02/2017.
our given date range in where clause is 1/02/17 and 5/2/17
Tom books car on 31/01/2017 at 9am and returns it at 12pm on 01/02/2017 … 3 hour booking included only (ie. ½ day on 1st Feb), as the the 1st day (ie. 31st Jan) falls outside our Date Range starting point of 01/02/2017
If any of the column values differ (ie. Company Name, Loan Vehicle Registration, Service Advisor, or Drivers Number Plate)then, that will be a unique row. But if those column are all the same and the only difference is the booking date (ie. if Joe books same car out twice within the Date Range period, using the same Company, Service Advisor, and Drivers Number Plate), then that should be grouped/calculated as one record line.
My current stored procedure is
ALTER PROCEDURE [dbo].[bookingAnalysis]
#from_date nvarchar(50)=NULL,
#to_date nvarchar(50)=NULL
AS
BEGIN
SET NOCOUNT ON;
Select cmp.CompanyName,v.NumberPlate as Loan_Vehicle_Registration,m.ModelDescription as Vehicle_Description,
cnt.FirstName,cnt.LastName,DATEDIFF(HOUR,b.DueOutDate,b.ActualReturnedDate)as
Duration_of_Loan,b.bookingID,b.DriversNumberPlate from Bookings as b
inner join Companies as cmp on b.AssignedCompanyID=cmp.CompanyID
inner join Vehicles as v on v.VehicleID=b.VehicleID
inner join Models as m on m.ModelID=v.ModelID inner join Contacts
cnt on cnt.ContactID=b.ContactID where b.DueOutDate>= #from_date and b.ActualReturnedDate<= #to_date
END
Currently I am getting datediff between b.DueoutDate and b.ActualReturnDate
It's going to be a lot simpler to do this if you first create a calendar table, with one row per date. You can use that then to filter out weekends, and you will get one row as a result for each of the days when the booking is valid.
After that you just have to check:
if the start date is the same as calendar date -> check possible hours to be deducted from the start
if the end date is the same as calendar date -> check possible hours to be deducted from the end
Then just sum all other days with 8 hours, if that was your working day.

Subtracting two dates (including hours and minutes) considering only working time vba or Excel

I need to subtract two dates (including hours and minutes), but I only need to consider working hours. That is, I need to omit lunch time (from 13 to 14 hrs), weekends and hours after 18 hrs and before 9 hrs of the following day, in a working day (from Mo to Fr). Any thoughts?
I don't mind if it's an Excel formula or a vba code.
I have this formula, but it doesn't omit lunch time:
9*(NETWORKDAYS(initial_time;ending_time)-1)-24*((MOD(initial_time;1)-MOD(ending_‌​time;1)))
Here's a possible solution. It assumes an 8 hour work days for all but the start and end date. Also that start date/time is 9:00 or after and end date/time is 18:00 or earlier and that both are on a weekday.
=(NETWORKDAYS(A2,B2)-2)*8+IF(MOD(A2,1)>0.58333,(TIME(18,0,0)-MOD(A2,1))*24,(TIME(18,0,0)-MOD(A2,1))*24-1)+IF(MOD(B2,1)>0.58333,(MOD(B2,1)-TIME(9,0,0))*24-1,(MOD(B2,1)-TIME(9,0,0))*24)
.58333 equates to 14:00. The formula:
multiplies networkdays * 8
+ hours from start date/time until 18:00 subtracting 1 hour if start time is before 14:00
+ hours from 9:00 until end date/time subtracting 1 hour if end time is after 14:00
Of course this doesn't take any holidays into account.

datetime manipulation: replace all dates with 00:00 time with 24:00 the previous day

I have a table described here: http://sqlfiddle.com/#!3/f8852/3
The date_time field for when the time is 00:00 is wrong. For example:
5/24/2013 00:00
This should really be:
5/23/2013 24:00
So hour 00:00 corresponds to the last hour of the previous day (I didn't create this table but have to work with it). Is there way quick way when I do a select I can replace all dates with 00:00 as the time with 24:00 the previous day? I can do it easily in python in a for loop but not quite sure how to structure it in sql. Appreciate the help.
All datetimes are instants in time, not spans of a finite length, and they can exist in only one day. The instant that represents Midnight is by definition, in the next day, the day in which it is the start of the day, i.e., a day is closed on its beginning and open at its end, or, to phrase it again, valid allowable time values within a single calendar date vary from 00:00:00.00000, to 23:59:59.9999.
This would be analogous to asking that the minute value within an hour be allowed to vary from 1 to 60, instead of from 0 to 59, and that the value of 60 was the last minute of the previous hour.
What you are talking about is only a display issue. Even if you could enter a date as 1 Jan 2013 24:00, (24:00:00 is not a legal time of day) it would be entered as a datetime at the start of the date 2 Jan, not at the end of 1 Jan.
One thing that illustrates this, is to notice that, because of rounding (SQL can only resolve datetimes to within about 300 milleseconds), if you create a datetime that is only a few milleseconds before midnight, it will round up to midnight and move to the next day, as can be seen by running the following in enterprise manager...
Select cast ('1 Jan 2013 23:59:59.999' as datetime)
SQL server stoers all datetimes as two integers, one that represents the number days since 1 Jan 1900, and the other the number of ticks (1 tick is 1/300th of a second, about 3.33 ms), since midnight. If it has been zero time interval since Midnight, it is stll the same day, not the previous day.
If you have been inserting data assuming that midnight 00:00:00 means the end of the day, you need to fix that.
If you need to correct your existing data, you need to add one day to every date in your database that has midnight as it's time component, (i.e., has a zero time component).
Update tbale set
date_time = dateAdd(day, 1, date_time)
Where date_time = dateadd(day, datediff(day, 0, date_time), 0)

How do I compare overlapping values within a row?

I seem to have a problem with this SQL query:
SELECT * FROM appts
WHERE timeStart >='$timeStart'
AND timeEnd <='$timeEnd'
AND dayappt='$boatdate'
The time is formatted as military time. The logistics is that a boat rental can be reserved at 7am til 1pm or 9am til 1pm or 9am til 5pm. If there is an appt within that range, it should return appts but it has proven to be inconsistent. If I pick 9am til 1pm, it will ignore appts that started with 7am even though it overlaps 9am-1pm. If I pick 9 to 5, it will return nothing even though it should with the 7am to 1pm. How do I make a SQL statement that includes the whole range from timeStart to timeEnd including those that overlap?
Shahkalpesh answered the question with:
I think you need an OR.
SELECT * FROM appts
WHERE (timeStart >='$timeStart'
OR timeEnd <='$timeEnd')
AND dayappt='$boatdate'
I posted a comment that I consider this to be wrong, giving a pair of counter-examples:
This is plain wrong - #ShaneD is correct. For example, this will pick out a booking between 05:00 and 06:00 because the actual end time is less than any of the end times you ask about. It will also pick up rentals from 18:00 onwards, for the equivalent reason.
In a response to my comment, Shahkalpesh requested:
Could you post a separate reply with data & input parameters with expected output?
Fair enough - yes. Slightly edited, the question says:
The logic is that a boat rental can be reserved
from 7am until 1pm, or
from 9am until 1pm, or
from 9am until 5pm.
If there is an appointment within that range, it should return appointments but it has proven to be inconsistent. If I pick 9am until 1pm, ...
Enough background. We can ignore the date of the appointments, and just consider the times. I'm assuming that there is an easy way to limit the times recorded to hh:mm format; not all DBMS actually provide that, but the extension to handle hh:mm:ss is trivial.
Appointments
Row timeStart timeEnd Note
1 07:00 13:00 First valid range
2 09:00 13:00 Second valid range
3 09:00 17:00 Third valid range
4 14:00 17:00 First plausibly valid range
5 05:00 06:00 First probably invalid range
6 18:00 22:30 Second probably invalid range
Given a search for appointments overlapping the range 09:00 - 13:00, Shahkalpesh's (simplified) query becomes:
SELECT * FROM Appointments
WHERE (timeStart >= '09:00' OR timeEnd <= '13:00')
This will return all six rows of data. However, only rows 1, 2, 3 overlap the time period 09:00 - 13:00. If rows 1, 2, and 3 are the only valid representative appointment values, then Shahkalpesh's query produces the correct answer. However, if the row 4 (which I think is plausibly valid) is permitted, then it should not be returned. Similarly, rows 5 and 6 - if present - should not be returned. [Actually, assuming timeStart <= timeEnd for all rows in the table (and there are no NULL values to mess things up), we can see that Shahkalpesh's query will return ANY row of data for the 09:00-13:00 query because either the start time of the row is greater 09:00 or the end time is less than 13:00 or both. This is tantamount to writing 1 = 1 or any other tautology in the WHERE clause.]
If we consider ShaneD's query (as simplified):
SELECT * FROM Appointments
WHERE timeStart <= '13:00' AND timeEnd >= '09:00'
we see that it also selects rows 1, 2, and 3, but it rejects rows 4 (because timeStart > '13:00'), 5 (because timeEnd < '09:00') and 6 (because timeStart > '13:00'). This expression is an archetypal example of how to select rows which 'overlap', counting 'meets' and 'met by' (see "Allen's Interval Algebra", for instance) as overlapping. Changing '>=' and '<=' alters the set of intervals counted as overlapping.
The correct check would look like this:
SELECT * FROM appts
WHERE timeStart <='$timeEnd'
AND timeEnd >='$timeStart'
AND dayappt='$boatdate'
Other good explanations have been given but I'll go ahead and update it with an alternative explanation of how I visualize this myself. Most people are looking for each possible overlap, considering two time periods, they are trying to think of each combination of start and end that can make an appointment overlap. I think about it as when do two time periods not overlap which for some reason is easier for me.
Say the time period I am checking for is today, I want to find any time period that does not overlap today. There are really only two scenarios for that, either the time period starts after today (PeriodStart > EndOfToday) or the time period ends before today (PeriodEnd < StartOfToday).
Given that we havea simple test for not overlapping:
(PeriodStart > EndOfToday) OR (PeriodEnd < StartOfToday)
A quick flip around and you have a simple test for overlap:
(PeriodStart <= EndOfToday) AND (PeriodEnd >= StartOfToday)
-Shane
Thanks Shane, Shahkalpesh, and Jonathan.
I actually overlooked the fact that Shane "swapped" the variables (I was still using timeStart<=$timeStart when it should be timeStart <= $timeEnd). I ran with the modified statement as Jonathan/Shane suggested and it works. As Jonathan did point out, I did obviously missed out some time ranges that I should have tested against.
Now with Jonathan's explanation, I now get a better picture of my mistake is and it's helpful.
I think you need an OR.
SELECT * FROM appts
WHERE (timeStart >='$timeStart'
OR timeEnd &LT;='$timeEnd')
AND dayappt='$boatdate'
Assuming each record cares about only a specific day.
i.e. Boats rented don't run across more than 1 day.