SQL 填充每月总工作日减去当前财政年度的银行假期

Posted

技术标签:

【中文标题】SQL 填充每月总工作日减去当前财政年度的银行假期【英文标题】:SQL populate total working days per month minus bank holidays for current financial year 【发布时间】:2012-02-07 16:48:08 【问题描述】:

我正在寻找一个看起来像我的第一张附加图片的视图,但是右侧的列已填充且不为空白。逻辑如下:

数据必须是当前财政期间的数据。因此 4 月将是 2011 年,3 月将是 2012 年,依此类推。

单月的可用天数计算如下:

工作日总数(周一至周五)减去属于该特定月份的任何银行假期,对于该特定财政年度(我们已保存在表格中 - 请参见第二张图片)。

假日表的列名从左到右:holidaytypeidnameholstartholend。 表名:holidaytable

要计算出累积月份“可用天数”,需要对单个月份的已填充数据求和。例如 April-May 将是 April 和 May 的数据 SUMMED 等等。

我需要完美格式的 SQL 查询,以便可以直接粘贴并正常工作(即使用正确的列名和表名)

感谢收看。

【问题讨论】:

你有一个dates 表,一个包含每个日期列表的表吗?如果没有,那么我建议你买一个。 不,我没有这样的表,无法在这个数据库上创建表 【参考方案1】:
DECLARE @StartDate DATETIME, @EndDate DATETIME

SELECT  @StartDate = '01/04/2011',
        @EndDate = '31/03/2012'
        
CREATE TABLE #Data (FirstDay DATETIME NOT NULL PRIMARY KEY, WorkingDays INT NOT NULL)

;WITH DaysCTE ([Date]) AS
(   SELECT  @StartDate
    UNION ALL
    SELECT  DATEADD(DAY, 1, [Date])
    FROM    DaysCTE
    WHERE   [Date] <= @Enddate
)

INSERT INTO #Data
SELECT  MIN([Date]),
        COUNT(*) [Day]
FROM    DaysCTE
        LEFT JOIN HolidayTable
            ON [Date] BETWEEN HolStart AND HolEnd
WHERE   HolidayTypeID IS NULL
AND     DATENAME(WEEKDAY, [Date]) NOT IN ('Saturday', 'Sunday')
GROUP BY DATEPART(MONTH, [Date]), DATEPART(YEAR, [Date])
OPTION (MAXRECURSION 366)

DECLARE @Date DATETIME
SET @Date = (SELECT MIN(FirstDay) FROM #Data)

SELECT  Period,
        WorkingDays [Days Available (Minus the Holidays)]
FROM    (   SELECT  DATENAME(MONTH, Firstday) [Period],
                    WorkingDays,
                    0 [SortField],
                    FirstDay
            FROM    #Data
            UNION
            SELECT  DATENAME(MONTH, @Date) + ' - ' + DATENAME(MONTH, Firstday),
                    (   SELECT  SUM(WorkingDays)
                        FROM    #Data b
                        WHERE   b.FirstDay <= a.FirstDay
                    ) [WorkingDays],
                    1 [SortField],
                    FirstDay 
            FROM    #Data a
            WHERE   FirstDay > @Date
        ) data
ORDER BY SortField, FirstDay

DROP TABLE #Data

如果您这样做超过 1 年,您将需要更改线路:

OPTION (MAXRECURSION 366)

否则你会得到一个错误 - 数字必须大于你查询的天数。


编辑

我刚刚遇到了我的这个旧答案,真的不喜欢它,有很多事情我现在认为是不好的做法,所以我要纠正所有问题:

    I did not terminate statements with a semi colon properly 使用递归 CTE 生成日期列表 Generate a set or sequence without loops – part 1 Generate a set or sequence without loops – part 2 Generate a set or sequence without loops – part 3 Did not include the column list for an insert 使用 DATENAME 来消除周末,这是特定于语言的,最好明确设置 DATEFIRST 并使用 DATEPART 使用LEFT JOIN/IS NULL 而不是NOT EXISTS 来消除假期表中的记录。 In SQL Server LEFT JOIN/IS NULL is less efficient than NOT EXISTS

这些都是小事,但它们是我在审查别人的问题时会批评的事情(至少在我的脑海里,如果不是大声说出来的话),所以我真的不能不纠正我自己的工作!重写查询会给出。

SET DATEFIRST 1;

DECLARE @StartDate DATETIME = '20110401',
        @EndDate DATETIME = '20120331';

CREATE TABLE #Data (FirstDay DATETIME NOT NULL PRIMARY KEY, WorkingDays INT NOT NULL);

WITH DaysCTE ([Date]) AS
(   SELECT  TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
            DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, @StartDate)
    FROM    sys.all_objects a
)
INSERT INTO #Data (FirstDay, WorkingDays)
SELECT  FirstDay =  MIN([Date]),
        WorkingDays = COUNT(*) 
FROM    DaysCTE d
WHERE   DATEPART(WEEKDAY, [Date]) NOT IN (6, 7)
AND     NOT EXISTS
        (   SELECT  1
            FROM    dbo.HolidayTable ht
            WHERE   d.[Date] BETWEEN ht.HolStart AND ht.HolEnd
        )
GROUP BY DATEPART(MONTH, [Date]), DATEPART(YEAR, [Date]);

DECLARE @Date DATETIME = (SELECT MIN(FirstDay) FROM #Data);

SELECT  Period,
        [Days Available (Minus the Holidays)] = WorkingDays 
FROM    (   SELECT  DATENAME(MONTH, Firstday) [Period],
                    WorkingDays,
                    0 [SortField],
                    FirstDay
            FROM    #Data
            UNION
            SELECT  DATENAME(MONTH, @Date) + ' - ' + DATENAME(MONTH, Firstday),
                    (   SELECT  SUM(WorkingDays)
                        FROM    #Data b
                        WHERE   b.FirstDay <= a.FirstDay
                    ) [WorkingDays],
                    1 [SortField],
                    FirstDay 
            FROM    #Data a
            WHERE   FirstDay > @Date
        ) data
ORDER BY SortField, FirstDay;

DROP TABLE #Data;

最后一点,使用 calendar table 存储所有日期并具有工作日、假期等标志,而不是使用仅存储假期的假期表,此查询变得更加简单。

【讨论】:

【参考方案2】:

让我在这篇文章中加几分钱。刚刚获得计算计划时间和实际时间之间差异的任务。下面的代码被转换为一个函数。到目前为止,逻辑没有问题:

declare @date datetime = '11/07/2012'
declare @t table (HolidayID int IDENTITY(1,1) primary key,
    HolidayYear int,
    HolidayName varchar(50),
    HolidayDate datetime)

INSERT @t
VALUES(2012, 'New Years Day', '01/02/2012'),
(2012,'Martin Luther King Day', '01/16/2012'),
(2012,'Presidents Day', '02/20/2012'),
(2012,'Memorial Day', '05/28/2012'),
(2012,'Independence Day', '07/04/2012'),
(2012,'Labor Day', '09/03/2012'),
(2012,'Thanksgiving Day', '11/22/2012'),
(2012,'Day After Thanksgiving', '11/23/2012'),
(2012,'Christmas Eve', '12/24/2012'),
(2012,'Christmas Day', '12/25/2012'),
(2013, 'New Years Day', '01/01/2013'),
(2013,'Martin Luther King Day', '01/21/2013'),
(2013,'Presidents Day', '02/18/2013'),
(2013,'Good Friday', '03/29/2013'),
(2013,'Memorial Day', '05/27/2013'),
(2013,'Independence Day', '07/04/2013'),
(2013,'Day After Independence Day', '07/05/2013'),
(2013,'Labor Day', '09/02/2013'),
(2013,'Thanksgiving Day', '11/28/2013'),
(2013,'Day After Thanksgiving', '11/29/2013'),
(2013,'Christmas Eve', NULL),
(2013,'Christmas Day', '12/25/2013')


DECLARE @START_DATE DATETIME, 
    @END_DATE DATETIME, 
    @Days int

SELECT  @START_DATE = DATEADD(MONTH, DATEDIFF(MONTH, 0, @date), 0) 

SELECT  @END_DATE = DATEADD(month, 1,@START_DATE)

;WITH CTE AS
(
    SELECT DATEADD(DAY, number, (DATEADD(MONTH, DATEDIFF(MONTH, 0, @date), 0) )) CDate
    FROM master.dbo.spt_values where type = 'p' and number between 0 and 365
        EXCEPT
    SELECT HolidayDate FROM @t WHERE HolidayYear = YEAR(@START_DATE) 
)
SELECT @Days = COUNT(CDate) --, datepart(dw, CDate) WDay
FROM CTE 
WHERE (CDate >=@START_DATE and CDate < @END_DATE) AND DATEPART(dw, CDate) NOT IN(1,7)

SELECT @Days 

【讨论】:

以上是关于SQL 填充每月总工作日减去当前财政年度的银行假期的主要内容,如果未能解决你的问题,请参考以下文章

Where 子句 (T-SQL) 中的财政年度迄今为止

sql 获取每月当前的年度总销售额

仅适用于当前财政年度日期的动态 SQL

MDX 从 2 年前开始销售到今天

动态计算截至该日期的财政年度结束和周数

sql server 2008 r2:当前会计年度 where 子句中的 case 语句