2011-08-20 107 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個工作日剪斷了遞歸部分如上所示。