2011-08-20 104 views
1

我想根据月份的星期和星期几使用以下函数来安排每月发生的俱乐部会议。在下面的例子中,我有一个(将)函数返回该月的第三个星期三。如果那一天发生在过去,那么它将返回下个月的第三个星期三。SQL第一周的第N个月的第N天

我想摆脱循环,我觉得有一个更好的计算方法。还有更多面向对象的过程吗?你的意见?

--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

签出以下帖子,它看起来可以帮助你:[计算一个月中的第N个工作日](http://blogs.lessthandot.com/index.php/DataMgmt/DBProgramming/calculating-nth-weekday-in一个月) – jdavies

+0

可能的重复[使用T-SQL获取下个月的第一个星期天](http://stackoverflow.com/questions/1506036/get-first-sunday-of-next-month-using-t- sql) – Hogan

+0

jdavies - 伟大的链接。我会在笔记本电脑上添加您的和猫的解决方案。 – Pete

回答

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编写的,但除了与ISO年份和ISO周有关的列外,它完全是标准SQL(我认为)。

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)编写的,所以我不确定它会对你有多大帮助。但如果你愿意,我会在后期发布。

+0

一个理货表。这是辉煌而快速的。谢谢。 – Pete

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 
0

这是一个使用日期数学返回下一工作日第N次上或在指定日期之后的另一个功能基础的解决方案。它没有使用任何循环来说明,但是如果下一个第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)[email protected]@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当月的未来第N个工作日剪断了递归部分如上所示。