SQL Server Query to Get Available Employee based on Schedule - sql

I have two tables, parent table Employees and child table Employees_Availability, like this:
Employees table:
EmployeesID Name Group Availability_Order Available
--------------------------------------------------------------
1 Steve Sales 1 TRUE
2 Ann Sales 2 TRUE
3 Jack Sales 3 FALSE
4 Sandy Support 4 TRUE
5 Bill Support 5 TRUE
6 John Support 6 TRUE
Employees_Schedule table:
EmployeesID Day From To
----------------------------------------------
1 Monday 8:00 12:00
1 Monday 13:00 17:00
2 Monday 12:00 13:00
3 Tuesday 7:30 11:30
3 Wednesday 7:30 11:30
3 Friday 14:30 16:30
4 Tuesday 11:30 17:00
5 Wednesday 8:00 12:00
5 Wednesday 13:00 17:00
5 Thursday 12:00 13:00
5 Friday 7:30 11:30
6 Friday 12:00 13:00
How can I create a query that given date/time and Group return first available employee? I am using SQL Server 2012. Here is what I started doing but got stuck:
Select top 1
Name
from
Empolyees e join? Employees_Schedule s
on
e.employeesID = s.EmployeesID
where
e.group = 'Sales'
and DATENAME(Weekday,'5/24/2016 10:00') = s.Day
and CAST('5/24/2016 10:00' AS TIME) 'hh:mm' >= CAST(s.from AS TIME)
and CAST('5/24/2016 10:00' AS TIME) 'hh:mm' <= CAST(s.to AS TIME)
order by
e.availability_order
Thanks

Have you looked into Window Function and CTE? You could easily achieve this with, for example..
Row_Number() OVER(PARTITION BY day ORDER BY starttime ASC) as ColumnName
Combined with predicate
WHERE columnName = 1 AND groupName = 'groupname'
For detail, read BOL on OVER()Clause here, and CTE here.

It looks like you're close. If you wrap the main part of your SQL in a Common Table Expression and use the row_number() window function then you can find the first available:
;with cte as (
Select top 1
Name,
row_number() over (order by ea.From) PrioritySequence
from
Empolyees e join? Employees_Schedule s
on
e.employeesID = s.EmployeesID
where
e.group = 'Sales'
and DATENAME(Weekday,'5/24/2016 10:00') = s.Day
and CAST('5/24/2016 10:00' AS TIME) 'hh:mm' >= CAST(s.from AS TIME)
and CAST('5/24/2016 10:00' AS TIME) 'hh:mm' <= CAST(s.to AS TIME)
)
select *
from cte
where PrioritySequence = 1

Related

Grouping shift data by 7-day windows in SQL Server 2012

What I want to do is to calculate the number of shifts and hours worked by each employee in any given 7-day period. In order to achieve this, I need to identify and group 'islands' of shifts. Note that this 7-day period is not tied to a calendar week and the beginning and ending of this 7-day period would vary from employee to employee. This is sets it apart from other similar questions asked her in the past.
I have a table like this:
Person ID Start Date End Date Start time End time Hours Worked
12345 06-07-20 06-07-20 6:00 AM 7:45 AM 1.75
12345 06-07-20 06-07-20 8:15 AM 8:45 AM 0.50
12345 06-07-20 06-07-20 9:19 AM 9:43 AM 0.40
12345 08-07-20 08-07-20 12:00 AM 12:39 AM 0.65
12345 09-07-20 09-07-20 10:05 PM 11:59 PM 1.90
12345 11-07-20 11-07-20 4:39 PM 4:54 PM 0.25
12345 22-07-20 22-07-20 7:00 AM 7:30 AM 0.50
12345 23-07-20 23-07-20 1:00 PM 3:00 PM 2.00
12345 24-07-20 24-07-20 9:14 AM 9:35 AM 0.35
12345 27-07-20 27-07-20 4:00 PM 6:00 PM 2.00
12345 27-07-20 27-07-20 2:00 PM 4:00 PM 2.00
12345 28-07-20 28-07-20 9:00 AM 10:00 AM 1.00
12345 28-07-20 28-07-20 4:39 AM 4:59 AM 0.34
I want group and summarise the data above like this:
Person ID From To Number of shifts Number of Hours
12345 06-07-20 11-07-20 6 5.45
12345 22-07-20 28-07-20 7 8.19
Note that the first grouping for employee 12345 starts on 06-07-20 and ends on 11-07-20 because these shifts fall within the 06-07-20 - 13-07-20 7-day window.
The next day 7-day window is from 22-07-20 to 28-07-20, which means that the start date for the 7-day window has to be dynamic and based on the data i.e. not constant which makes this a complex task.
Also note that an employee may work multiple shifts in a day and that the shifts may not be consecutive.
I was playing around with using DATEDIFF() with LAG() and LEAD() but was unable to get to where I want. Any help would be appreciated.
I think you need a recursive CTE gor this. The idea is to enumerate the shifts of each person, and then iteratively walk the dataset, while keeping track of the first date of the period - when there is more than 7 days between the start of a period and the current date, the start date resets, and a new group starts.
with recursive
data as (select t.*, row_number() over(partition by personid order by start_date) rn from mytable t)
cte as (
select personid, start_date, start_date end_date, hours_worked, rn
from data
where rn = 1
union all
select
c.personid,
case when d.start_date > dateadd(day, 7, c.start_date) then d.start_date else c.start_date end,
d.start_date,
d.hours_worked,
d.rn
from cte c
inner join data d on d.personid = c.personid and d.rn = c.rn + 1
)
select personid, start_date, max(start_date) end_date, count(*) no_shifts, sum(hours_worked)
from cte
group by personid, start_date
This assumes that:
dates do not span over multiple days, as shown in your sample data
dates are stored as date datatype, and times as time

Aggregate result from Oracle request

I have a table looking like this :
Table: table_name
name priority day hour
-------------------------------------
name1 1 monday 21:00
name2 3 tuesday 21:00
name3 1 monday 21:00
name4 2 monday 21:00
name5 2 sunday 22:00
name6 1 sunday 23:00
name7 1 thursday 00:00
name8 2 sunday 22:00
Is someone as any idea how I can do a request, then manipulate the result to aggregate the result as below (without the column header of course) :
priority day hour name
-----------------------------------------------
1 monday 21:00 name1,name3
3 tuesday 21:00 name2
2 monday 21:00 name4
2 sunday 22:00 name5,name8
1 sunday 23:00 name6
1 thursday 00:00 name7
I want to group by priority and day and hour.
Priority can be 1 to 5.
Day can obviously be Monday to Sunday
Hour can obviously be any hours :)
Name can be anything.
At the end, I need a SQL query to write the formatted results into a file.
As a workaround for the ORA-01489 you can use XMLAGG as a workaround as per this answer
with extract
SET LONG 2000000
SET pagesize 50000
SELECT rtrim(xmlagg(XMLELEMENT(e,text,',').EXTRACT('//text()')
).GetClobVal(),',') very_long_text
FROM
(SELECT to_char(to_date(level,'j'), 'jsp') text FROM dual CONNECT BY LEVEL < 250
)
STRAGG can be usefull
SELECT priority, day, hour, stragg(name)
FROM table_name
GROUP BY priority, day, hour
and if it is not sufficient use LISTAGG
SELECT priority, day, hour, LISTAGG(name, ',') WITHIN GROUP (ORDER BY name)
FROM table_name
GROUP BY priority, day, hour

SQL: Differences between times and counting the frequency

I have the following data ordered by events, ID and then start_time:
EVENT ID START_TIME END_TIME
1 101 1:00 2:00
1 101 3:00 3:30
1 102 1:00 4:00
1 102 5:00 6:00
2 103 10:00 11:00
2 103 12:00 13:00
2 103 13:30 14:00
2 103 14:30 15:00
And I want to end up with the following:
Difference_hour Frequency
1 3
0,5 2
I would like to obtain a query that is looking at the difference between the END_TIME of an ID and the START_TIME of the same ID within the same EVENT (to mention specifically, i am not interested in the difference between the START_TIME and END_TIME of the same row).
Example: in event 1 we have to ID's 101, and I would like to have the difference between the first END_TIME (2:00) and the following START_TIME on the second row 3:00). The difference is 1 hour. If we do this similar for ID 102, we end up with another difference of 1 hour.
In the end, I would like to count the frequency of each of the differences, which can be seen in the second table.
select diff_hour, count(*)
from
(
select (next_start - end_time)*86400 as diff_hour
from
(
select end_time, lead(start_time) over (partition by event, id order by start_time) next_start
from MyTable
) x1
where next_start is not null
) x2
group by diff_hour

Giving a common value to groups of consecutive hours in SQL

I am using Netezza.
Let's say I have a table with two fields: one field is a timestamp corresponding to every hour in the day, the other is an indicator for whether or not a patient took an antacid during the hour. The table looks as follows:
Timestamp Antacid?
11/23/2016 08:00 1
11/23/2016 09:00 1
11/23/2016 10:00 1
11/23/2016 11:00 0
11/23/2016 12:00 0
11/23/2016 13:00 1
11/23/2016 14:00 1
11/23/2016 15:00 0
Is there a way to assign a common partition value to each set of consecutive hour intervals? Something like this...
Timestamp Antacid? Group
11/23/2016 08:00 1 1
11/23/2016 09:00 1 1
11/23/2016 10:00 1 1
11/23/2016 11:00 0 NULL
11/23/2016 12:00 0 NULL
11/23/2016 13:00 1 2
11/23/2016 14:00 1 2
11/23/2016 15:00 0 NULL
I would ultimately like to figure out the start date and end date for all consecutive hours of antacid usage (so the start and end dates for the first group would be 11/23/2016 08:00 and 11/23/2016 10:00 respectively, and the start/end dates for the second group would be 11/23/2016 13:00 and 11/23/2016 14:00, respectively). I have done this before with consecutive days using extract(epoch from date - row_number()) but I'm not sure how to handle hours.
I assume this has to be done for each patient (id in the query here). You can use
select id,antacid,min(dt) startdate,max(dt) enddate from (
select t.*,
-row_number() over(partition by id,antacid order by dt)
+ row_number() over(partition by id order by dt) grp
from t
) x
where antacid = 1
group by id,antacid,grp
order by 1,3
The inner query gets you the continuous groups of 0 and 1 for antacid for a given patient id. Because you only need the start and end dates for antacid=1, you can use a where clause to filter.
Add partition by date if this has to be done for each day.
Edit: Grouping rows only if the difference between the current row and the next row is one hour.
select id,antacid,min(dt) startdate,max(dt) enddate from (
select t.*,
--change dateadd as per Netezza functions so you add -row_number hours
dateadd(hour,-row_number() over(partition by id,antacid order by dt),dt) grp
from t
) x
where antacid = 1
group by id,antacid,grp
order by 1,3

SQL Find latest date where condition doesn't exist

I'm quite new to SQL and working on a query that has been thoroughly defeating me for a while now. I come to this site often - it's a terrific resource thanks to all of your expertise, and generally I find what I need, but this time I think my query is a bit too specific and I've not found something applicable. Could someone give me a hand, please?
I have two tables: one Client table and one Contact (aka appointment) table. What I need to find are all of the clients' most recent appointment days (before a certain date, in this case '11/08/2015') where the Outcome for any appointment on that day is NOT a '2'. Each client may have more than one appointment on a single day, and an Outcome of '2' for any of those appointments means that we have to ignore the whole day and move back to the next most recent day..
For example, Client '3' should have a returned appointment date of '01/07/2015' (and just one row) and not the two rows for '16/07/2015', because one of the appointments on '16/07/2015' had an Outcome of '2'. All other values for Outcome are acceptable (including NULL), just not '2'.
The multiple appointment on the same day bit is the part that I'm finding tricky - I can find the latest appointment day using a Select MAX (or TOP 1) statement, but when I add on a "<> '2'" it still continues to return the same days that may have an Outcome '2' because other appointments on that same day have another Outcome. I've been trying to play around with my tables and GROUP BY and NOT EXIST, but I don't seem to be making any headway.
Contact
ClientID AppDate Outcome
1 30/07/2015 17:00 2
1 01/07/2015 17:00 3
2 03/03/2015 16:00 NULL
2 01/03/2015 16:00 NULL
3 16/07/2015 15:40 6
3 16/07/2015 15:40 2
3 01/07/2015 15:40 3
4 05/08/2015 12:30 6
4 05/08/2015 12:30 2
4 01/08/2015 12:30 3
5 23/07/2015 15:30 2
5 23/07/2015 15:30 NULL
5 01/07/2015 15:30 4
6 20/07/2015 10:10 NULL
6 20/07/2015 10:10 2
6 01/07/2015 10:10 6
7 23/07/2015 15:40 2
7 01/07/2015 15:40 1
7 23/06/2015 15:40 8
8 13/07/2015 11:30 2
8 13/07/2015 11:30 6
8 01/07/2015 11:30 2
8 01/06/2015 11:30 3
9 29/07/2015 17:00 3
9 29/07/2015 17:00 6
10 14/07/2015 11:00 NULL
10 01/07/2015 11:00 5
Client
ClientID Forename Surname
1 I B
2 J B
3 S C
4 S T
5 P C
6 K D
7 P E
8 P H
9 S F
10 A G
Apologies if I'm missing something glaringly obvious! Thanks for reading and for any responses. I attach my truncated query for your general amusement...
SELECT
cli.ClientID ,
cli.Forename ,
cli.Surname ,
con.AppDate ,
con.Outcome
FROM
Client AS cli
INNER JOIN
Contact AS con
ON cli.ClientID = con.ClientID
AND con.AppDate =
(SELECT MAX(con1.AppDate)
FROM Contact AS con1
WHERE con.ClientID = con1.ClientID
AND con1.AppDate < '11/08/2015 00:00:00'
AND con1.Outcome <> '2')
ORDER BY
cli.ClientID
EDIT:
Thank you to Mr Linoff for the Cross Apply query, it worked perfectly.
Sorry that I didn't include the expected output earlier. For reference (for anyone else working with a similar problem in future) I was looking to obtain:
Appointments
Client ID Act Date and Time Outcome
1 01/07/2015 17:00 3
2 03/03/2015 16:00 NULL
3 01/07/2015 15:40 3
4 01/08/2015 12:30 3
5 01/07/2015 15:30 4
6 01/07/2015 10:10 6
7 01/07/2015 15:40 1
8 01/06/2015 11:30 3
9 29/07/2015 17:00 3
9 29/07/2015 17:00 6
10 14/07/2015 11:00 NULL
I think cross apply is the best approach to this:
select c.*, con.*
from client c cross apply
(select top 1 con.*
from (select con.*,
sum(case when Outcome = 2 then 1 else 0 end) over (partition by ClientId, AppDate) as num2s
from contact con
where con.ClientId = c.ClientId and
con.AppDate < '2015-11-08'
) con
where num2s = 0
order by AppDate desc
) con;
In this case, cross apply works a lot like a correlated subquery, but you can return multiple values. The subquery uses window functions to count the number of "2" on a given day and the rest of the logic should be pretty obvious.
This returns one row from the most recent date with appropriate appointments. If you want multiple such rows, use with ties.
What you need to do is to have a condition which picks out all the Dates where Outcome is two and filter them out.
Something like this:
WITH ClientCTE AS
(
SELECT MAX(con1.AppDate) AS AppDate ,ClientID
FROM Contact AS con1
WHERE con.ClientID = con1.ClientID
AND con1.AppDate < '11/08/2015 00:00:00'
AND con1.Outcome <> '2'
AND con1.AppDate NOT IN (SELECT AppDate FROM Contact WHERE Outcome = '2'))
SELECT
* FROM Client C
INNER JOIN
Contact AS con
ON cli.ClientID = con.ClientID
INNER JOIN ClientCTE CTE
ON cli.ClientID = CTE.ClientCTE
AND CTE.AppDate = con.AppDate
Let me know if it works
Please try the below query.
It uses the MAX(case...) OVER( partition BY CAST(AppDate as DATE) windowed function to flag all appointments which have a 2 outcome on same day along with WHERE clause to filter out dates.
This flag is then used in outer WHERE clause to remove unwanted data and joined on contacts and client tables
SELECT
cli.ClientID ,
cli.Forename ,
cli.Surname ,
con.AppDate ,
con.Outcome
from
client cli right join contact con
on con.ClientID=cli.ClientID
inner join
(
select ClientID,MAX(AppDate)as lastDate from
( select *,
MAX(CASE when ISNULL(Outcome,1) =2 then 1 else 0 end) OVER(PARTITION BY CAST(AppDate as DATE),ClientID ORDER BY AppDate) as flag
from Contact
where AppDate< '2015-11-08'
) p
where flag =0 group by ClientID
) s
on s.ClientId=con.ClientID and s.lastDate=con.AppDate
Also here's the link to sql fiddle for demo:
http://sqlfiddle.com/#!6/c519d/2