Как создать SQL-запрос для SQL Server Reporting Services 2008, чтобы вернуть количество случаев больничных дней - PullRequest
0 голосов
/ 05 июня 2010

Как создать SQL-запрос, чтобы получить количество случаев больничных дней.

Где один случай определяется как последовательные дни болезни. Было бы также считаться одним случаем, если выходные выпали в течение этого периода времени или праздник упал в тот период времени.

Примеры того, что считается одним вхождением:

  • Человек болен в понедельник и вторник.
  • Человек болен в пятницу и следующий понедельник.
  • Человек болеет в четверг, в пятницу выходной, а в понедельник он болен.

Для этих примеров будет рассмотрено три случая.

Существует таблица, которая содержит больничные дни (дата) (по одной строке на каждый больничный день) и таблицу, в которой указаны наблюдаемые даты отпуска.

Чтобы упростить таблицы и поля:

tbl_emp
   empid
   empname

tbl_sick
   empid
   sickdate

tbl_holiday
   holiday

Ответы [ 5 ]

1 голос
/ 05 июня 2010

По логике, это должно работать, но, вероятно, это не самое элегантное решение.

После создания небольшой выборки данных я собираю все «подозрительные» дни, включая известные больничные, известные праздничные и выходные дни, которые относятся либо к больному, либо к выходному. Затем я определяю начало и конец каждой группы последовательных дней, а для каждого сотрудника подсчитываю даты начала диапазонов, которые содержат больничный день.

/***** SAMPLE DATA *****/
declare @sick table (
    empid int,
    sick datetime
)

declare @holiday table (
    holiday datetime
)

/* Example 1 */
insert into @sick values (1,'2010/01/04'); /* Mon */
insert into @sick values (1,'2010/01/05'); /* Tue */

/* Example 2 */
insert into @sick values (1,'2010/01/15'); /* Fri */
insert into @sick values (1,'2010/01/18'); /* Mon */

/* Example 3 */
insert into @sick values (1,'2010/01/21'); /* Thu */
insert into @holiday values('2010/01/22'); /* Fri */
insert into @sick values (1,'2010/01/25'); /* Mon */

/* Extra Examples */
insert into @sick values (3,'2010/01/08'); 
insert into @sick values (2,'2010/01/08');
insert into @holiday values ('2010/01/11');

insert into @sick values (3,'2010/01/20');
insert into @sick values (3,'2010/01/21');

/* Extra Holiday */
insert into @holiday values ('2010/02/05');
/***** SAMPLE DATA *****/

/* First a CTE to gather all of the 'suspect' days together
   including known sick days, known holidays and weekends
   that are adjacent to either a sick day or a holiday */
with suspectdays as (

    /* Start with all Sick days */
    select
        empid,
        sick dt,
        'sick' [type]
    from
        @sick

    /* Add all Saturdays following a sick Friday */
    union
    select
        empid,
        DATEADD(day,1,sick) dt,
        'weekend' [type]
    from
        @sick
    where
        (DATEPART(WEEKDAY,sick) + @@DATEFIRST) % 7 = 6

    /* Add all Sundays following a sick Friday */
    union
    select
        empid,
        DATEADD(day,2,sick) dt,
        'weekend' [type]
    from
        @sick
    where
        (DATEPART(WEEKDAY,sick) + @@DATEFIRST) % 7 = 6

    /* Add all Sundays preceding a sick Monday */
    union
    select
        empid,
        DATEADD(day,-1,sick) dt,
        'weekend' [type]
    from
        @sick
    where
        (DATEPART(WEEKDAY,sick) + @@DATEFIRST) % 7 = 2

    /* Add all Saturdays preceding a sick Monday */
    union
    select
        empid,
        DATEADD(day,-2,sick) dt,
        'weekend' [type]
    from
        @sick
    where
        (DATEPART(WEEKDAY,sick) + @@DATEFIRST) % 7 = 2

    /* Add all Holidays */
    union
    select
        empid,
        holiday dt,
        'holiday' [type]
    from
        @holiday,
        (select distinct empid from @sick) as a

    /* Add all Saturdays following a holiday Friday */
    union
    select
        empid,
        DATEADD(day,1,holiday) dt,
        'weekend' [type]
    from
        @holiday,
        (select distinct empid from @sick) as a
    where
        (DATEPART(WEEKDAY,holiday) + @@DATEFIRST) % 7 = 6

    /* Add all Sundays following a holiday Friday */
    union
    select
        empid,
        DATEADD(day,2,holiday) dt,
        'weekend' [type]
    from
        @holiday,
        (select distinct empid from @sick) as a
    where
        (DATEPART(WEEKDAY,holiday) + @@DATEFIRST) % 7 = 6

    /* Add all Sundays preceding a holiday Monday */
    union
    select
        empid,
        DATEADD(day,-1,holiday) dt,
        'weekend' [type]
    from
        @holiday,
        (select distinct empid from @sick) as a
    where
        (DATEPART(WEEKDAY,holiday) + @@DATEFIRST) % 7 = 2

    /* Add all Saturdays preceding a holiday Monday */
    union
    select
        empid,
        DATEADD(day,-2,holiday) dt,
        'weekend' [type]
    from
        @holiday,
        (select distinct empid from @sick) as a
    where
        (DATEPART(WEEKDAY,holiday) + @@DATEFIRST) % 7 = 2

),

/* Now a CTE to identify the start and end of each 
   group of consecutive days for each employee */
suspectranges as (
    select distinct
        sd.empid,
        (   select
                max(dt)
            from
                suspectdays
            where
                empid = sd.empid and
                DATEADD(day,-1,dt) not in (select dt from suspectdays where empid = sd.empid) and
                dt <= sd.dt
        ) rangeStart,
        (   select
                min(dt)
            from
                suspectdays
            where
                empid = sd.empid and
                DATEADD(day,1,dt) not in (select dt from suspectdays where empid = sd.empid) and
                dt >= sd.dt
        ) rangeEnd
    from
        suspectdays sd
)

/* For each employee count the start dates of ranges that contain a sick day */
select
    empid,
    COUNT(rangeStart) SickIncidents
from
    suspectranges sr
where
    exists (select * from suspectdays where dt between sr.rangeStart and sr.rangeEnd and empid=sr.empid and type='sick')
group by
    empid

Для примера данных, которые я создал, вот результат.

empid       SickIncidents
----------- -------------
1           3
2           1
3           2
0 голосов
/ 09 мая 2011

Только что наткнулся на этот старый вопрос и подумал, что смогу предложить элегантное решение.

Предполагается, что в вашей базе данных есть служебная функция, подобная

create function dbo.Fn_Number (@Start int, @N int)
returns @Number table
(
    N   int not null primary key
)
as
begin
    declare @i int
    set @i = @Start
    while @i <= @N
    begin
        insert into @Number values (@i)
        set @i = @i + 1
    end

    return
end
go

И, используя пример данных JC, вы можете выполнить следующее

/* We need to consider the following range */
declare @From datetime
declare @To datetime

select @From = min(sick), @To = max(sick)
  from @sick

/* Ignoring holidays and Saturday & Sunday create a table of workdays 
   for the given range */
declare @Workdays table (workday datetime not null primary key)

insert into @Workdays
select dateadd(day, n.N, @From) as Workdays
  from dbo.Fn_Number(0, datediff(day, @From, @To) - 1) n
  where -- ignore Saturday and Sunday 
    datepart(weekday, dateadd(day, n.N, @From)) not in (1, 7) and 
    -- ignore holidays
    dateadd(day, n.N, @From) not in (select holiday from @holiday)

Суть решения здесь. Он говорит, что с помощью empid можно считать количество дней болезни, но игнорировать любые дни, когда предыдущий рабочий день был также днем ​​болезни (потому что мы не хотим считать это дважды)

select empid, count(*) as sick_events
  from @sick s
  where 
    not exists
    ( -- make sure there wasn't a sick day prior to this one for this employee
      select *
        from @sick sa
        where sa.empid = s.empid and sa.sick < s.sick and
          not exists
          (  -- and if there was ensure that there wasn't an intervening workday
            select *
              from @Workdays w
              where w.workday < s.Sick and w.workday > sa.Sick
           )
     )
  group by empid
0 голосов
/ 05 июня 2010

Во-первых, самый простой способ провести анализ по датам - это создать таблицу календаря, представляющую собой последовательный список дат. Преимущество заключается в том, что с его помощью можно легко указать, какие дни не являются рабочими (по какой-либо причине), а какие являются выходными.

Create Table dbo.Calendar   (
                            [Date] Date not null primary key clustered
                            , IsHoliday bit not null default(0)
                            , IsWorkday bit not null default(1)
                            , Constraint CK_Calendar Check ( Case 
                                                                When IsHoliday = 1 And IsWorkDay <> 1 Then 0
                                                                Else 1
                                                                End = 1 )
                            )

Единственное дополнительное ограничение, которое я здесь сделал, заключается в том, что если что-то является выходным, то оно также должно быть помечено как не рабочий день. Теперь давайте заполним нашу календарную таблицу:

;With Numbers As
    (
    Select Row_Number() Over( Order By C1.object_id ) As Value
    From sys.columns As C1
        Cross Join sys.columns As C2
    )
    , CalendarItems As
    (
    Select N.Value, DateAdd(d, N.Value, '2000-01-01')As [Date]
    From Numbers As N
    Where DateAdd(d, N.Value, '2000-01-01') <= '2100-01-01'
    )
Insert Calendar( [Date], IsWorkDay )
Select [Date], Case When DatePart(dw, [Date]) In(1,7) Then 0 Else 1 End As IsWorkDay
From CalendarItems

Я использую CTE для генерации последовательного списка целых чисел, который затем я могу использовать для заполнения своей таблицы. Часто полезно, чтобы эта таблица была статичной. Кроме того, я отметил дни, которые в воскресенье или субботу, не как рабочие дни. В приведенном выше запросе я произвольно заполнил свою календарную таблицу датами с 2000 по 2100 год, однако вы можете легко расширить диапазон, если хотите.

Мы даже можем обновить таблицу для учета вашей tbl_holiday таблицы:

Update Calendar
Set IsHoliday = 1
From Calendar   
    Join tbl_Holiday
        On tbl_Holiday.holiday = Calendar.[Date]

Наконец, у нас есть запрос, чтобы получить количество вхождений:

;With WorkDayNums As
    (
    Select C1.[Date]
        , Row_Number() Over ( Order By C1.[Date] ) As Seq
    From dbo.Calendar As C1
    Where C1.IsWorkDay = 1
    )
Select S.empid, Count(*) As SickOccurances
From WorkDayNums As WDN
    Join    (
            Select Min(S1.sickdate) MinSickDate, Max(S1.sickdate) As MaxSickDate
            From tbl_sick As S1
            ) As MinMax
        On WDN.[Date] Between MinMax.MinSickDate And MinMax.MaxSickDate
    Join tbl_sick As S
        On S.sickdate = WDN.[Date]
Where Exists    (
                Select 1
                From WorkDayNums As WDN2
                    Join tbl_sick As S2
                        On S2.sickdate= WDN2.[Date]
                Where WDN2.Seq = WDN.Seq + 1
                )
Group By S.empid

Что я сделал здесь, так это создал последовательность для каждого рабочего дня. Так что если в пятницу 1, то в понедельник будет 2, за исключением любых праздников. С этим я могу легко видеть, был ли следующий рабочий день больным днем.

Вы не указали в своем ОП о том, считаются ли более двух последовательных дней как один или несколько случаев. При таком подходе каждая двухдневная комбинация считается одним случаем. Поэтому, если кто-то находится в понедельник, вторник и среду, приведенный выше запрос должен учитываться как два вхождения.

0 голосов
/ 05 июня 2010

Вот моя попытка. Довольно много этого запроса поддается предварительному расчету, вместо того, чтобы делать все это каждый раз. Особенно с таблицей рабочих дней с возрастающим порядковым номером.

--Base Tables
WITH tbl_holiday AS
(
SELECT CAST(2010-05-27 AS DATETIME) AS holidate UNION ALL
SELECT CAST(2010-05-28 AS DATETIME)
),
tbl_emp AS
(
SELECT 1 AS empid, 'Bob' AS empname UNION ALL
SELECT 2 AS empid, 'Dave' AS empname
),
tbl_sick AS
(
SELECT 1 AS empid, '2010-04-01' as sickdate UNION ALL
SELECT 1, '2010-04-02' UNION ALL
SELECT 1, '2010-04-09' 
),

--Calculated Tables
tbl_WorkingDays AS
(
SELECT dateadd(day,number,'2010-01-01') AS workdate
FROM master.dbo.spt_values 
WHERE Type='P' AND number <= DATEDIFF(day,'2010-01-01',getdate())
AND (@@datefirst + datepart(weekday, dateadd(day,number,'2010-01-01'))) % 7 not in (0, 1)
EXCEPT
SELECT * FROM tbl_holiday
),
tbl_NumberedWorkingDays AS
(
SELECT ROW_NUMBER() OVER (ORDER BY workdate) AS N,
workdate
FROM tbl_WorkingDays 
)



SELECT e.empid, e.empname, COUNT(nwd.N) AS Absences
FROM tbl_emp e
LEFT JOIN tbl_sick s ON e.empid = s.empid
LEFT JOIN tbl_NumberedWorkingDays nwd ON nwd.workdate = s.SickDate
WHERE (s.empid IS NULL) OR (nwd.N = 1) OR 
NOT EXISTS (SELECT * FROM tbl_sick s2 WHERE s.empid = s2.empid AND sickdate = (SELECT 
                   workdate FROM tbl_NumberedWorkingDays WHERE N = nwd.N-1))
GROUP BY e.empid, e.empname
0 голосов
/ 05 июня 2010

Если бы у меня было немного больше времени, я бы попытался получить для вас запрос. Но это действительно напоминает мне о вызове sql, достаточно близком к вашей проблеме.

Различные подходы

Объяснение подходов

Вероятно, вам придется использовать форму рекурсии или хитрый метод соединения, чтобы выяснить, какую последовательность дней вы предписываете. Надеюсь, это поможет вам в поиске запроса.

...