2010-06-04 59 views
0

如何创建一个sql查询来返回发病日数。如何为sql报告服务2008创建一个sql查询来返回病假发生次数

其中一个事件被定义为连续的日子病倒。如果周末在该时间段内下降或假期在该时间段内下降,则也可以认为是一种情况。

  • 一人请病假周一和周二:什么是考虑一个发生

    例子。

  • 一个人在星期五和下星期一生病了。
  • 一个人生病了星期四,星期五是假期,星期一他们生病了。

对于这些例子,它将被视为三次出现。

有一张表包含病假日期(日期)(每病假一行)和包含观察到的假期日期的表格。

为了简化表和字段:

tbl_emp 
    empid 
    empname 

tbl_sick 
    empid 
    sickdate 

tbl_holiday 
    holiday 
+0

这是在不知道必要的数据库表的模式的情况下难以回答!如果您还没有桌子,您需要先询问如何存储这些信息。如果你有表格,更新你的文章以包含它们。 – 2010-06-04 20:30:05

+0

为了简化计算 - tbl_emp - EMPID,empname, tbl_sick - EMPID,sickdate, tbl_holiday - holidate 请让我知道如果你需要更多的信息。谢谢 – Bill 2010-06-04 20:36:58

+0

我会撤回所有数据,并在Java中执行,因为编写长SQL会伤害我的头;) – bwawok 2010-06-05 02:47:34

回答

0

如果我有更多一点时间,我会试图让查询出来给你。但是这真的让我想起了一个足够接近你的问题的sql挑战。

Different Approaches

Explanation of Approaches

你可能不得不结束了递归或一个棘手的连接方法的形式计算出,如果天的顺序是你开了什么。希望这可以帮助您查询查询。

1

从逻辑上说,这应该可行,但它可能不是最优雅的解决方案。

在创建了一些样本数据之后,我收集了所有'可疑'日,包括已知病假,已知假日和周末,与病假或假期相邻。然后,我确定每组连续日的开始和结束日期,并且为每个员工计算包含病假的范围的开始日期。

/***** 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

这是我的尝试。相当多的这个查询可以预先计算,而不是每次都做。特别是具有增加序号的工作日表格。

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

首先,对日期进行分析的最简单的方法是有一个日历表,它是一个日期顺序列表。优点是可以很容易地指出哪些日子不是工作日(无论什么原因)以及哪些日子是假日。

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] 

,我们有我们的查询来获取occurrances数量:

;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除外任何假期。有了这个,我可以很容易地看到下一个工作日是否病假。

您没有在OP中明确说明是否有多于两个连续的天数被计为单次发生或多次发生。在这种方法中,每两天的组合将被视为单一事件。所以如果有人在星期一,星期二和星期三,上​​面的查询应该算作两次。

0

刚刚遇到这个老问题,我想我可能会提供一个优雅的解决方案。

假设你已经在你的数据库类似于一个效用函数

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