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.

 All Forums
 SQL Server 2000 Forums
 Transact-SQL (2000)
 Bradford Factor

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 things

A 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 table
create table #absencetype
(absencetypeid int,
absencetype varchar(20)
)

insert #absencetype
select 1, 'None' union all
select 2, 'Holiday' union all
select 3, 'Sickness'

--table that tells us (among other things) what date users were absent on
create table #flexrecord (userid int,flexdate datetime,absencetypeid int)

insert #flexrecord
select 1, '10/10/2011', 1 union all
select 1, '11/10/2011', 3 union all
select 1, '12/10/2011', 1 union all
select 1, '13/10/2011', 1 union all
select 1, '14/10/2011', 3 union all
select 1, '17/10/2011', 3 union all
select 1, '18/10/2011', 1 union all
select 1, '19/10/2011', 1 union all
select 1, '20/10/2011', 1 union all
select 1, '21/10/2011', 1 union all
select 1, '24/10/2011', 1 union all
select 1, '25/10/2011', 3 union all
select 1, '26/10/2011', 3 union all
select 1, '27/10/2011', 3 union all
select 1, '28/10/2011', 1

--working patterns table
create table #FlexHours (userid int, DayOfWeek int, FlexHours datetime)
insert #FlexHours
select 1, 1, '01/01/1900 08:00:00' union all
select 1, 2, '01/01/1900 08:00:00' union all
select 1, 3, '01/01/1900 00:00:00' union all
select 1, 4, '01/01/1900 08:00:00' union all
select 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:

select
userid,
count(userid)
from #flexrecord
where absencetypeid = 3
group by userid


I can also not count any days that they aren't contracted to work with something like this:
select
fr.userid,
count(fr.userid)
from #flexrecord fr
join
(
select * from
#flexhours
where flexhours <> '01/01/1900 00:00:00' -- indicates contracted day off
) fh
on fr.userid = fh.userid and datepart(dw, fr.flexdate) = fh.dayofweek

where fr.absencetypeid = 3
group 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).
Go to Top of Page

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 WorkGrps
AS
(
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'
)
, Absence
AS
(
SELECT userid
,COUNT(DISTINCT grp) AS Periods
,COUNT(userid) AS DaysOff
FROM WorkGrps
WHERE absencetypeid = 3
GROUP BY userid
)
-- select * from Absence
SELECT userid, Periods, DaysOff
,Periods * Periods * DaysOff As BradfordFactor
FROM Absence

Go to Top of Page

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
Go to Top of Page

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?
Go to Top of Page

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
Go to Top of Page

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 = 3

INSERT 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;
Go to Top of Page

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
Go to Top of Page

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.
Go to Top of Page

yonabout
Posting Yak Master

112 Posts

Posted - 2011-10-27 : 04:29:39
Nice one.

Thanks for the help.

Cheers,

Yonabout
Go to Top of Page
   

- Advertisement -