Разделение дат на интервалы с использованием начальной и конечной даты - PullRequest
0 голосов
/ 15 ноября 2018

У меня есть сценарий, в котором мне нужно разбить данный диапазон дат на месячные интервалы.

Например, ввод такой, как показано ниже:

StartDate   EndDate
2018-01-21  2018-01-29
2018-01-30  2018-02-23
2018-02-24  2018-03-31
2018-04-01  2018-08-16
2018-08-17  2018-12-31

И ожидаемый результат должен быть таким, как показано ниже:

StartDate   EndDate
2018-01-21  2018-01-29
2018-01-30  2018-01-31
2018-02-01  2018-02-23
2018-02-24  2018-02-28
2018-03-01  2018-03-31
2018-04-01  2018-04-30
2018-05-01  2018-05-31
2018-06-01  2018-06-30
2018-07-01  2018-07-31
2018-08-01  2018-08-16
2018-08-17  2018-08-31
2018-09-01  2018-09-30
2018-10-01  2018-10-31
2018-11-01  2018-11-30
2018-12-01  2018-12-31

Ниже приведен пример данных.

CREATE TABLE #Dates
(
    StartDate DATE,
    EndDate DATE
);


INSERT INTO #Dates
(
    StartDate,
    EndDate
)
VALUES
('2018-01-21', '2018-01-29'),
('2018-01-30', '2018-02-23'),
('2018-02-24', '2018-03-31'),
('2018-04-01', '2018-08-16'),
('2018-08-17', '2018-12-31');

Ответы [ 4 ]

0 голосов
/ 15 ноября 2018

Начните с начальной начальной даты и рассчитайте конец месяца или просто используйте конечную дату, если она находится в том же месяце. Используйте только что рассчитанную конечную дату + 1 в качестве начальной для рекурсии и повторите расчет.

WITH cte AS 
 ( SELECT StartDate, -- initial start date
      CASE WHEN EndDate < DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+1,0))
           THEN EndDate
           ELSE           DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+1,0))
      END AS newEnd, -- LEAST(end of current month, EndDate)
      EndDate
   FROM #Dates

   UNION ALL

   SELECT dateadd(DAY,1,newEnd), -- previous end + 1 day, i.e. 1st of current month
      CASE WHEN EndDate <= DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+2,0)) 
           THEN EndDate 
           ELSE            DATEADD(DAY,-1,DATEADD(MONTH, DATEDIFF(MONTH,0,StartDate)+2,0)) 
      END, -- LEAST(end of next month, EndDate)
      EndDate
   FROM cte
   WHERE newEnd < EndDate 
 )
SELECT StartDate, newEnd 
FROM cte
0 голосов
/ 15 ноября 2018

Вы можете применить кросс-заявку с помощью таблицы Master..spt_values, чтобы получить строку для каждого месяца между StartDate и EndDate.

SELECT * 
into #dates
FROM (values 
('2018-01-21', '2018-01-29')
,('2018-01-30', '2018-02-23')
,('2018-02-24', '2018-03-31')
,('2018-04-01', '2018-08-16')
,('2018-08-17', '2018-12-31')
)d(StartDate  , EndDate)



SELECT
    SplitStart as StartDate 
    ,case when enddate < SplitEnd then enddate else SplitEnd end as EndDate
FROM  #dates d
cross apply (
    SELECT 
        cast(dateadd(mm, number, dateadd(dd, (-datepart(dd, d.startdate) +1) * isnull((number / nullif(number, 0)), 0), d.startdate)) as date) as SplitStart
        ,cast(dateadd(dd, -datepart(dd, dateadd(mm, number+1, startdate)), dateadd(mm, number+1, startdate)) as date) as SplitEnd
    FROM 
    master..spt_values 
    where type = 'p' 
      and number between 0 and (((year(enddate) - year(startdate)) * 12) +  month(enddate) - month(startdate))   
) s

drop table #dates
0 голосов
/ 15 ноября 2018

Следующее также должно работать

  1. Сначала я помещаю начальные и конечные даты в один столбец в данных блока cte.
  2. В блоке som_eom я создаюstart_of_month и end_of_month для всех 12 месяцев.
  3. Я объединяю шаги 1 и 2 в curated_set
  4. Я создаю curated_set, упорядоченный по столбцу даты
  5. Наконец, я отклоняю нежелательныезаписей, в моем предложении фильтра не в ('som', 'StartDate')
with data
   as (select *
         from dates
        unpivot(x for y in(startdate,enddate))t
       )
    ,som_eom
      as (select top 12
                 cast('2018-'+cast(row_number() over(order by (select null)) as varchar(2))+'-01' as date) as som
                 ,dateadd(dd
                          ,-1
                           ,dateadd(mm
                                    ,1
                                    ,cast('2018-'+cast(row_number() over(order by (select null)) as varchar(2))+'-01' as date)
                                    )
                           ) as eom
                from information_schema.tables
            )
      ,curated_set
        as(select *
             from data
            union all
            select *
              from som_eom
            unpivot(x for y in(som,eom))t
            )
       ,curated_data
         as(select x
                  ,y
                  ,lag(x) over(order by x) as prev_val
              from curated_set
             )
select prev_val as st_dt,x as end_dt
       ,y  
  from curated_Data
where y not in('som','StartDate')
0 голосов
/ 15 ноября 2018

Вы можете использовать рекурсивный CTE.Основная идея состоит в том, чтобы начать с первой даты 2018-01-21 и построить список дат начала и окончания всех месяцев до последней даты 2018-12-31.Затем выполните внутреннее объединение с вашими данными и при необходимости закрепите даты.

DECLARE @Dates TABLE (StartDate DATE, EndDate DATE);
INSERT INTO @Dates (StartDate, EndDate) VALUES
('2018-01-21', '2018-01-29'),
('2018-01-30', '2018-02-23'),
('2018-02-24', '2018-03-31'),
('2018-04-01', '2018-08-16'),
('2018-08-17', '2018-12-31');

WITH minmax AS (
    -- clamp min(start date) to 1st day of that month
    SELECT DATEADD(MONTH, DATEDIFF(MONTH, CAST('00010101' AS DATE), MIN(StartDate)), CAST('00010101' AS DATE)) AS mindate, MAX(EndDate) AS maxdate
    FROM @Dates
), months AS (
    -- calculate first and last day of each month
    -- e.g. for February 2018 it'll return 2018-02-01 and 2018-02-28
    SELECT mindate AS date01, DATEADD(DAY, -1, DATEADD(MONTH, 1, mindate)) AS date31, maxdate
    FROM minmax
    UNION ALL
    SELECT DATEADD(MONTH, 1, prev.date01), DATEADD(DAY, -1, DATEADD(MONTH, 2, prev.date01)), maxdate
    FROM months AS prev
    WHERE prev.date31 < maxdate
)
SELECT
    -- clamp start and end date to first and last day of corresponding month
    CASE WHEN StartDate < date01 THEN date01 ELSE StartDate END,
    CASE WHEN EndDate > date31 THEN date31 ELSE EndDate END
FROM months
INNER JOIN @Dates ON date31 >= StartDate AND EndDate >= date01

Если rCTE не является опцией, вы всегда можете СОЕДИНИТЬСЯ с таблицей чисел или таблицей дат (идея, приведенная выше, по-прежнему применима).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...