У меня есть следующий вход:
id | value
----+-------
1 | 136
2 | NULL
3 | 650
4 | NULL
5 | NULL
6 | NULL
7 | 954
8 | NULL
9 | 104
10 | NULL
Я ожидаю следующий результат:
id | value
----+-------
1 | 136
2 | 136
3 | 650
4 | 650
5 | 650
6 | 650
7 | 954
8 | 954
9 | 104
10 | 104
Тривиальным решением было бы объединить таблицы с <
отношением, а затем выбрать MAX
значение в GROUP BY
:
WITH tmp AS (
SELECT t2.id, MAX(t1.id) AS lastKnownId
FROM t t1, t t2
WHERE
t1.value IS NOT NULL
AND
t2.id >= t1.id
GROUP BY t2.id
)
SELECT
tmp.id, t.value
FROM t, tmp
WHERE t.id = tmp.lastKnownId;
Тем не менее, тривиальное выполнение этого кода создаст внутренне квадрат числа строк входной таблицы ( O (n ^ 2) ). Я ожидал, что t-sql оптимизирует его - на уровне блоков / записей задача очень проста и линейна, по сути это цикл for ( O (n) ).
Однако в моих экспериментах последняя версия MS SQL 2016 не может правильно оптимизировать этот запрос, что делает невозможным его выполнение для большой входной таблицы.
Кроме того, запрос должен выполняться быстро, делая невозможным аналогичное простое (но очень отличающееся) решение на основе курсора.
Использование некоторой временной таблицы с поддержкой памяти может быть хорошим компромиссом, но я не уверен, что ее можно будет выполнить значительно быстрее, учитывая, что мой пример запроса с использованием подзапросов не работал.
Я также думаю о том, чтобы откопать некоторую оконную функцию из документации t-sql, что можно было сделать, чтобы сделать то, что я хочу. Например, накопленная сумма делает нечто очень похожее, но я не мог обмануть ее, чтобы получить последний ненулевой элемент, а не сумму элементов ранее.
Идеальным решением был бы быстрый запрос без процедурного кода или временных таблиц. В качестве альтернативы, также возможно решение с временными таблицами, но процедурная итерация таблицы - нет.
источник