Включая все месяцы года без дубликатов, когда диапазоны дат могут быть последовательными или не использовать T- SQL - PullRequest
0 голосов
/ 05 февраля 2020

У меня есть один или несколько начальных и конечных периодов, которые могут быть последовательными, перекрывающимися или между ними. Моя цель - показать все 12 месяцев независимо от того, когда начинается период; то есть я могу видеть месяцы до периода, в течение периода и, возможно, после периода. Для этого примера я проверяю его по 2019 году, поэтому я хочу, чтобы все 12 месяцев были заполнены для 2019.

У меня есть следующие примеры данных, чтобы проиллюстрировать проблему:

DECLARE @DATES TABLE (ID int, EffectiveDate date, EffectiveEndDate date)
INSERT INTO @DATES
VALUES
    (43, '2018-10-01', '2019-09-30'),
    (43, '2019-10-01', '2020-09-30'),
    (44, '2019-10-01', '2020-09-30');

У меня также есть таблица подсчета, которая имеет все 12 месяцев и начало месяца (для краткости опущена, но это временная таблица со столбцом с именем N, который имеет значение 1-12, представляющее месяц, и столбец StartOfMonth, который является начальной датой месяца. Теперь мне нужно, чтобы каждый идентификатор (в данном случае 43 и 44) показывал все 12 месяцев. Это легко с 43, где есть две записи, которые запускаются с октября С 2018 по ноябрь 2020 года, поскольку он выпадает на все 12 месяцев. 44 однако дает мне только октябрь, ноябрь и декабрь, поскольку в октябре начинается только одна строка. Я не могу добавить строку за предыдущие месяцы.

Таблица месяцев просто определяется следующим образом:

DROP TABLE IF EXISTS #Months;
CREATE TABLE #Months (N tinyint, StartOfMonth date);
INSERT INTO #Months
VALUES
    (1, DATEFROMPARTS(2019, 1, 1)),
    (2, DATEFROMPARTS(2019, 2, 1)),
    (3, DATEFROMPARTS(2019, 3, 1)),
    (4, DATEFROMPARTS(2019, 4, 1)),
    (5, DATEFROMPARTS(2019, 5, 1)),
    (6, DATEFROMPARTS(2019, 6, 1)),
    (7, DATEFROMPARTS(2019, 7, 1)),
    (8, DATEFROMPARTS(2019, 8, 1)),
    (9, DATEFROMPARTS(2019, 9, 1)),
    (10, DATEFROMPARTS(2019, 10, 1)),
    (11, DATEFROMPARTS(2019, 11, 1)),
    (12, DATEFROMPARTS(2019, 12, 1));

Код:

SELECT Month = m.N, 
       d.ID, 
       d.EffectiveDate, 
       d.EffectiveEndDate,
       -- This flag doesn't mean anything, just so I can better see the results I'm getting
       Ind = CASE
                    WHEN m.StartOfMonth BETWEEN d.EffectiveDate AND d.EffectiveEndDate
                    THEN 1
                    ELSE 0
                END
FROM @dates d
     LEFT JOIN #Months m
        ON m.N BETWEEN 1 AND 12
WHERE
    m.StartOfMonth
        BETWEEN EffectiveDate AND EffectiveEndDate
ORDER BY ID, m.N;

Это дает мне следующий (неправильный) вывод:

Month   ID  EffectiveDate   EffectiveEndDate    Ind
1       43  2018-10-01      2019-09-30          1
2       43  2018-10-01      2019-09-30          1
3       43  2018-10-01      2019-09-30          1
4       43  2018-10-01      2019-09-30          1
5       43  2018-10-01      2019-09-30          1
6       43  2018-10-01      2019-09-30          1
7       43  2018-10-01      2019-09-30          1
8       43  2018-10-01      2019-09-30          1
9       43  2018-10-01      2019-09-30          1
10      43  2019-10-01      2020-09-30          1
11      43  2019-10-01      2020-09-30          1
12      43  2019-10-01      2020-09-30          1
!!! THIS PART IS WRONG !!!
10      44  2019-10-01      2020-09-30          1
11      44  2019-10-01      2020-09-30          1
12      44  2019-10-01      2020-09-30          1

Если Я пропускаю е Эффективная дата / эффективная дата окончания проверки или попытка сделать какое-то заявление о ситуации, где я говорю, что если месяц начинается до даты вступления в силу, тогда включите его в любом случае 43 удваивается на месяцы, потому что есть две строки, а 44 работает, как ожидалось.

Мне нужно получить это:

Month   ID  EffectiveDate   EffectiveEndDate    Ind
1       43  2018-10-01      2019-09-30          1
2       43  2018-10-01      2019-09-30          1
3       43  2018-10-01      2019-09-30          1
4       43  2018-10-01      2019-09-30          1
5       43  2018-10-01      2019-09-30          1
6       43  2018-10-01      2019-09-30          1
7       43  2018-10-01      2019-09-30          1
8       43  2018-10-01      2019-09-30          1
9       43  2018-10-01      2019-09-30          1
10      43  2019-10-01      2020-09-30          1
11      43  2019-10-01      2020-09-30          1
12      43  2019-10-01      2020-09-30          1
1       44  2019-10-01      2020-09-30          0
2       44  2019-10-01      2020-09-30          0
3       44  2019-10-01      2020-09-30          0
4       44  2019-10-01      2020-09-30          0
5       44  2019-10-01      2020-09-30          0
6       44  2019-10-01      2020-09-30          0
7       44  2019-10-01      2020-09-30          0
8       44  2019-10-01      2020-09-30          0
9       44  2019-10-01      2020-09-30          0
10      44  2019-10-01      2020-09-30          1
11      44  2019-10-01      2020-09-30          1
12      44  2019-10-01      2020-09-30          1

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

1 Ответ

1 голос
/ 06 февраля 2020

Возможно, есть лучший способ сделать это, но вот уродливое решение:

-- Build base data
DECLARE @DATES TABLE (ID int, EffectiveDate date, EffectiveEndDate date)
INSERT INTO @dates
VALUES
    (43, '2018-10-01', '2019-09-30'),
    (43, '2019-10-01', '2020-09-30'),
    (44, '2019-10-01', '2020-09-30');

DECLARE @months TABLE (StartOfMonth date, n int)

;WITH dateCTE
AS
(
  SELECT ROW_NUMBER() OVER (ORDER BY number) - 1  AS rn
  FROM master.dbo.spt_values
)
INSERT @months (StartOfMonth, n)
SELECT CAST(DATEADD(mm, rn, '2018-01-01') AS date) AS StartOfMonth, DATEPART(mm,DATEADD(mm, rn, '2018-01-01')) AS n
FROM dateCTE
WHERE rn < 48

-- build a list of all IDs and months where the ID is active in the year
;with dateCTE
AS
(
  SELECT DISTINCT d.ID, m.StartOfMonth, m.n
  FROM @months AS m
  CROSS
  JOIN @dates AS d
  WHERE DATEPART(YEAR,m.StartOfMonth) BETWEEN DATEPART(YEAR,d.EffectiveDate) and DATEPART(YEAR,d.EffectiveEndDate)

)
-- join list from previous step to the activity data
-- this generates the full list with NULLs where the ID was not active
,listCTE
AS
(
  SELECT cd.ID, cd.StartOfMonth, cd.n, d.EffectiveDate, d.EffectiveEndDate
  FROM dateCTE AS cd
  LEFT
  JOIN @dates AS d
  ON   d.ID = cd.ID
  AND  cd.StartOfMonth between d.EffectiveDate AND d.EffectiveEndDate
)
-- fill in the NULLS by joining the table back to itelf
SELECT  n AS [Month],
        ID,
        COALESCE(EffectiveDate, 
                 (SELECT TOP 1 EffectiveDate FROM listCTE AS l2 WHERE l2.ID = l.ID AND l2.EffectiveDate > l.StartOfMonth ORDER BY l2.StartOfMonth DESC),
                 (SELECT TOP 1 EffectiveDate FROM listCTE AS l2 WHERE l2.ID = l.ID AND l2.EffectiveEndDate < l.StartOfMonth ORDER BY l2.StartOfMonth DESC)
        ) AS EffectiveDate,
        COALESCE(EffectiveEndDate, 
                 (SELECT TOP 1 EffectiveEndDate FROM listCTE AS l2 WHERE l2.ID = l.ID AND l2.EffectiveDate > l.StartOfMonth ORDER BY l2.StartOfMonth DESC),
                 (SELECT TOP 1 EffectiveEndDate FROM listCTE AS l2 WHERE l2.ID = l.ID AND l2.EffectiveEndDate < l.StartOfMonth ORDER BY l2.StartOfMonth DESC)
                ) AS EffectiveEndDate,
        CASE
            WHEN StartOfMonth BETWEEN EffectiveDate AND EffectiveEndDate
            THEN 1
            ELSE 0
        END AS Ind,
        StartOfMonth
FROM listCTE AS l
WHERE DATEPART(YEAR,StartOfMonth) = 2019
ORDER BY ID, StartOfMonth

(этот код использует табличную переменную @months, а не временную таблицу #months в оригинале)

Это работает, формируя список всех идентификаторов и месяцев, затем присоединяя его к таблице @dates, чтобы генерировать месяцы, в которых каждый идентификатор активен. Наконец, второй набор результатов соединяется с самим собой, чтобы заполнить значения NULL.

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

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