我需要计算SLA的截止日期/结束日期。作为输入值,我有开始日期和时间跨度(以分钟为单位)。此计算需要考虑营业时间,周末和假期。
我看过很多输入是开始日期和结束日期的例子,但是一直在努力寻找与上述输入值类似的东西。
这个问题有优雅的解决方案吗?有没有办法在不使用循环的情况下计算到期日期?如果不做类似以下可怕算法的事情,我想不出一种计算方法:
答案 0 :(得分:4)
您需要一张有效营业时间的表格,不包括周末和假日(或标记为周末/假日,以便您可以跳过它们。)每行代表一天和当天的工作小时数。然后查询从开始日期到第一个(最小)日期的营业时间表,其中总和(小时* 60)大于您的分钟参数,不包括标记的周末/假日行。这会给你结束日期。
这是日历表:
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
HTH
答案 1 :(得分:2)
这是我能做的最好的,仍然使用循环但使用日期函数而不是递增分钟变量。希望你喜欢它。
--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))),''))
end
select @due_date
答案 2 :(得分:1)
以下是使用WorkSchedule
表的选项,其中包含可用于计算SLA的营业时间。要考虑周末和假日,请不要将这些天的记录插入WorkSchedule
表。
此解决方案还使用&#34; Tally&#34;表到期日计算中的数字表。我还包括调试输出以帮助您查看正在发生的事情,因此只需注释掉或取消注释任何调试部分以查看更少/更多信息。
我在此示例中使用了SQL临时表,以便您可以在不弄乱当前数据库架构的情况下运行它,但如果使用此解决方案,则应替换为物理表。
测试数据设置:
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;
GO
答案 3 :(得分:1)
我从你的问题中了解到,你需要的是如下
这是你可以做的事情
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
注意:您将在数据库中设置一次DateMetadata表
上述代码的设置:
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
这是一个有效的DEMO
如果您需要任何解释,我准备好了
希望它会帮助你
答案 4 :(得分:1)
Sql计算到期日,不包括假期和考虑营业时间,如下所示: - &lt;注意: - 它正常工作营业时间(8-5)维护假期表
CREATE TABLE [dbo].[holiday](
[id] [int] IDENTITY(1,1) NOT NULL,
[region] [nvarchar](10) NULL,
[Hdate] [date] NULL,
)
&GT;
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
答案 5 :(得分:0)
我创建了一个函数来计算表格中的到期日,一旦按照贝思和其他人的方法(如您所见,可以使用各种类似的方法填充)来填写到期日,我只花了一个小时就思考了所有英国假期,并在表格中填充包括2029年以前的复活节在内的日期,而无需使用这些确切的指南。
请注意,我的表格在工作时间(正常工作时间为8小时,正常工作周为5天)中包含SLA,您的工作时间可能会有所不同,但是您可以轻松地对此进行修改,只需确保将工作时间设置为相同SLA表和下面的函数。
下面的代码是T-SQL(用SSMS v17.8.1编写)
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
SLA表:
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