2016-08-24 2 views
1

У меня есть запрос (ниже), который начнется в определенное время. с этого времени мне нужно увеличивать неделю за раз (каждая новая строка будет новой неделей - с воскресенья до субботы), я сделал это, но новая морщина заключается в том, что если это конец месяца, ей нужно остановитесь в эту дату и начните работу в первый месяц, но все равно остановитесь в эту субботу. набор результатов good/bad приведен ниже, а также мой запрос до этого момента.Sql Date - Breaking End of Month up

Плохие результаты:

2016-05-22 2016-05-28 
2016-05-29 2016-06-04 

Что мне нужно:

2016-05-22 2016-05-28 
2016-05-29 2016-05-31 
2016-06-01 2016-06-04 

Код:

BEGIN 
    DECLARE @StartDate DATE = DATEFROMPARTS(2016, 5, 22); 
    DECLARE @EndDate DATE = CAST(GETDATE() AS DATE); 
    DECLARE @Today DATE = @EndDate; 
    DECLARE @EndOfMonth DATE = @StartDate 

    ; WITH [Dates] AS (
     SELECT 
      @StartDate AS [StartDate], 
      DATEADD(DAY, 6, @StartDate) AS [EndDate] 
     UNION ALL 
     SELECT 
      DATEADD(DAY, 7, [StartDate]), 
      DATEADD(DAY, 7, [EndDate]) 
     FROM [Dates] 
     WHERE DATEADD(DAY, 7, [StartDate]) <= @EndDate 
    ) 
    SELECT 
     [tcw].[StartDate], 
     [tcw].[EndDate] 
    FROM [Dates] AS [tcw] 
    OPTION (MAXRECURSION 0) 
END 
GO 
+0

Какой РСУБД это? Добавьте тег, чтобы указать, используете ли вы 'mysql',' postgresql', 'sql-server',' oracle' или 'db2' - или что-то еще. –

+0

Я вижу много ответов с объединением всех и нескольких cte и т. Д. Я знаю, что мои формулы немного менее просты в использовании, но это единственный рекурсивный cte. cheers – Matt

ответ

1

Учитывая, что вы используете DATEFROMPARTS, я предполагаю, что вы используете SQL 2012 или более поздней версии. Я также полагаюсь на вашу существующую логику в отношении определения первой и последней дат. Исходя из этого, замените ваш запрос следующим:

; WITH [Dates] AS (
    SELECT 
     @StartDate AS [StartDate], 
     DATEADD(DAY, 6, @StartDate) AS [EndDate] 
    UNION ALL 
    SELECT 
     DATEADD(DAY, 7, [StartDate]), 
     DATEADD(DAY, 7, [EndDate]) 
    FROM [Dates] 
    WHERE DATEADD(DAY, 7, [StartDate]) <= @EndDate 
) 
-- All weeks where all dates are within the same month 
SELECT 
    StartDate 
    ,EndDate 
FROM [Dates] 
WHERE MONTH(StartDate) = MONTH(EndDate) 
-- For weeks where all dates not within the same month, the last week in the month 
UNION ALL SELECT 
    StartDate 
    ,EOMONTH(StartDate) 
FROM [Dates] 
WHERE MONTH(StartDate) <> MONTH(EndDate) 
-- For weeks where all dates not within the same month, the first week in the (next) month 
UNION ALL SELECT 
    DATEADD(dd, 1, EOMONTH(StartDate)) 
    ,EndDate 
FROM [Dates] 
WHERE MONTH(StartDate) <> MONTH(EndDate) 

Профсоюзы делают это немного неудобно, но если вы не обрабатывать века с каждым проходить он будет работать достаточно быстро. Отметим также, что некоторые «недели» будут содержать один день, например, на 31 июля 2016 года по 31 июля 2016 года.

- Добавления на основе комментария ------------ ---------------------

следующий запрос делает это, но с большим предупреждением ...

; WITH cteDates AS (
    SELECT 
     @StartDate AS StartDate, 
     DATEADD(DAY, 6, @StartDate) AS EndDate, 
     CASE 
      WHEN DATEPART(dw, EOMONTH(@StartDate)) between 2 and 6 then 0 -- Assumes SET DATEFIRST is 1! 
      ELSE 1 
     END AS MonthEndsOnWeekend 
    UNION ALL 
    SELECT 
     DATEADD(DAY, 7, StartDate), 
     DATEADD(DAY, 7, EndDate), 
     CASE 
      WHEN DATEPART(dw, EOMONTH(DATEADD(DAY, 7, StartDate))) between 2 and 6 then 0 
      ELSE 1 
     END 
    FROM cteDates 
    WHERE DATEADD(DAY, 7, StartDate) <= @EndDate 
) 
-- All weeks where all dates are within the same month, 
-- and all month-ending weeks where the last day of the month is Saturday or Sunday 
SELECT 
    StartDate 
    ,EndDate 
FROM cteDates 
WHERE MONTH(StartDate) = MONTH(EndDate) 
    OR MonthEndsOnWeekend = 1 
-- For weeks where all dates not within the same month, 
-- and the the last day of the month is NOT Saturday or Sunday, 
-- the last week in the month 
UNION ALL SELECT 
    StartDate 
    ,EOMONTH(StartDate) 
FROM cteDates 
WHERE MONTH(StartDate) <> MONTH(EndDate) 
    AND MonthEndsOnWeekend = 0 
-- For weeks where all dates not within the same month, 
-- and the the last day of the month is NOT Saturday or Sunday, 
-- the first week in the (next) month 
UNION ALL SELECT 
    DATEADD(dd, 1, EOMONTH(StartDate)) 
    ,EndDate 
FROM cteDates 
WHERE MONTH(StartDate) <> MONTH(EndDate) 
    AND MonthEndsOnWeekend = 0 

Я использую функцию DATEPART для определения дня недели (суббота, воскресенье и т. д.). SQL вернет номер для этой функции, где число, возвращаемое за день недели, зависит от установки SET DATEFIRST. Для моих установок мы используем значение по умолчанию: 1 = понедельник. Если у вас нет полного контроля над настройкой SET DATEFIRST во всем мире, ваш код может быть запущен, forever, тогда у вас могут возникнуть проблемы, выходящие за рамки этой дискуссии. Альтернативой, которую я использовал, является использование DATENAME, который будет возвращать символьные строки, например. Субботу, воскресенье и т. Д., Но это имеет ту же проблему в отношении настройки SET LANGUAGE.

(fyi, я также вынул [] и переименовал цит, потому что они меня подслушивали.)

+0

Это похоже на работу, как я описал - спасибо! - У меня есть другой вопрос/вопрос, хотя это и появилось. Если конец месяца выпадет на субботу или воскресенье, то проигнорируйте его и просто по умолчанию по воскресеньям через субботу, как в обычную неделю. Спасибо заранее, это мне очень помогло. – gevjen

+0

добавление, которое вы предоставили, было именно тем, что мне нужно - спасибо за помощь. Я поставил эту функцию в постель - еще раз спасибо. – gevjen

0

Глядя на ваш код, который я предполагаю, что вы используете SQL-сервер. EOMONTH() доступен в sql-server 2012 + и упрощает поиск даты окончания. В основном вам нужно проверить, в какой день недели начинается дата начала и как близко к концу месяца начинается день начала, а затем сбалансировать это через рекурсию. вы можете добавить еще один столбец LEVEL и отслеживать номер недели, если хотите. Например. 1 как LEVEL в первом запросе на уровне UNION Level + 1 в качестве уровня.

DECLARE @StartDate DATE = '2016-05-22' 
DECLARE @EndDate DATE = GETDATE() 

--if @StartDate provided is middle of the week and you want to adjust to sunday use this 
SET @StartDate = CASE 
     WHEN DATEPART(WEEKDAY,@StartDate) = 1 THEN @StartDate 
     ELSE DATEADD(day,-7 + DATEPART(WEEKDAY,@StartDate),@StartDate) 
    END 

;WITH cteRecursiveDates AS (
    SELECT 
     @StartDate as Startdate 
     ,CASE 
      WHEN DATEDIFF(day,@StartDate,EOMONTH(@StartDate)) < (7 - DATEPART(WEEKDAY,@StartDate)) 
       THEN DATEADD(day,DATEDIFF(day,@StartDate,EOMONTH(@StartDate)) ,@StartDate) 
      ELSE DATEADD(day,(7- DATEPART(WEEKDAY,@StartDate)),@StartDate) 
     END as EndDate 

    UNION ALL 

    SELECT 
     DATEADD(day,1,c.EndDate) as StartDate 
     ,CASE 
      WHEN DATEDIFF(day,DATEADD(day,1,c.EndDate),EOMONTH(DATEADD(day,1,c.EndDate))) < (7 - DATEPART(WEEKDAY,DATEADD(day,1,c.EndDate))) 
      THEN DATEADD(day,DATEDIFF(day,DATEADD(day,1,c.EndDate),EOMONTH(DATEADD(day,1,c.EndDate))) ,DATEADD(day,1,c.EndDate)) 
      ELSE DATEADD(day,(7- DATEPART(WEEKDAY,DATEADD(day,1,c.EndDate))),DATEADD(day,1,c.EndDate)) 
     END as EndDate 
    FROM 
     cteRecursiveDates c 
    WHERE 
     DATEADD(day,1,c.EndDate) <= @EndDate 
) 

SELECT * 
FROM 
    cteRecursiveDates 
0

Я добавил день недели (бек/конец) только для иллюстрации

Declare @Date1 Date='2016-05-22' 
Declare @Date2 Date='2016-07-31' 

;with cteBase as (
Select * 
     ,Flag1=IIF(Row_Number() over (Partition By Month(RetVal) Order By RetVal)=1 or DatePart(WEEKDAY,RetVal)=1 or Day(RetVal)=1 ,1,0) 
     ,Flag2=IIF(DatePart(WEEKDAY,RetVal)=7 or Lead(Day(RetVal),1) over (Order By RetVal)=1,1,0) 
     ,RowNr=Row_Number() over (Order By RetVal) 
    From [dbo].[udf-Create-Range-Date](@Date1,@Date2,'DD',1) 
) 
Select DateR1=cast(A.RetVal as Date) 
     ,B.DateR2 
     ,DOW1=DateName(Weekday,A.RetVal) 
     ,DOW2=DateName(Weekday,B.DateR2) 
From cteBase A 
Cross Apply (Select DateR2=min(cast(RetVal as date)) From cteBase Where Flag2=1 and RowNr>=A.RowNr) B 
Where A.Flag1=1 and B.DateR2 is not null 

Возвращает

DateR1  DateR2  DOW1  DOW2 
2016-05-22 2016-05-28 Sunday  Saturday 
2016-05-29 2016-05-31 Sunday  Tuesday 
2016-06-01 2016-06-04 Wednesday Saturday 
2016-06-05 2016-06-11 Sunday  Saturday 
2016-06-12 2016-06-18 Sunday  Saturday 
2016-06-19 2016-06-25 Sunday  Saturday 
2016-06-26 2016-06-30 Sunday  Thursday 
2016-07-01 2016-07-02 Friday  Saturday 
2016-07-03 2016-07-09 Sunday  Saturday 
2016-07-10 2016-07-16 Sunday  Saturday 
2016-07-17 2016-07-23 Sunday  Saturday 
2016-07-24 2016-07-30 Sunday  Saturday 

ОДС я использую для создания динамических диапазонов дат

CREATE FUNCTION [dbo].[udf-Create-Range-Date] (@DateFrom datetime,@DateTo datetime,@DatePart varchar(10),@Incr int) 

Returns 
@ReturnVal Table (RetVal datetime) 

As 
Begin 
    With DateTable As (
     Select DateFrom = @DateFrom 
     Union All 
     Select Case @DatePart 
       When 'YY' then DateAdd(YY, @Incr, df.dateFrom) 
       When 'QQ' then DateAdd(QQ, @Incr, df.dateFrom) 
       When 'MM' then DateAdd(MM, @Incr, df.dateFrom) 
       When 'WK' then DateAdd(WK, @Incr, df.dateFrom) 
       When 'DD' then DateAdd(DD, @Incr, df.dateFrom) 
       When 'HH' then DateAdd(HH, @Incr, df.dateFrom) 
       When 'MI' then DateAdd(MI, @Incr, df.dateFrom) 
       When 'SS' then DateAdd(SS, @Incr, df.dateFrom) 
       End 
     From DateTable DF 
     Where DF.DateFrom < @DateTo 
    ) 

    Insert into @ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767) 

    Return 
End 

-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1) 
0

Разделите каждую неделю, что разрыв конца месяца на два, а затем объединение обратно в них, которые не нарушают конца месяца.

declare 
     @StartDate date = datefromparts(2016, 5, 22), 
     @EndDate date = cast(getdate() as date) 

    ; 
    with 
    dates as 
    (
     select 
      @StartDate as StartDate, 
      dateadd(day, 6, @StartDate) as EndDate, 
      eomonth(@StartDate) as EndOfMonth, 
      dateadd(day, 1 - day(dateadd(month, 1, @StartDate)), 
       dateadd(month, 1, @StartDate)) as FirstDayOfNextMonth 

     union all 

     select 
      dateadd(day, 7, d.StartDate), 
      dateadd(day, 7, d.EndDate), 
      eomonth(dateadd(day, 7, d.StartDate)), 
      dateadd(day, 1 - day(dateadd(month, 1, d.StartDate)), 
       dateadd(month, 1, d.StartDate)) 
     from 
      dates as d 
     where 
      dateadd(day, 7, StartDate) <= @EndDate 
    ) 
    -- week within the month 
    select 
     d.StartDate, 
     d.EndDate 
    from 
     dates as d 
    where 
     d.EndDate <= d.EndOfMonth 

    union all 

    -- week breach to next month, first part 
    select 
     d.StartDate, 
     d.EndOfMonth as EndDate 
    from 
     dates as d 
    where 
     d.EndDate > d.EndOfMonth 

    union all 

    -- week breach to next month, second part 
    select 
     d.FirstDayOfNextMonth as StartDate, 
     d.EndDate 
    from 
     dates as d 
    where 
     d.EndDate > d.EndOfMonth 

    order by 1 

    option (maxrecursion 0) 

Результат:

| StartDate | EndDate | 
|------------|------------| 
| 2016-05-22 | 2016-05-28 | 
| 2016-05-29 | 2016-05-31 | 
| 2016-06-01 | 2016-06-04 | 
| 2016-06-05 | 2016-06-11 | 
| 2016-06-12 | 2016-06-18 | 
| 2016-06-19 | 2016-06-25 | 
| 2016-06-26 | 2016-06-30 | 
| 2016-07-01 | 2016-07-02 | 
| 2016-07-03 | 2016-07-09 | 
| 2016-07-10 | 2016-07-16 | 
| 2016-07-17 | 2016-07-23 | 
| 2016-07-24 | 2016-07-30 | 
| 2016-07-31 | 2016-07-31 | 
| 2016-08-01 | 2016-08-06 | 
| 2016-08-07 | 2016-08-13 | 
| 2016-08-14 | 2016-08-20 |