【问题标题】:How can I exclude weekends and holidays in SQL Server query如何在 SQL Server 查询中排除周末和节假日
【发布时间】:2016-03-15 20:31:14
【问题描述】:

我正在创建一个查询,它将调整不包括节假日和周末的日期。

示例数据:

Adjusted Date | Adjusted Date(Excluding Holidays and weekends)

02/06/16 | 02/09/16

在我的示例中,日期是周末,调整后的日期变为 2 月 9 日,因为 2 月 8 日是假期,因此需要调整以使调整后的日期为工作日。目前,我有一个财政年度中所有周末和节假日的单独表格。

select  case when (
               select   count(dbo.WeekendsHoliday.[Weekends & Holidays])
               from     dbo.WeekendsHoliday
               where    dbo.WeekendsHoliday.[Weekends & Holidays] 
                    = case when convert(time, [Time Received]) > convert(time, '5:00:00 PM') 
                           then dateadd(day, 1, [Date Received]) 
                           else [Date Received] 
                      end
              ) > 0 
         then case (datename(DW, 
                             case when convert(time, [Time Received]) > convert(time, '5:00:00 PM') 
                                  then dateadd(day, 1, [Date Received])
                                  else [Date Received] 
                             end))
                when 'Saturday'
                then dateadd(day, 2, 
                             case when convert(time, [Time Received]) > convert(time, '5:00:00 PM') 
                                  then dateadd(day, 1, [Date Received])
                                  else [Date Received] 
                             end)
                else dateadd(day, 1, 
                             case when convert(time, [Time Received]) > convert(time, '5:00:00 PM') 
                                  then dateadd(day, 1, [Date Received])
                                  else [Date Received] 
                             end)
              end
    end as [Adjusted Date Excluding holidays and weekends]

这里发生的情况是,如果假期是连续 2 天(星期四和星期五),则调整后的日期将是星期六,但由于是周末,因此仍然无效。

调整后的日期在这里是一个别名

【问题讨论】:

  • 你有周末假期餐桌吗?
  • 是的,SELECT count([dbo].[WeekendsHoliday].[Weekends & Holidays] 周末和假期是字段名称..
  • 你的代码格式能好一点吗?
  • CASE WHEN (SELECT count([dbo].[WeekendsHoliday].[Weekends & Holidays]) FROM [dbo].[WeekendsHoliday] WHERE [dbo].[WeekendsHoliday].[Weekends & Holidays] = AdjustedDate > 0 THEN CASE (DATENAME(DW, AdjustedDate)) WHEN 'Saturday' THEN DATEADD(DAY, 2, AdjustedDate) ELSE DATEADD(DAY, 1, AdjustedDate) END END AS [Adjusted Date Excluding holidays and weekends]
  • 不作为评论.. 只是编辑问题:),你能告诉我这张桌子的样子吗?

标签: sql-server date


【解决方案1】:

我建议创建一个函数,根据包含周末和节假日的表递归验证下一个工作日。这种方法的优点是它是一个可在您需要时重复使用的函数。

这个函数接收日期和时间。 (根据您问题中的代码)如果时间在下午 5 点之后,则增加一天。之后,继续检查日期是否不在周末或节假日,直到找到下一个工作日:

CREATE FUNCTION dbo.adjustedDate(@dateReceived DATETIME, @timeReceived TIME)
RETURNS DATETIME
AS
BEGIN
    DECLARE @adjustedDate DATETIME = @dateReceived

    -- Verify time to add 1 day to @adjustedDate
    IF @timeReceived IS NOT NULL
        IF @timeReceived > CONVERT(TIME, '5:00:00 PM')
             SET @adjustedDate = DATEADD(DAY, 1, @adjustedDate)

    -- Continue adding 1 day to @adjustedDate recursively until find one date that is not a weekend or holiday
    IF EXISTS(SELECT [Weekends & Holidays]
                FROM dbo.WeekendsHoliday
                WHERE [Weekends & Holidays] = @adjustedDate)                
        SET @adjustedDate = dbo.adjustedDate(DATEADD(DAY, 1, @adjustedDate), NULL)

    RETURN @adjustedDate
END

【讨论】:

  • 你好,> 5:00PM 规则适用于我的另一个字段,即调整后的日期。调整日期(不包括节假日和周末)的规则不同。如果调整的日期是 2016 年 2 月 6 日是星期六,那么调整的日期(不包括节假日和周末)应该是 2016 年 2 月 9 日,因为 2016 年 2 月 8 日是节假日,它移动了 2 次,即使 2 月 8 日是星期一,它也会忽略那个,因为它是假期
  • 时间部分可以省略。但即便如此,该函数的目的是:给定一个日期,找到下一个可用的工作日,跳过包含在表中的周末和节假日。我用你提到的2月(6、7、8节假日)的情况测试了函数,结果如下:INSERT INTO dbo.WeekendsHoliday VALUES ('2016-02-06'), ('2016-02-07'), ('2016-02-08') GO SELECT dbo.adjustedDate('2016-02-06', NULL) -- RETURNS 2016-02-09 GO SELECT dbo.adjustedDate('2016-02-07', NULL) -- RETURNS 2016-02-09 GO SELECT dbo.adjustedDate('2016-02-08', NULL) -- RETURNS 2016-02-09
【解决方案2】:

这会获取 WeekendsHoliday 表中的日期,并找到不在同一个表中的第二天。

如果您的表中的日期在 WeekendsHoliday 表中,那么您离开连接到结果以获得“下一天”

DECLARE @WeekendsHoliday TABLE ([Weekends & Holidays] DATETIME)
INSERT INTO @WeekendsHoliday VALUES
('2016-03-05'),
('2016-03-06'),
('2016-03-07'),
('2016-03-12'),
('2016-03-13');

DECLARE @Schedule TABLE ([WorkDay] DATETIME)
INSERT INTO @Schedule VALUES
('2016-03-02'),
('2016-03-03'),
('2016-03-05'),
('2016-03-07'),
('2016-03-08'),
('2016-03-11'),
('2016-03-12');     

WITH RecursiveCTE AS
(
    SELECT
        [Weekends & Holidays],
        DATEADD(d, 1, [Weekends & Holidays]) AS [Next Day]
    FROM
        @WeekendsHoliday
    UNION ALL   
    SELECT 
        cte.[Weekends & Holidays],
        DATEADD(d, 1, [Next Day])
    FROM 
        RecursiveCTE cte
    WHERE 
        [Next Day] IN (SELECT [Weekends & Holidays] FROM @WeekendsHoliday)
),
AggregateCTE AS (
    SELECT 
        [Weekends & Holidays], 
        MAX([Next Day]) [Next Day] 
    FROM 
        RecursiveCTE 
    GROUP BY 
        [Weekends & Holidays]
)
SELECT  
    s.WorkDay,
    COALESCE(cte.[Next Day], s.WorkDay) AS [Adjusted Date Excluding holidays and weekends]
FROM 
    @Schedule s
    LEFT JOIN AggregateCTE cte ON s.[WorkDay] = cte.[Weekends & Holidays]

如果您只想查看已调整的日期,请使用 INNER JOIN AggregateCTE 而不是 LEFT JOIN AggregateCTE。如果可能,我还建议按开始日期和结束日期范围过滤WITH RecursiveCTE cte。

【讨论】:

  • 我会说比使用函数更有效。
【解决方案3】:

SQL Fiddle Demo

SELECT MIN(allDays.dte)
FROM (
      SELECT '2015-01-01' + INTERVAL ones.a + 10*tens.a + 100*hundred.a DAY dte
      FROM
       (SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) ones,
       (SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) tens,
       (SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) hundred
      WHERE '2015-01-01' + INTERVAL ones.a + 10*tens.a + 100*hundred.a DAY  <  '2016-01-01'
     ) allDays
LEFT JOIN holidays H
   ON allDays.dte = H.holydate
WHERE 
     H.holydate IS NULL
 AND allDays.dte >= '2015-12-12'  -- HERE go your Source DATE

输出

| MIN(allDays.dte) |
|------------------|
|       2015-12-14 |  --Because 12 and 13 are holidays

解释

首先,您需要为 allDays 创建一个列表。这里我生成一个子查询来列出来自2015 的所有日期,你需要适应大范围。

SELECT '2015-01-01' + INTERVAL ones.a + 10*tens.a + 100*hundred.a DAY dte
      FROM
       (SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) ones,
       (SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) tens,
       (SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ) hundred
      WHERE '2015-01-01' + INTERVAL ones.a + 10*tens.a + 100*hundred.a DAY  <  '2016-01-01'

然后您执行left join 以查看 DATE 是否为假期

LEFT JOIN holidays H
   ON allDays.dte = H.holydate

最后只选择不放假的小日期

WHERE H.holydate IS NULL           -- NULL mean doesnt have a match in the holiday table
  AND allDays.dte >= '2015-12-12'  -- HERE go your Source DATE

【讨论】:

  • 您还可以将allDays 保存在单独的表中以简化查询。 100 年只有 36,500 行,所以不需要太多空间或时间
【解决方案4】:

我创建了非常简单的代码,它将排除节假日和周六周日,同时在您的日期中添加天数。首先,您必须为此创建假期表,然后您可以使用以下代码。我希望你能用我的代码得到你的结果,因为我已经创建了它并且它工作正常。

    ;WITH Numbers AS
(
    SELECT 1 AS value
    UNION ALL
    SELECT value + 1 AS value
    FROM Numbers
    WHERE Numbers.value <= 99
)
SELECT FinalTable.FromDate,FinalTable.AddedDays,FinalTable.AdjustedDate 
FROM (
    SELECT Final.*,ROW_Number() OVER (ORDER BY (SELECT NULL)) AS AddedDays 
    FROM (
        SELECT tbl.FromDate,CASE WHEN DATENAME(dw,tbl.AdjustedDate) = 'Saturday' THEN 0 
                                 WHEN DATENAME(dw,tbl.AdjustedDate) = 'Sunday' THEN 0
                                 WHEN tbl.AdjustedDate in (SELECT Holiday_Date FROM Holiday) THEN 0
                            ELSE 1 END AS LogicNumber ,tbl.days, tbl.AdjustedDate 
        FROM (
            SELECT @FromDate AS FromDate, DATEADD(DAY,num,@FromDate) AS AdjustedDate, num AS days 
            FROM ( 
                  SELECT ROW_Number() OVER (ORDER BY (SELECT NULL)) AS num  FROM Numbers
                 ) t
            WHERE num <= 100
             ) tbl 
        )Final 
        WHERE LogicNumber = 1 
    )FinalTable 
    WHERE AddedDays = @days

让我们说@FromDate = '2017-12-30' 2018 年 1 月 1 日是假期表中的假期,我们必须添加 10 天,即@days = 10

输出

FromDate    | AddedDays | AdjustedDate
2017-12-30  | 10        | 2018-01-15

【讨论】:

    猜你喜欢
    • 2010-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-02
    • 2019-02-16
    • 1970-01-01
    相关资源
    最近更新 更多