SQL N-й день N-й недели месяца - PullRequest
       10

SQL N-й день N-й недели месяца

1 голос
/ 20 августа 2011

Я хочу использовать следующую функцию для планирования собраний клубов, которые проводятся ежемесячно на основе недели и дня недели месяца. В приведенном ниже примере у меня есть (быть) функция, которая возвращает третью среду месяца. Если этот день произошел в прошлом, он возвращает третью среду следующего месяца.

Я хочу уйти от петель и чувствую, что есть лучший метод для расчета. Есть ли более ОО процесс? Ваше мнение?

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

Ответы [ 3 ]

2 голосов
/ 20 августа 2011

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, но он написан для PostgreSQL (PL / PGSQL), поэтому я не уверен, насколько он вам поможет. Но я выложу позже, если хотите.

0 голосов
/ 08 июля 2015

Вот еще одно решение на основе функций с использованием математики даты, которое возвращает следующий 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)+@@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

Если вы хотите, чтобы прошедший или будущий N-й день недели текущего месяца основывался на параметре @Date, а не на следующий N-й день недели, вырежьте рекурсивную часть, как указано выше.

0 голосов
/ 09 июля 2012

Вот математический способ дат, чтобы выполнить то, что вы хотите без циклов:

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 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...