Sql ut所有英国假日,并填写表格,包括2029年之前的复活节日期,无需使用这些确切的指南)

Sql ut所有英国假日,并填写表格,包括2029年之前的复活节日期,无需使用这些确切的指南),sql,sql-server-2005,Sql,Sql Server 2005,请注意,我的表格包含工作时间内的SLA(正常工作日为8小时,正常工作周为5天),您的工作时间可能会有所不同,但您可以轻松地进行修改,只需确保您的工作时间在SLA表格和下面的功能中设置为相同即可 下面的代码是T-SQL(在SSMS v17.8.1中编写) 如果你希望得到一个有效的答案,你需要给出一些输入和预期结果的例子。你还需要回答一些问题,比如:每个营业日营业时间都一样吗?你认为假日是什么样的?但是我认为你可以不用一个循环或者一个日期和营业时间表来完成这项工作。你能提到一个示例吗?是的,你可以不

请注意,我的表格包含工作时间内的SLA(正常工作日为8小时,正常工作周为5天),您的工作时间可能会有所不同,但您可以轻松地进行修改,只需确保您的工作时间在SLA表格和下面的功能中设置为相同即可

下面的代码是T-SQL(在SSMS v17.8.1中编写)


如果你希望得到一个有效的答案,你需要给出一些输入和预期结果的例子。你还需要回答一些问题,比如:每个营业日营业时间都一样吗?你认为假日是什么样的?但是我认为你可以不用一个循环或者一个日期和营业时间表来完成这项工作。你能提到一个示例吗?是的,你可以不用一个循环来完成这项工作,只需要一个其他的示例来编写你想要的代码。这看起来可能是重复的:假期不是这样工作的。其中一些是的,但每年都有变化(即感恩节=11月的第四个星期四,如果第四个星期六或星期日分别是星期六或星期日,则7月4日可以在第三或第五天庆祝)@srutzky最后,这取决于他来自哪个国家,而我定义的表格在某种程度上可以灵活地定义他希望独立于一年之外的任何假日,我查看了日历,选择了一些
CREATE TABLE [dbo].[tblDay](
    [dt] [datetime] NOT NULL,
    [dayOfWk] [int] NULL,
    [dayOfWkInMo] [int] NULL,
    [isWeekend] [bit] NOT NULL,
    [holidayID] [int] NULL,
    [workingDayCount] [int] NULL,
 CONSTRAINT [PK_tblDay] PRIMARY KEY CLUSTERED 
(
    [dt] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE PROCEDURE [dbo].[usp_tblDay]
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE  
        @Dt datetime ,
        @wkInMo int,
        @firstDwOfMo int,
        @holID int,
        @workDayCount int,
        @weekday int,
        @month int,
        @day int,
        @isWkEnd bit

    set @workDayCount = 0
    SET @Dt = CONVERT( datetime, '2008-01-01' ) 
    while @dt < '2020-01-01'
    begin
        delete from tblDay where dt = @dt

        set @weekday = datepart( weekday, @Dt )
        set @month = datepart(month,@dt)
        set @day = datepart(day,@dt)

        if @day = 1  -- 1st of mo
            begin
                set @wkInMo = 1
                set @firstDwOfMo = @weekday
            end

        if ((@weekday = 7) or (@weekday = 1)) 
            set @isWkEnd = 1 
        else 
            set @isWkEnd = 0

        if @isWkEnd = 0 and (@month = 1 and @day = 1) 
            set @holID=1        -- new years on workday
        else if @weekday= 6 and (@month = 12 and @day = 31) 
            set @holID=1        -- holiday on sat, change to fri
        else if @weekday= 2 and (@month = 1 and @day = 2) 
            set @holID=1        -- holiday on sun, change to mon

        else if @wkInMo = 3 and @weekday= 2 and @month = 1 
            set @holID = 2      -- mlk

        else if @wkInMo = 3 and @weekday= 2 and @month = 2 
            set @holID = 3      -- President’s

        else if @wkInMo = 4 and @weekday= 2 and @month = 5 and datepart(month,@dt+7) = 6
            set @holID = 4      -- memorial on 4th mon, no 5th
        else if @wkInMo = 5 and @weekday= 2 and @month = 5 
            set @holID = 4      -- memorial on 5th mon

        else if @isWkEnd = 0 and (@month = 7 and @day = 4) 
            set @holID=5        -- July 4 on workday
        else if @weekday= 6 and (@month = 7 and @day = 3) 
            set @holID=5        -- holiday on sat, change to fri
        else if @weekday= 2 and (@month = 7 and @day = 5) 
            set @holID=5        -- holiday on sun, change to mon

        else if @wkInMo = 1 and @weekday= 2 and @month = 9 
            set @holID = 6      -- Labor

        else if @isWkEnd = 0 and (@month = 11 and @day = 11) 
            set @holID=7        -- Vets day on workday
        else if @weekday= 6 and (@month = 11 and @day = 10) 
            set @holID=7        -- holiday on sat, change to fri
        else if @weekday= 2 and (@month = 11 and @day = 12) 
            set @holID=7        -- holiday on sun, change to mon

        else if @wkInMo = 4 and @weekday= 5 and @month = 11 
            set @holID = 8      -- thx

        else if @holID = 8
            set @holID = 9      -- dy after thx

        else if @isWkEnd = 0 and (@month = 12 and @day = 25) 
            set @holID=10       -- xmas day on workday
        else if @weekday= 6 and (@month = 12 and @day = 24) 
            set @holID=10       -- holiday on sat, change to fri
        else if @weekday= 2 and (@month = 12 and @day = 26) 
            set @holID=10       -- holiday on sun, change to mon
        else
            set @holID = null

        insert into tblDay select @dt,@weekday,@wkInMo,@isWkEnd,@holID,@workDayCount

        if @isWkEnd=0 and @holID is null 
            set @workDayCount = @workDayCount + 1

        set @dt = @dt + 1
        if datepart( weekday, @Dt ) = @firstDwOfMo 
            set @wkInMo = @wkInMo + 1
    end
END
holidayID   holiday rule description
1   New Year's Day  Jan. 1
2   Martin Luther King Day  third Mon. in Jan.
3   Presidents' Day third Mon. in Feb.
4   Memorial Day    last Mon. in May
5   Independence Day    4-Jul
6   Labor Day   first Mon. in Sept
7   Veterans' Day   Nov. 11
8   Thanksgiving    fourth Thurs. in Nov.
9   Fri after Thanksgiving  Friday after Thanksgiving
10  Christmas Day   Dec. 25

--set up our source data
declare @business_hours table
(
    work_day    varchar(10),
    open_time   varchar(8), 
    close_time  varchar(8)
)
insert into @business_hours values ('Monday',   '08:30:00', '17:00:00')
insert into @business_hours values ('Tuesday',  '08:30:00', '17:00:00')
insert into @business_hours values ('Wednesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Thursday', '08:30:00', '17:00:00')
insert into @business_hours values ('Friday',   '08:30:00', '18:00:00')
insert into @business_hours values ('Saturday', '09:00:00', '14:00:00')

declare @holidays table ( holiday varchar(10) ) insert into @holidays values ('2015-01-01') insert into @holidays values ('2015-01-02')

--Im going to assume the SLA of 2 standard business days (0900-1700) = 8*60*2 = 960 declare @start_date datetime = '2014-12-31 16:12:47' declare @time_span int = 960-- time till due in minutes

declare @true bit = 'true' declare @false bit = 'false'

declare @due_date datetime --our output

--other variables declare @date_string varchar(10) declare @today_closing datetime declare @is_workday bit = @true declare @is_holiday bit = @false

--Given our timespan is in minutes, lets also assume we dont care about seconds in start or due dates set @start_date = DATEADD(ss,datepart(ss,@start_date)*-1,@start_date)

while (@time_span > 0) begin

set @due_date       = DATEADD(MINUTE,@time_span,@start_date)
set @date_string    = FORMAT(DATEADD(dd, 0, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @today_closing  = (select convert(datetime,@date_string + ' ' + close_time) from @business_hours where work_day = DATENAME(weekday,@start_date))

if exists((select work_day from @business_hours where work_day = DATENAME(weekday,@start_date))) 
    set @is_workday = @true 
else 
    set @is_workday = @false

if exists(select holiday from @holidays where holiday = @date_string)
    set @is_holiday = @true
else 
    set @is_holiday = @false

if  @is_workday = @true and @is_holiday = @false
begin
    if @due_date > @today_closing 
        set @time_span = @time_span - datediff(MINUTE, @start_date, @today_closing)
    else 
        set @time_span = @time_span - datediff(minute, @start_date, @due_date)
end

set @date_string = FORMAT(DATEADD(dd, 1, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @start_date = CONVERT(datetime, @date_string + ' ' + isnull((select open_time from @business_hours where work_day = DATENAME(weekday,convert(datetime,@date_string))),''))
CREATE TABLE #WorkSchedule(WorkStart datetime not null primary key, WorkEnd datetime not null);
GO
CREATE TABLE #Tally (N int not null primary key);
GO

--POPULATE TEST DATA
--populate Tally table
insert into #Tally (N)
select top 10000 N = row_number() over(order by o.object_id)
from sys.objects o cross apply sys.objects o2
;
go

--POPULATE WITH DUMMY TEST DATA
INSERT INTO #WorkSchedule(WorkStart, WorkEnd)
SELECT 
     workStart = dateadd(hour, 8, t.workDate) 
    , workEnd = dateadd(hour, 17, t.workDate) 
FROM (
    SELECT top 10000 workDate = dateadd(day, row_number() over(order by o.object_id), '2000-01-01')
    FROM sys.objects o cross apply sys.objects o2
) t 
--Exclude weekends from work schedule
WHERE datename(weekday, t.workDate) not in ('Saturday','Sunday')
;

GO
SET NOCOUNT ON;
DECLARE @startDate datetime;
DECLARE @SLA_timespan_mins int;

DECLARE @workStartDayOne datetime; 
DECLARE @SLA_Adjusted int;
DECLARE @dueDate datetime;

--SET PARAM VALUES HERE FOR TESTING TO ANY DATE/SLA TIMESPAN YOU WANT: 
SET @startDate = '2014-01-04 05:00'; --Saturday
SET @SLA_timespan_mins = 10 * 60 ; --10 hrs.

--get the info day 1, since your start date might be after the work start time.
select top 1 @workStartDayOne = s.WorkStart 
    --increase the SLA timespan mins to account for difference between work start and start time
    , @SLA_Adjusted = case when @startDate > s.WorkStart then datediff(minute, s.WorkStart, @startDate) else 0 end + @SLA_timespan_mins
from #WorkSchedule s
where s.WorkEnd > @startDate
    and s.WorkStart <> s.WorkEnd
order by s.WorkStart asc
;

--DEBUG info:
select 'Debug Info' as DebugInfo, @startDate AS StartDate, @workStartDayOne as workStartDayOne, @SLA_timespan_mins as SLA_timespan_mins, @SLA_Adjusted as SLA_Adjusted;

--now sum all the non work hours during that period and determine the additional mins that need added.
;with cteWorkMins as
(
    SELECT TOP (@SLA_Adjusted)
          s.WorkStart, s.WorkEnd
        , WorkMinute = dateadd(minute, t.N, cast(s.WorkStart as datetime))
        , t.N as MinuteOfWorkDay
        , RowNum = row_number() over(order by s.WorkStart, t.N)
    FROM #WorkSchedule s
        INNER JOIN #Tally t 
            ON t.N between 1 and datediff(minute, s.WorkStart, s.WorkEnd)
    WHERE s.WorkStart >= @workStartDayOne
    ORDER BY s.WorkStart, t.N
)
/**/
SELECT @dueDate = m.WorkMinute 
FROM cteWorkMins m 
WHERE m.RowNum = @SLA_Adjusted 
--*/
/**
--DEBUG: this query will show every minute that is accounted for during the Due Date calculation.
SELECT m.*
FROM cteWorkMins m 
--WHERE m.RowNum = @SLA_Adjusted 
ORDER BY m.WorkMinute
--*/
;

select @dueDate as DueDate;
GO
IF object_id('TEMPDB..#WorkSchedule') IS NOT NULL
DROP TABLE #WorkSchedule;
GO
IF object_id('TEMPDB..#Tally') IS NOT NULL
DROP TABLE #Tally;
declare @start datetime,
        @min int,
        @days int

set @start= '28 Dec 2014'
set @min = 2200 

-- get the number of days
set @days=datediff(day,@start,dateadd(minute,@min,@start))

-- get the due date
select max(Date)
from
    (select row_number() over( order by t.Id)-1 as Id,t.Date 
     from DateMetadata t
     inner join BusinessDays b on Day(t.Date) = b.Day
     where t.Date > = @start and not exists(select 1 from Holidays h 
                      where h.Day=Day(t.Date)
                      and h.Month=Month(t.Date))) as p
where p.Id < @days
create table Holidays(Id int identity(1,1),
                      Name nvarchar(50),
                      Day int,
                      Month int)
create table BusinessDays(Id int identity(1,1),
                           Name nvarchar(20),
                           Day int)

-- i am putting some days that are known, 
--  it depends on you to define which holidays you want
insert into Holidays (Name,Day,Month) values('Christmas',25,12)
insert into Holidays(Name,Day,Month) values('New Year',31,12)
insert into Holidays(Name,Day,Month) values('Valentine',14,2)
insert into Holidays(Name,Day,Month) values('Mothers day',21,3)
insert into Holidays(Name,Day,Month) values('April fools day',1,4)

-- i am assuming that the business days are from monday till friday and 
--  saturday and sunday are off days
insert into BusinessDays(Name,Day) values ('Monday',1)
insert into BusinessDays(Name,Day) values('Tuesday',2)
insert into BusinessDays(Name,Day) values('Wednesday',3)
insert into BusinessDays(Name,Day) values('Thursday',4)
insert into BusinessDays(Name,Day) values('Friday',5)
-- set up a table that contains all dates from now till 2050 for example
-- and you can change the value of 2050 depending on your needs
-- this table you will setup it once
create table DateMetadata(Id int identity(1,1),
                          Date datetime)

declare @date datetime
set @date='01 Jan 2014'
while @date < '31 Dec 2050'
begin
   insert into DateMetadata(Date) values(@date)
   set @date = @date + 1
end
CREATE TABLE [dbo].[holiday](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [region] [nvarchar](10) NULL,
    [Hdate] [date] NULL,
)
declare @start datetime= getdate()
declare @slamins int =960 --- SLA time in mins
declare @Country varchar(2)='NA'
declare @start_hour int = 8 -- business start hour
declare @end_hour int = 17 -- business end hour
declare @true bit = 'true'
declare @false bit = 'false'
declare @is_workday bit = @true
declare @is_holiday bit = @false
declare @due_date datetime
declare @today_closing datetime
declare @temp int = 0
declare  @holidays table (HDate DateTime)   --  Table variable to hold holidayes

---- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
 Insert Into @Holidays (HDate) Select date from HOLIDAY Where region=@Country and Hdate>=DateAdd(dd, DateDiff(dd,0,@start), 0)

--check for weekends
set @start =  case(datepart(dw,@start)+@@datefirst-1)%7 
      when 0 then Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
      when 6 then Convert(dateTime,CONVERT(varchar,@start+2,101)+' 08:00:00')
      else @start end 

-- check if start time is before business hour
if datepart(hh, @start) < @start_hour set @start = Convert(dateTime,CONVERT(varchar,@start,101)+' 08:00:00')

-- check if start time is after business hour
if datepart(hh, @start) >= @end_hour set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')

-- loop start
while (@slamins > 0)
begin
 -- prepared closing date time based on start date
 set @today_closing = Convert(dateTime,CONVERT(varchar,@start,101)+' 17:00:00')
 set @due_date      = @start
 -- calculate number of Minute between start date and closing date
 set @temp = DATEDIFF(N, @start , @today_closing);

 --check for weekends
if (DATEPART(dw, @start)!=1 AND DATEPART(dw, @start)!=7) 
    set @is_workday = @true 
else 
    set @is_workday = @false
--check for holidays
if (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@start), 0)) = 0
    set @is_holiday =@false 
else 
    set @is_holiday = @true
if  @is_workday = @true and @is_holiday = @false
begin

    if(@temp  <  @slamins)
    begin
        set @slamins = @slamins - @temp
    end
else 
    begin
        set @due_date = DATEADD(MINUTE,@slamins,@start)
        set @slamins = 0
        print @due_date
    end
end
set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
end

select @due_date
CREATE FUNCTION [JIRA].[Fn_JIRA_Due_Date] (
    @CreatedDate DATETIME, @SLA_Business_Hours INTEGER
) RETURNS DATETIME
AS
-- SELECT [JIRA].[Fn_JIRA_Due_Date]('2019-12-28 08:00:00', 24)

/* 

baldmosher™
2019-03-25

* Function returns the DueDate for a JIRA ticket, based on the CreatedDate and the SLA (based on the Issue Type, or the Epic for Tasks) and business hours per date (set in [JIRA].[Ref_JIRA_Business_Hours])
* Called by IUP to store this at the time the ticket is loaded
* Can only consider SLA in Business Hours:
    * <24hrs calendar = <8hrs business
    * =24hrs calendar =  8hrs business
    * >24hrs calendar =  8hrs business * business days

*/


BEGIN

    IF @CreatedDate IS NULL OR @SLA_Business_Hours IS NULL RETURN NULL;

    DECLARE @SLA_Hours_Remaining SMALLINT = @SLA_Business_Hours;

    --SET DATEFIRST 1;
    DECLARE @DueDate DATETIME;
    DECLARE @BusHrsStart DECIMAL(18,10) = 8                         ; -- start of Business Hours (8am)
    DECLARE @BusHrsClose DECIMAL(18,10) = 16                        ; -- close of Business Hours (4pm)
    --DECLARE @WkndStart DECIMAL(18,10) = 6                         ; -- start of weekend (Sat)
    DECLARE @Hours_Today SMALLINT                                   ; -- # hours left in day to process ticket

    -- PRINT 'Created ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))

    --!!!! extend to the next whole hour just to simplify reporting -- need to work on fixing this eventually
    SET @CreatedDate = DATEADD(MINUTE,60-DATEPART(MINUTE,@CreatedDate),@CreatedDate)
    -- PRINT 'Rounded ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))


--check if created outside business hours and adjust CreatedDate to start the clock first thing at the next business hours start of day (days are checked next)
    IF DATEPART(HOUR,@CreatedDate) < @BusHrsStart 
    --created before normal hours, adjust @CreatedDate later to @BusHrsStart same day
        BEGIN
        SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate AS DATE) AS DATETIME))
        END

    IF DATEPART(HOUR,@CreatedDate) >= @BusHrsClose
    --created after normal hours, adjust @CreatedDate to @BusHrsStart next day
        BEGIN
        SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate+1 AS DATE) AS DATETIME))
        --adjust CreatedDate to start the clock the next day with >0 business hours (i.e. extend if it falls on a weekend or holiday)
        SET @CreatedDate = CAST(@CreatedDate AS DATE)
        StartNextWorkingDay:
        IF (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = @CreatedDate ORDER BY [Date]) = 0 
            BEGIN 
            SET @CreatedDate = DATEADD(DAY,1,@CreatedDate)
            GOTO StartNextWorkingDay
            END
            --DATEADD(DAY, DATEDIFF(DAY,0,@CreatedDate+7)/7*7,0);  -- midnight, Monday next week
        SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,@CreatedDate);  -- BusHrsStart
        END
    -- PRINT 'Started ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))

--third, check the business hours for each date from CreatedDate onwards to determine the relevant DueDate
    SET @DueDate = @CreatedDate
    -- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
    SET @Hours_Today = @BusHrsStart + (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date]) - DATEPART(HOUR, @CreatedDate)
    -- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
    DueNextWorkingDay:
    IF @SLA_Hours_Remaining > @Hours_Today
        BEGIN
        -- PRINT 'Due another day'
        SET @SLA_Hours_Remaining = @SLA_Hours_Remaining - @Hours_Today            --adjust remaining time after today's hours
        SET @Hours_Today = (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date])
        -- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
        -- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
        SET @DueDate = DATEADD(DAY,1,DATEADD(HOUR,@BusHrsStart,CAST(CAST(@DueDate AS DATE) AS DATETIME)))              --adjust DueDate to first thing next day
        END
    IF @SLA_Hours_Remaining <= @Hours_Today
        BEGIN
        -- PRINT 'Due today'
        SET @DueDate = DATEADD(HOUR,@SLA_Hours_Remaining,@DueDate)
        END
    ELSE
        BEGIN
        GOTO DueNextWorkingDay
        END

    -- PRINT 'DueDate ' + CAST(CAST(@DueDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@DueDate AS TIME) AS VARCHAR(8))

    RETURN @DueDate

END

GO
CREATE TABLE [JIRA].[Ref_JIRA_SLAs](
    [SLA_SK] [SMALLINT] IDENTITY(1,1) NOT NULL,
    [Project] [VARCHAR](20) NULL,
    [Issue_Type] [VARCHAR](50) NULL,
    [Epic_Name] [VARCHAR](50) NULL,
    [SLA_Business_Hours] [SMALLINT] NULL,
    [Comments] [VARCHAR](8000) NULL
) ON [PRIMARY] WITH (DATA_COMPRESSION = PAGE)
GO