Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
Author |
Topic |
yonabout
Posting Yak Master
112 Posts |
Posted - 2011-10-26 : 07:21:48
|
Hi,I need to work out employees bradford factors.A bradford factor is a number that gives you an indication of whether an employee has a sickness problem.Bradford factors are calculated by the following formula for a 12 month period.(number of periods of sickness X number of periods of sickness) X total days sickness.So if an employee is off sick one day a week for 4 weeks in the 12 month period, their bradford factor would be:(4 X 4) X 4 = 64.But if they were off for 4 consecutive days in one week, their bradford factor would be:(1 X 1) X 4 = 4.So far so good?My problem is how to determine the periods of sickness in the period.The tables I've got to work this out with are a FlexRecord table (holds userid, a date and whether they were absent or not amongst other thingsA FlexHours table that shows working patterns, as not all people are on a standard 8 hour day for 5 days a week. If they were sick for 3 days in a row, but one of the days was a day they aren't contracted to be in the office, then it counts as 1 period of sickness of 2 days.Here's some test data:--absence type tablecreate table #absencetype(absencetypeid int,absencetype varchar(20))insert #absencetypeselect 1, 'None' union allselect 2, 'Holiday' union allselect 3, 'Sickness'--table that tells us (among other things) what date users were absent oncreate table #flexrecord (userid int,flexdate datetime,absencetypeid int)insert #flexrecordselect 1, '10/10/2011', 1 union allselect 1, '11/10/2011', 3 union allselect 1, '12/10/2011', 1 union allselect 1, '13/10/2011', 1 union allselect 1, '14/10/2011', 3 union allselect 1, '17/10/2011', 3 union allselect 1, '18/10/2011', 1 union allselect 1, '19/10/2011', 1 union allselect 1, '20/10/2011', 1 union allselect 1, '21/10/2011', 1 union allselect 1, '24/10/2011', 1 union allselect 1, '25/10/2011', 3 union allselect 1, '26/10/2011', 3 union allselect 1, '27/10/2011', 3 union allselect 1, '28/10/2011', 1--working patterns tablecreate table #FlexHours (userid int, DayOfWeek int, FlexHours datetime)insert #FlexHoursselect 1, 1, '01/01/1900 08:00:00' union allselect 1, 2, '01/01/1900 08:00:00' union allselect 1, 3, '01/01/1900 00:00:00' union allselect 1, 4, '01/01/1900 08:00:00' union allselect 1, 5, '01/01/1900 08:00:00' OK, so for this employee I can work out how many days they were off sick like this:selectuserid,count(userid)from #flexrecord where absencetypeid = 3group by userid I can also not count any days that they aren't contracted to work with something like this:selectfr.userid,count(fr.userid)from #flexrecord fr join ( select * from #flexhours where flexhours <> '01/01/1900 00:00:00' -- indicates contracted day off ) fhon fr.userid = fh.userid and datepart(dw, fr.flexdate) = fh.dayofweekwhere fr.absencetypeid = 3group by fr.userid But I can't get my head around how to work out how many periods of sickness they've had in the period . In this example, it should be 3 periods of sickness, as the period from 14/10/2011 to 17/10/2011 is over a weekend and counts as one.Any ideas?Cheers,Yonabout |
|
malpashaa
Constraint Violating Yak Guru
264 Posts |
Posted - 2011-10-26 : 07:58:19
|
Try to use COUNT(DISTINCT DATEPART(WEEK, fr.flexdate)) instead of count(fr.userid). |
|
|
Ifor
Aged Yak Warrior
700 Posts |
Posted - 2011-10-26 : 08:37:28
|
1. You should probably use a calendar table. (Google this.)2. Your data is confusing as users are marked sick even on days they are not contracted to work.(I will assume you are not interested in these.)3. Assuming that #flexrecord contains all the dates minus weekends and public holidays, try something like:WITH WorkGrpsAS( SELECT R.userid, R.absencetypeid ,ROW_NUMBER() OVER (PARTITION BY R.userid ORDER BY R.flexdate) - ROW_NUMBER() OVER (PARTITION BY R.userid ORDER BY R.absencetypeid, R.flexdate) AS grp FROM #flexrecord R JOIN #FlexHours H ON r.userid = h.userid AND DATEPART(dw, r.flexdate) = H.[DayOfWeek] AND H.FlexHours > '19000101' WHERE R.flexdate >= '20110101' AND R.flexdate < '20120101'), AbsenceAS( SELECT userid ,COUNT(DISTINCT grp) AS Periods ,COUNT(userid) AS DaysOff FROM WorkGrps WHERE absencetypeid = 3 GROUP BY userid)-- select * from AbsenceSELECT userid, Periods, DaysOff ,Periods * Periods * DaysOff As BradfordFactorFROM Absence |
|
|
yonabout
Posting Yak Master
112 Posts |
Posted - 2011-10-26 : 09:48:17
|
Sorry Ifor, Is ROW_NUMBER() an SQL 2005 function? I'm on SQL 2000.I appreciate the data is a bit weird as it doesn't include weekend days, it's because no one is in on a weekend, so we don't need it.Malpashaa - I probably didn't explain myself very well - I don't need the number of weeks in the reporting period, I need the number of 'instances' the employee has been off, so in this case they were off sick as follows:11/10/2011 - 11/10/2011 (1 period of 1 day)14/10/2011 - 17/10/2011 (1 period of 2 days - don't count weekends)25/10/2011 - 27/10/2011 (1 period of 2 days - this employee doesn't work wednesdays so we don't count them)This gives us 3 periods of illness of 5 days in total. and a bradford factor of (3X3) X 5 = 45.Does this make any more sense?I'm OK getting the number of days they've been off, its working out how many times they've been ill that I'm struggling with.Cheers,Yonabout |
|
|
Ifor
Aged Yak Warrior
700 Posts |
Posted - 2011-10-26 : 10:11:44
|
Sorry about the 2005 syntax; there is no easy way to get the number of periods in SQL2000.Why are you interested in 11-Oct, 14-Oct and 25-Oct when user 1 does not work those days? |
|
|
yonabout
Posting Yak Master
112 Posts |
Posted - 2011-10-26 : 10:35:58
|
Hi,That's the point - I need to find out when people are contracted to work, but didn't becasue they were sick. The FlexHours table is used to record non standard working patterns - if you aren't in there, then you work 8 hours a day, 5 days a week. If you are in there, then it means you've got a non standard working pattern, so we record the user number, and what hours they work on each day monday to friday. So an entry on 'DayOfWeek' 3 of '01/01/1900 00:00:00' means they are contracted to work zero hours - i.e they don't work on wednesdays.So I need to work out all the days they've got an absence type of 3 (Sickness) on a day when they are contracted to work, which in the test data I sent is all the 'Sickness' days except the 26th - a total of 5 days off 6.It probably seems like a bit of a weird way to store the data, but it works for us because it means that there's less overhead managing the front end application.The problem I've got is working out when they started and finished being sick so I can find the number of instances of sickness.Cheers,Yonabout |
|
|
malpashaa
Constraint Violating Yak Guru
264 Posts |
Posted - 2011-10-26 : 11:29:45
|
Try this:SELECT fr.userid, fr.flexdate, fh.DayOfWeek INTO #T1 FROM #flexrecord AS fr LEFT OUTER JOIN (SELECT * FROM #flexhours WHERE flexhours <> '01/01/1900 00:00:00') AS fh ON fr.userid = fh.userid AND DATEPART(dw, fr.flexdate) = fh.dayofweek WHERE fr.absencetypeid = 3INSERT INTO #T1(userid, flexdate, DayOfWeek) SELECT userid, DATEADD(DAY, 1, flexdate), 6 FROM #T1 WHERE DayOfWeek = 5;INSERT INTO #T1(userid, flexdate, DayOfWeek) SELECT userid, DATEADD(DAY, 2, flexdate), 7 FROM #T1 WHERE DayOfWeek = 5;CREATE INDEX __T1__idx__userid__flexdate ON #T1(userid, flexdate);SELECT T.userid, COUNT(T.userid) AS periods_count FROM (SELECT T.userid, MIN(T.flexdate) AS start_of_period, MAX(T.flexdate) AS end_of_period FROM (SELECT T1.userid, T1.flexdate, (SELECT MIN(T2.flexdate) FROM #T1 AS T2 WHERE T2.userid = T1.userid AND T2.flexdate >= T1.flexdate AND NOT EXISTS(SELECT * FROM #T1 AS T3 WHERE T3.userid = T2.userid AND T3.flexdate = DATEADD(DAY, 1, T2.flexdate))) AS grouping_factor FROM #T1 AS T1) AS T GROUP BY userid, grouping_factor) AS T GROUP BY T.userid; |
|
|
yonabout
Posting Yak Master
112 Posts |
Posted - 2011-10-26 : 11:51:11
|
That looks cool...So you've filled in the weekend gaps in a temporary table (#T1), then you've joined it to itself to give the start and end dates for each period of sickness, then counted up the periods.I've used something similar before but I've had proper 'start' and 'end' data that I could use as a marker to define the bits of the table I needed to join to itself - I never thought to create a 'grouping_factor' field to do the same job.Do you have to include the weekend days so the DATEADD bit of the grouping_factor field works properly? Also are there any articles on that technique that you know of - I'd like to get my head around it a bit more.Cheers,Yonabout |
|
|
malpashaa
Constraint Violating Yak Guru
264 Posts |
Posted - 2011-10-26 : 12:46:21
|
quote: Originally posted by yonabout Do you have to include the weekend days so the DATEADD bit of the grouping_factor field works properly?
Yes.quote: Originally posted by yonabout Also are there any articles on that technique that you know of - I'd like to get my head around it a bit more.
I have read about this technique from the book "Inside Microsoft SQL Server 2005: T-SQL Querying" By Itzik Ben-Gan, Lubor Kollar, Dejan Sarka. You can google about "Existing and Missing Ranges" and you can find the exact section in the book. |
|
|
yonabout
Posting Yak Master
112 Posts |
Posted - 2011-10-27 : 04:29:39
|
Nice one.Thanks for the help.Cheers,Yonabout |
|
|
|
|
|
|
|