计数连续休假日可以跳过假期和周末

时间:2017-01-18 17:02:01

标签: c# sql sql-server

我有一张表,其中包含用户休假日的记录。 一个样本是:

+---------+-----------+---------+------------+
| country | user_name | user_id |  vac_date  |
+---------+-----------+---------+------------+
| canada  | James     |    1111 | 2015-02-13 |
| canada  | James     |    1111 | 2015-02-17 |
| canada  | James     |    1111 | 2015-02-18 |
| canada  | James     |    1111 | 2015-02-10 |
| canada  | James     |    1111 | 2015-02-11 |
+---------+-----------+---------+------------+

根据以上数据,从第13天到第18天,计数将是3,因为第14和第15是周末,第16是加拿大的假期。基本上,如果用户下一个工作日休息,我试图保持并继续计算。我还有一张桌子,里面有所有假期,包括国家和假日日期。假期表的样本数据为:

+---------+-------------+-------------+
| country | holidayDesc | holidayDate |  
+---------+-------------+-------------+
| canada  | Family Day  | 2015-02-16  |  
+---------+-------------+-------------+

目前我在SQL中有一个通常计算日期的查询,因此它只计算假期表中的任何内容。例如:如果用户参加2015年3月3日,2015年4月4日,2015年3月5日关闭,那么它将计数为3,但对于上表示例,它将仅为第13和第2的计数为1从17日至18日。

SELECT DISTINCT user_name
    ,min(vac_date) as startDate
    ,max(vac_date) as endDate
    ,datediff(day, min(vac_date), max(vac_date)) as consecutiveCount
FROM (
    SELECT user_name
        ,vac_date
        ,user_id
        ,groupDate = DATEADD(DAY, - ROW_NUMBER() OVER (
                PARTITION BY user_id ORDER BY vac_date
                ), vac_date)
    FROM mytable
    WHERE country = 'canada'
        AND vac_date BETWEEN '20150101'
            AND '20151231'
    ) z
GROUP BY user_name
    ,groupDate
HAVING datediff(day, min(vac_date), max(vac_date)) >= 0
ORDER BY user_name
    ,min(vac_date);

这是它目前从上述样本数据输出的内容:

+-----------+------------+------------+------------------+
| user_name | startDate  |  endDate   | consecutiveCount |
+-----------+------------+------------+------------------+
| James     | 2015-02-10 | 2015-02-11 |                2 |
| James     | 2015-02-13 | 2015-02-13 |                1 |
| James     | 2015-02-17 | 2015-02-18 |                2 |
+-----------+------------+------------+------------------+

理想情况下,我希望它是:

+-----------+------------+------------+------------------+
| user_name | startDate  |  endDate   | consecutiveCount |
+-----------+------------+------------+------------------+
| James     | 2015-02-10 | 2015-02-11 |                2 |
| James     | 2015-02-13 | 2015-02-18 |                3 |
+-----------+------------+------------+------------------+

但我不知道纯SQL是否可行。我也可以尝试将其合并到C#中。

如果有帮助我也使用C#和SQL Server Management Studio。任何帮助,将不胜感激。提前致谢

3 个答案:

答案 0 :(得分:1)

这看起来像是经典的Gaps&岛屿有点扭曲。

Declare @YourTable table (country varchar(25),user_name varchar(25),user_id varchar(25),vac_date date)
Insert Into @YourTable values
('canada','James','1111','2015-02-13'),
('canada','James','1111','2015-02-17'),
('canada','James','1111','2015-02-18'),
('canada','James','1111','2015-02-10'),
('canada','James','1111','2015-02-11')

Declare @Holiday table (country varchar(25),holidayDate date)
Insert Into @Holiday values
('canada','2015-02-16')

Select user_name
      ,startDate = min(vac_date)
      ,endDate   = max(vac_date)
      ,consecutiveCount = sum(DayCnt)
From  (
        Select *
              ,Grp =  Day(vac_date) - Row_Number() over (Partition By country,user_id Order by vac_date)
         From  (Select Country,user_name,user_id,vac_date,DayCnt=1 from @YourTable
                Union All
                Select A.Country,user_name,user_id,vac_date=b.holidayDate,DayCnt=1
                 From  @YourTable A
                 Join  @Holiday B on A.country=B.country and abs(DateDiff(DD,vac_date,holidayDate))=1
                Union All
                Select A.Country,user_name,user_id,vac_date=b.retval,DayCnt=0
                 From  @YourTable A
                 Join  (
                        Select * From [dbo].[udf-Range-Date]('2015-01-01','2017-12-31','DD',1) where DateName(WEEKDAY,RetVal) in ('Saturday','Sunday')
                       ) B on abs(DateDiff(DD,vac_date,RetVal))=1

               ) S
      ) A
 Group By user_name,Grp
 Having Sum(DayCnt)>1

返回

user_name   startDate   endDate     consecutiveCount
James       2015-02-10  2015-02-11  2
James       2015-02-16  2015-02-18  3

生成动态日期范围的UDF - 可以是您自己的查询

CREATE FUNCTION [dbo].[udf-Range-Date] (@R1 datetime,@R2 datetime,@Part varchar(10),@Incr int)
Returns Table
Return (
    with cte0(M)   As (Select 1+Case @Part When 'YY' then DateDiff(YY,@R1,@R2)/@Incr When 'QQ' then DateDiff(QQ,@R1,@R2)/@Incr When 'MM' then DateDiff(MM,@R1,@R2)/@Incr When 'WK' then DateDiff(WK,@R1,@R2)/@Incr When 'DD' then DateDiff(DD,@R1,@R2)/@Incr When 'HH' then DateDiff(HH,@R1,@R2)/@Incr When 'MI' then DateDiff(MI,@R1,@R2)/@Incr When 'SS' then DateDiff(SS,@R1,@R2)/@Incr End),
         cte1(N)   As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
         cte2(N)   As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
         cte3(N,D) As (Select 0,@R1 Union All Select N,Case @Part When 'YY' then DateAdd(YY, N*@Incr, @R1) When 'QQ' then DateAdd(QQ, N*@Incr, @R1) When 'MM' then DateAdd(MM, N*@Incr, @R1) When 'WK' then DateAdd(WK, N*@Incr, @R1) When 'DD' then DateAdd(DD, N*@Incr, @R1) When 'HH' then DateAdd(HH, N*@Incr, @R1) When 'MI' then DateAdd(MI, N*@Incr, @R1) When 'SS' then DateAdd(SS, N*@Incr, @R1) End From cte2 )

    Select RetSeq = N+1
          ,RetVal = D 
     From  cte3,cte0 
     Where D<=@R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1) 
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1) 
*/

答案 1 :(得分:1)

我尝试采用不同的路线,但随后找到了John Cappelletti解决方案的修复程序。

首先,您需要在holiday表格中添加周末日期。

Get a list of dates between two dates using a function

然后假期UNION ALL休假,但添加说明字段,以便您可以区分两者。

有一些CROSS JOIN因此您可以为每个国家/地区和用户(need testing)

度假和周末
SELECT [country], 
       [user_name], [user_id], [vac_date], 'vacation' as description
FROM vacations
UNION ALL 
SELECT c.[country], 
       u.[user_name],
       u.[user_id],
       [holidayDate], 
       'holiday' as description
FROM holidays     
CROSS JOIN (SELECT DISTINCT [country] FROM vacations) c
CROSS JOIN (SELECT DISTINCT [user_name], [user_id] FROM vacations) u  

然后最终查询与John建议的相同,但这次你只计算休假日。

WITH joinDates as (
    SELECT [country], 
           [user_name], [user_id], [vac_date], 'vacation' as description
    FROM vacations
    UNION ALL 
    SELECT c.[country], 
           u.[user_name],
           u.[user_id],
           [holidayDate], 
           'holiday' as description
    FROM holidays     
    CROSS JOIN (SELECT DISTINCT [country] FROM vacations) c
    CROSS JOIN (SELECT DISTINCT [user_name], [user_id] FROM vacations) u    
)    
Select user_name
      ,startDate = min(vac_date)
      ,endDate   = max(vac_date)
      ,consecutiveCount = count(*)
From  (
        Select *
              ,Grp =  Day(vac_date) - Row_Number() over (Partition By country,user_id 
                                                         Order by vac_date)
         From  joinDates S
      ) A
WHERE description = 'vacation'    -- only count vacation days ignore holiday/weekend   
Group By user_name, Grp
Having count(*)>1
ORDER BY startDate

<强> SQL DEMO

<强>输出

enter image description here

RAW OUTPUT

在这里,您可以通过

查看分组前的数据

enter image description here

答案 2 :(得分:0)

好的,我对这个问题的理解是,你想要做的只是一天的假日。许多企业将此称为“缺席”和#34;按原因区分缺勤。在这种情况下,您尝试将假期视为假期的延续(出于时间目的),如果假期发生在星期五,但该人在周一休假,那应该是一个连续的超时时间。

就个人而言,我在C#中这样做是因为DateTime对象的属性可以使这比使用frankenquery容易得多。下面的代码假定您有一个名为Employee的对象,它包含自己的DateTimes记录,如下所示:

public class Employee
{
     public int ID {get;set;}
     public string Name {get;set;}
     public List<DateTime> DaysIWasOut {get;set;}
}

public static int TimeOut(IEnumerable employees)
{     
       int totalOutInstances = 0;
       DataTable dt = HolidaysPlease(); //this refers to another method
       //to fill the table.  Just a basic SQLAdapter.Fill kind of thing. 
       //Basic so I won't waste time on it here.
       foreach(var e in employees)
       {
         var holidays = dt.AsEnumerable().Where(t => Convert.ToDateTime(t[3]) == d)        //holidays now has all of the holidays the employee had off. 
         totalOutInstances = e.DaysIWasOut.Count();
         foreach(var d in e.DaysIWasOut)
         {
            int daystolook = 0;
            if (d.DayOfWeek == DayOfWeek.Friday)
               daystolook +=3;
            else
               daystolook +=1;
            if(e.DaysIWasOut.Contains(d.AddDays(daystolook))                        
                   {totalOutInstances --; } //don't count that day               
         }

    }
 return totalOutInstances;
}