SQL интерполяция пропущенных значений для определенного диапазона дат c - с некоторыми условиями - PullRequest
0 голосов
/ 12 марта 2020

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

У меня есть таблица с месячными интервалами, структурированная как это:

+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
|  1 |     10 | 12/17/2017   | 1/17/2018    |
|  1 |     10 | 1/18/2018    | 2/18/2018    |
|  1 |     10 | 2/19/2018    | 3/19/2018    |
|  1 |     10 | 3/20/2018    | 4/20/2018    |
|  1 |     10 | 4/21/2018    | 5/21/2018    |
+----+--------+--------------+--------------+

Я обнаружил, что иногда в конце / начале года, когда я знаю, что он должен существовать, иногда отсутствует месяц, например:

+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
|  2 |     10 | 10/14/2018   | 11/14/2018   |
|  2 |     10 | 11/15/2018   | 12/15/2018   |
|  2 |     10 | 1/17/2019    | 2/17/2019    |
|  2 |     10 | 2/18/2019    | 3/18/2019    |
|  2 |     10 | 3/19/2019    | 4/19/2019    |
+----+--------+--------------+--------------+

Мне нужно заявление, которое будет:

  1. Определить, где пропущен период на конец года (но не найти пропущенные месяцы, которые не являются в начале / конце год).
  2. Создайте этот интервал, используя длину существующего интервала для этого идентификатора (возможно, используя среднюю длину интервала для идентификатора, чтобы сделать это?). Я мог бы создать интервал из «промежутка» между предыдущим и следующим интервалом, за исключением того, что он не будет работать, если я пропущу интервал в начале или конце записи идентификатора (т.е. если запись начинается, скажем, 1/16). / 2015, мне нужна сумма за 12/15 / 2014-1 / 15/2015
  3. Интерполировать «сумму» для этого интервала, используя среднесуточную «сумму» из ближайшего существующего интервала.

Конечный результат для приведенного выше примера должен выглядеть следующим образом:

+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
|  2 |     10 | 10/14/2018   | 11/14/2018   |
|  2 |     10 | 11/15/2018   | 12/15/2018   |
|  2 |     10 | 12/16/2018   | 1/16/2018    |
|  2 |     10 | 1/17/2019    | 2/17/2019    |
|  2 |     10 | 2/18/2019    | 3/18/2019    |
+----+--------+--------------+--------------+

"Приятно иметь" будет флаг, указывающий, что это значение интерполировано.

Есть ли способ сделать это эффективно в SQL? Я написал решение в SAS, но мне нужно переместить его в SQL, и мое решение SAS очень неэффективно (оптимизация не является целью, поэтому любое утверждение, которое делает то, что мне нужно, это fantasti c).

РЕДАКТИРОВАТЬ: я сделал SQLFiddle с моей таблицей примеров здесь:

http://sqlfiddle.com/#! 18 / 8b16d

1 Ответ

1 голос
/ 13 марта 2020

Вы можете использовать последовательность CTE для построения данных за пропущенные периоды. В этом запросе первый CTE (EOYS) генерирует все даты конца года (YYYY-12-31), относящиеся к таблице; вторая (INTERVALS) средняя длина интервала для каждого ID и третья (MISSING) попытка найти начальную (от t2) и конечную (от t3) даты смежных интервалов для любых пропущенных ( обозначено t1.ID IS NULL) интервалом в конце года. Выходные данные этого CTE затем используются в запросе INSERT ... SELECT для добавления пропущенных записей интервалов в таблицу, генерируя пропущенные даты, добавляя / вычитая длину интервала к дате окончания / начала соседнего интервала по мере необходимости.

Сначала мы добавим столбец interp, чтобы указать, была ли строка интерполирована:

ALTER TABLE Table1 ADD interp TINYINT NOT NULL DEFAULT 0;

Это устанавливает interp в 0 для всех существующих строк. Затем мы можем сделать INSERT, установив interp для всех этих строк в 1:

WITH EOYS AS (
  SELECT DISTINCT DATEFROMPARTS(DATEPART(YEAR, interval_beg), 12, 31) AS eoy
  FROM Table1
),
INTERVALS AS (
  SELECT ID, AVG(DATEDIFF(DAY, interval_beg, interval_end)) AS interval_len
  FROM Table1
  GROUP BY ID
),
MISSING AS (
  SELECT e.eoy, 
         ids.ID, 
         i.interval_len, 
         COALESCE(t2.amount, t3.amount) AS amount, 
         DATEADD(DAY,  1, t2.interval_end) AS interval_beg, 
         DATEADD(DAY, -1, t3.interval_beg) AS interval_end
  FROM EOYS e
  CROSS JOIN (SELECT DISTINCT ID FROM Table1) ids
  JOIN INTERVALS i ON i.ID = ids.ID
  LEFT JOIN Table1 t1 ON ids.ID = t1.ID
                     AND e.eoy BETWEEN t1.interval_beg AND t1.interval_end
  LEFT JOIN Table1 t2 ON ids.ID = t2.ID
                     AND DATEADD(MONTH, -1, e.eoy) BETWEEN t2.interval_beg AND t2.interval_end
  LEFT JOIN Table1 t3 ON ids.ID = t3.ID
                     AND DATEADD(MONTH,  1, e.eoy) BETWEEN t3.interval_beg AND t3.interval_end
  WHERE t1.ID IS NULL
)
INSERT INTO Table1 (ID, amount, interval_beg, interval_end, interp)
SELECT ID,
       amount,
       COALESCE(interval_beg, DATEADD(DAY, -interval_len, interval_end)) AS interval_beg,
       COALESCE(interval_end, DATEADD(DAY,  interval_len, interval_beg)) AS interval_end,
       1 AS interp
FROM MISSING

Это добавит в таблицу следующие строки:

ID  amount  interval_beg    interval_end    interp
2   10      2017-12-05      2018-01-04      1
2   10      2018-12-16      2019-01-16      1
2   10      2019-12-28      2020-01-27      1

Демонстрация по SQLFiddle

...