#sql #sql-server #tsql #lag
Вопрос:
При использовании функции задержки во временных рядах в SQL Server я всегда борюсь с первым значением во временном ряду. Предположим, что этот тривиальный пример
CREATE TABLE demo
([id] int, [time] date, [content] int)
;
INSERT INTO demo (id, time, content) VALUES
(1, '2021-05-31', cast(rand()*1000 as int)),
(2, '2021-06-01', cast(rand()*1000 as int)),
(3, '2021-06-02',cast(rand()*1000 as int)),
(4, '2021-06-03', cast(rand()*1000 as int)),
(5, '2021-06-04', cast(rand()*1000 as int)),
(6, '2021-06-05', cast(rand()*1000 as int)),
(7, '2021-06-06', cast(rand()*1000 as int)),
(8, '2021-06-07', cast(rand()*1000 as int)),
(9, '2021-06-08', cast(rand()*1000 as int));
Я хочу получить все значения и их предыдущее значение в июне, так что что-то вроде этого
select content, lag(content, 1, null) over (order by time)
from demo
where time >= '2021-06-01'
пока все хорошо, однако первая запись приведет к нулю для предыдущего значения.
Конечно, есть много решений о том, как заполнить нулевое значение, например, выбрать больший диапазон и т. Д. Но Для очень больших таблиц я почему-то думаю, что для этого должно быть элегантное решение.
Иногда я делаю такие вещи, как это
select content, lag(content, 1,
(select content from demo d1 join
(select max(time) maxtime from demo where time < '2021-06-01') d2 on d1.time = d2.maxtime
)) over (order by time)
from demo
where time >= '2021-06-01'
Есть ли что-то более эффективное? (примечание: конечно, для этого тривиального примера я не вижу разницы, но для таблиц с разделением и 500 000 000 записей следует найти наиболее эффективное решение)
Посмотри на скрипку
Комментарии:
1. Ну, какое значение вы хотите отобразить для первого значения?
2. В первой строке указана дата 2021-06-01, предыдущее значение имеет дату 2021-5-31 и содержимое 1, поэтому 1
Ответ №1:
Ключевая идея состоит в том, чтобы использовать подзапрос:
select t.*
from (select content, lag(content) over (order by time)
from demo d
) d
where time >= '2021-06-01';
Это, вероятно, будет сканировать всю таблицу. Однако вы можете создать индекс demo(time, content)
, чтобы помочь lag()
.
Затем вы можете оптимизировать это, если у вас есть разумный период возврата. Например, если записи появляются каждый месяц, просто вернитесь на один месяц назад в подзапросе:
select t.*
from (select content, lag(content) over (order by time)
from demo d
where time >= '2021-05-01'
) d
where time >= '2021-06-01';
Это также может быть очень важно, если ваши данные разбиты на разделы, как это обычно бывает с большими таблицами.
Комментарии:
1. Итак, это в основном то, что я делаю все время… не кажется чем-то более эффективным
Ответ №2:
В данном конкретном случае, судя по вашим комментариям, вы можете сначала вычислить задержку по всей нефильтрованной таблице, а затем выполнить подзапрос, основанный на дате:
WITH cte AS (
SELECT time, content, LAG(content) OVER (ORDER BY time) lag_content
FROM demo
)
SELECT content, lag_content
FROM cte
WHERE time >= '2021-06-01';
Комментарии:
1. Я попробовал это на своем большом столе с полумиллиардом записей, и все прошло до смешного быстро. Как это возможно? Я бы подумал, что cte копирует все данные в оперативную память или около того?
2. @во-первых, я удивлен, что все прошло так быстро с такой большой таблицей, но в SQL Server CTE просто вставляется. Итак, мой ответ-просто делать
SELECT content, lag_content FROM ( ... cte here ...) t WHERE time >= '2021-06-01'
3. извините, это была ошибка с моей стороны. Казнь длилась несколько минут. Использование подзапросов и т.д. Значительно быстрее.
Ответ №3:
Какими бы вы хотели видеть нулевые значения? Я поставил их как 0 в приведенном ниже примере.
SELECT
content,
coalesce(LAG(content, 1, NULL) OVER(
ORDER BY
time
), content-1) lag_content
FROM
demo
WHERE
time >= '2021-06-01'
Выход:
content lag_content
-------------------
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 8
Попробуйте это здесь: dbfiddle
Комментарии:
1. Значение null на самом деле должно быть 1 в приведенном выше примере, т. Е. Значение со временем 2021-05-31, которое является фактическим значением задержки, но не является частью предложения where
2. Я изменил значение по умолчанию для объединения на 1, но подойдет ли это для других примеров?
3. Ну нет, 1-это просто пример, но без его поиска я не знаю значения в этой строке. Кроме того, вы можете просто изменить свое утверждение, включив 1, где стоит null, и удалить объединение
4. @во-первых, я только что изменил свой ответ. Пожалуйста, проверьте это и дайте мне знать, если это соответствует вашим потребностям.
5. Я не вижу, чтобы скрипка обновлялась. Не могли бы вы, пожалуйста, проверить?