我想使用以下功能来安排基于每月的周和周的月度发生的俱乐部会议。在下面的示例中,我有一个(将)函数返回该月的第三个星期三。如果那一天发生在过去,那么它将返回下个月的第3个星期三。
我想摆脱循环,我觉得有更好的计算方法。是否有更多的OO流程?你的意见?
--CREATE FUNCTION NextWeekDayofMonth
DECLARE
--(
@WEEK INT,
@WEEKDAY INT,
@REFERENCEDATE DATETIME
--)
--RETURNS DATETIME
--AS
-------------------------------
--Values for testing - Third Wednesday of the Month
set @WEEK = 3 --Third Week
set @WEEKDAY = 4 --Wednesday
set @REFERENCEDATE = '08/20/2011'
-------------------------------
BEGIN
DECLARE @WEEKSEARCH INT
DECLARE @FDOM DATETIME
DECLARE @RETURNDATE DATETIME
SET @FDOM = DATEADD(M,DATEDIFF(M,0,@REFERENCEDATE),0)
SET @RETURNDATE = DATEADD(M,0,@FDOM)
WHILE (@RETURNDATE < @REFERENCEDATE)
--If the calculated date occurs in the past then it
--finds the appropriate date in the next month
BEGIN
SET @WEEKSEARCH = 1
SET @RETURNDATE = @FDOM
--Finds the first weekday of the month that matches the provided weekday value
WHILE ( DATEPART(DW,@RETURNDATE) <> @WEEKDAY)
BEGIN
SET @RETURNDATE = DATEADD(D,1,@RETURNDATE)
END
--Iterates through the weeks without going into next month
WHILE @WEEKSEARCH < @WEEK
BEGIN
IF MONTH(DATEADD(WK,1,@RETURNDATE)) = MONTH(@FDOM)
BEGIN
SET @RETURNDATE = DATEADD(WK,1,@RETURNDATE)
SET @WEEKSEARCH = @WEEKSEARCH+1
END
ELSE
BREAK
END
SET @FDOM = DATEADD(M,1,@FDOM)
END
--RETURN @RETURNDATE
select @ReturnDate
END
答案 0 :(得分:2)
IMO,最好的流程是将重要的业务信息存储为数据库中表中的行。如果您构建日历表,则可以通过简单查询获取所有第三个星期三。查询不仅简单,而且可以看出它们显然是正确的。
select cal_date
from calendar
where day_of_week_ordinal = 3
and day_of_week = 'Wed';
今天或之后的第三个星期三也很简单。
select min(cal_date)
from calendar
where day_of_week_ordinal = 3
and day_of_week = 'Wed'
and cal_date >= CURRENT_DATE;
创建日历表非常简单。这是为PostgreSQL编写的,但它完全是标准的SQL(我认为),除了与ISO年份和ISO周相关的列。
create table calendar (
cal_date date primary key,
year_of_date integer not null
check (year_of_date = extract(year from cal_date)),
month_of_year integer not null
check (month_of_year = extract(month from cal_date)),
day_of_month integer not null
check (day_of_month = extract(day from cal_date)),
day_of_week char(3) not null
check (day_of_week =
case when extract(dow from cal_date) = 0 then 'Sun'
when extract(dow from cal_date) = 1 then 'Mon'
when extract(dow from cal_date) = 2 then 'Tue'
when extract(dow from cal_date) = 3 then 'Wed'
when extract(dow from cal_date) = 4 then 'Thu'
when extract(dow from cal_date) = 5 then 'Fri'
when extract(dow from cal_date) = 6 then 'Sat'
end),
day_of_week_ordinal integer not null
check (day_of_week_ordinal =
case
when day_of_month >= 1 and day_of_month <= 7 then 1
when day_of_month >= 8 and day_of_month <= 14 then 2
when day_of_month >= 15 and day_of_month <= 21 then 3
when day_of_month >= 22 and day_of_month <= 28 then 4
else 5
end),
iso_year integer not null
check (iso_year = extract(isoyear from cal_date)),
iso_week integer not null
check (iso_week = extract(week from cal_date))
);
您可以使用电子表格或UDF填充该表格。电子表格通常具有相当不错的日期和时间功能。我有一个UDF,但它是为PostgreSQL(PL / PGSQL)编写的,所以我不确定它对你有多大帮助。但如果你愿意,我会稍后发布。
答案 1 :(得分:0)
这是一种日期数学方法,可以在不循环的情况下完成您想要的任务:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Description: Gets the nth occurrence of a given weekday in the month containing the specified date.
-- For @dayOfWeek, 1 = Sunday, 2 = Monday, 3 = Tuesday, 4 = Wednesday, 5 = Thursday, 6 = Friday, 7 = Saturday
-- =============================================
CREATE FUNCTION GetWeekdayInMonth
(
@date datetime,
@dayOfWeek int,
@nthWeekdayInMonth int
)
RETURNS datetime
AS
BEGIN
DECLARE @beginMonth datetime
DECLARE @offSet int
DECLARE @firstWeekdayOfMonth datetime
DECLARE @result datetime
SET @beginMonth = DATEADD(DAY, -DATEPART(DAY, @date) + 1, @date)
SET @offSet = @dayOfWeek - DATEPART(dw, @beginMonth)
IF (@offSet < 0)
BEGIN
SET @firstWeekdayOfMonth = DATEADD(d, 7 + @offSet, @beginMonth)
END
ELSE
BEGIN
SET @firstWeekdayOfMonth = DATEADD(d, @offSet, @beginMonth)
END
SET @result = DATEADD(WEEK, @nthWeekdayInMonth - 1, @firstWeekdayOfMonth)
IF (NOT(MONTH(@beginMonth) = MONTH(@result)))
BEGIN
SET @result = NULL
END
RETURN @result
END
GO
DECLARE @nextMeetingDate datetime
SET @nextMeetingDate = dbo.GetWeekdayInMonth(GETDATE(), 4, 3)
IF (@nextMeetingDate IS NULL OR @nextMeetingDate < GETDATE())
BEGIN
SET @nextMeetingDate = dbo.GetWeekDayInMonth(DATEADD(MONTH, 1, GETDATE()), 4, 3)
END
SELECT @nextMeetingDate
答案 2 :(得分:0)
这是另一个基于函数的解决方案,它使用日期数学,在给定日期或之后返回下一个工作日。它没有使用任何循环来说明,但如果下一个第N个工作日在下个月,它最多可以重复一次迭代。
此功能将DATEFIRST设置考虑在使用非默认值的环境中。
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: David Grimberg
-- Create date: 2015-06-18
-- Description: Gets the next Nth weekday
-- @param Date is any date in a month
-- @param DayOfWeek is the weekday of interest ranging
-- from 1 to 7 with @@DATEFIRST being the
-- first day of the week
-- @param NthWeekday represents which ordinal weekday to return.
-- Positive values return dates relative to the start
-- of the month. Negative values return dates relative
-- to the end of the month. Values > 4 indicate the
-- last week, values < -4 indicate the first week.
-- Zero is assumed to be 1.
-- =============================================
ALTER FUNCTION dbo.xxGetNextNthWeekday
(
@Date date,
@NthWeekday smallint,
@DayOfWeek tinyint
)
RETURNS date
AS
BEGIN
DECLARE @FirstOfMonth date
DECLARE @inc int
DECLARE @Result date
-- Clamp the @NthWeekday input to valid values
set @NthWeekday = case when @NthWeekday = 0 then 1
when @NthWeekday > 4 then -1
when @NthWeekday < -4 then 1
else @NthWeekday
end
-- Normalize the requested day of week taking
-- @@DATEFIRST into consideration.
set @DayOfWeek = (@@DATEFIRST + 6 + @DayOfWeek) % 7 + 1
-- Gets the first of the current month or the
-- next month if @NthWeekday is negative.
set @FirstOfMonth = dateadd(month, datediff(month,0,@Date)
+ case when @NthWeekday < 0 then 1 else 0 end
, 0)
-- Add and/or subtract 1 week depending direction of search and the
-- relationship of @FirstOfMonth's Day of the Week to the @DayOfWeek
-- of interest
set @inc = case when (datepart(WEEKDAY, @FirstOfMonth)+@@DATEFIRST-1)%7+1 > @DayOfWeek
then 0
else -1
end
+ case when @NthWeekday < 0 then 1 else 0 end
-- Put it all together
set @Result = dateadd( day
, @DayOfWeek-1
, dateadd( WEEK
, @NthWeekday + @inc
, dateadd( WEEK -- Gets 1st Sunday on or
, datediff(WEEK, -1, @FirstOfMonth)
,-1 ) ) ) -- before @FirstOfMonth
-- [Snip here] --
if @Result < @Date
set @Result = dbo.xxGetNextNthWeekday( dateadd(month, datediff(month, 0, @Date)+1, 0)
, @NthWeekday, @DayOfWeek)
-- [to here for no recursion] --
Return @Result
END
如果您希望基于@Date参数而不是下一个工作日的当前月份的过去或将来的第N个工作日,则会删除上面提到的递归部分。