【问题标题】:Count Consecutive vacation days skip through holidays and weekends计数连续假期跳过假期和周末
【发布时间】:2017-06-03 04:14:55
【问题描述】:

我有一张记录用户休假日的表格。 一个示例是:

+---------+------------+---------+------------+ |国家 |用户名 |用户 ID | vac_date | +---------+------------+---------+------------+ |加拿大 |詹姆斯 | 1111 | 2015-02-13 | |加拿大 |詹姆斯 | 1111 | 2015-02-17 | |加拿大 |詹姆斯 | 1111 | 2015-02-18 | |加拿大 |詹姆斯 | 1111 | 2015-02-10 | |加拿大 |詹姆斯 | 1111 | 2015-02-11 | +---------+------------+---------+------------+

根据上述数据,从 2 月 13 日到 2 月 18 日,计数为 3,因为 14 日和 15 日是周末,而 16 日是加拿大的假期。所以本质上,如果用户在下一个工作日休假,我会尝试保留并继续计数。我还有一张包含所有假期的表格,其中包括国家和假期日期。假日表的示例数据为:

+---------+-------------+-------------+ |国家 |假日Desc |假期日期 | +---------+-------------+-------------+ |加拿大 |家庭日 | 2015-02-16 | +---------+-------------+-------------+

目前我在 SQL 中有一个查询通常计算日期,所以它只计算假期表中的任何内容。例如:如果用户在 2015 年 3 月 3 日、2015 年 3 月 4 日和 2015 年 3 月 5 日休假,则计数为 3,但对于上表示例,2 月 13 日和 2 日的计数仅为 1 2月17日至2月18日。

选择不同的用户名 ,min(vac_date) 作为 startDate ,max(vac_date) 作为 endDate ,datediff(day, min(vac_date), max(vac_date)) 作为连续计数 从 ( 选择用户名 ,vac_date ,用户身份 ,groupDate = DATEADD(DAY, - ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY vac_date ), vac_date) 来自我的表 WHERE 国家 = '加拿大' AND vac_date BETWEEN '20150101' 和“20151231” ) z 按用户名分组 ,组日期 有日期差异(天,分钟(vac_date),最大值(vac_date))> = 0 按用户名订购 ,min(vac_date);

这是它当前从上述示例数据中输出的内容:

+-----------+------------+------------+----------- --------+ |用户名 |开始日期 |结束日期 |连续计数 | +-----------+------------+------------+----------- --------+ |詹姆斯 | 2015-02-10 | 2015-02-11 | 2 | |詹姆斯 | 2015-02-13 | 2015-02-13 | 1 | |詹姆斯 | 2015-02-17 | 2015-02-18 | 2 | +-----------+------------+------------+----------- --------+

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

+-----------+------------+------------+----------- --------+ |用户名 |开始日期 |结束日期 |连续计数 | +-----------+------------+------------+----------- --------+ |詹姆斯 | 2015-02-10 | 2015-02-11 | 2 | |詹姆斯 | 2015-02-13 | 2015-02-18 | 3 | +-----------+------------+------------+----------- --------+

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

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

【问题讨论】:

  • 这是一个很好的起点。 spaghettidba.com/2015/04/24/…
  • 对不起格式,我修好了表格。如果还有其他事情,请告诉我。
  • 假期表呢? :D
  • 我还添加了一个假期表样本。如果还有其他事情,请告诉我。
  • 我不清楚。您要计算二月第二周(02/8 至 02/14)或之后一周(02/15 至 02/21)的连续工作日吗?或者整个二月?

标签: c# sql sql-server


【解决方案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  

那么最后的查询和约翰建议的一样,但是这次你只计算假期。

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

输出

原始输出

这里可以看到group by之前的数据

【讨论】:

  • @JohnCappelletti 我刚刚发现 grp 有一个错误,2015-02-282015-03-01 出现在不同的 grp 上。你知道为什么吗?
  • @JuanCarlosOropeza 非常感谢,明天我会更彻底地测试它。很遗憾我不能同时接受你和约翰的回答。
【解决方案2】:

这似乎是一个带有一点转折的经典间隙和岛屿。

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

【讨论】:

  • 您好,Jhon,我认为非常接近,但除了假期之外,您还需要以某种方式包括周末。还有starDate和endDate,不应该一样
  • @JuanCarlosOropeza 谢谢。我忽略了那部分 - 将进行更新。
  • 我认为如果将周末插入到假期表中会更容易。
  • 我可以动态选择某一年的周末日期,我是否只需添加另一个 union all 语句并具有相同的条件?非常感谢您的帮助。
  • @RonnieHuang 当然,假设周一到周五上班,但我很难理解周末如何计入连续天数。它们是 1 还是 0?
【解决方案3】:

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

就个人而言,我会在 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;
}

【讨论】:

  • 如果可能的话,我想用 SQL 来做,如果问题太大,我会试试你的解决方案。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 1970-01-01
  • 1970-01-01
  • 2020-10-18
相关资源
最近更新 更多