В оконном отчёте нужно отличить первую строку раздела от предыдущей строки, у которой само значение равно NULL. Как построить такую проверку?
Нельзя надёжно отличить эти случаи только по результату LAG(значение): в обоих случаях он может вернуть NULL. Нужно дополнительно проверять наличие предыдущей строки — например, вычислять через LAG гарантированно непустой идентификатор предыдущей записи.
Если идентификатор предыдущей строки равен NULL, предыдущей строки нет. Если идентификатор найден, а предыдущее значение равно NULL, строка существует, но её значение отсутствует.
Оконные функции появились как средство аналитической обработки без схлопывания строк в группы. В обычном GROUP BY после агрегации теряется индивидуальная строка, поэтому сравнивать запись с соседней или предыдущей строкой неудобно.
LAG решает задачу доступа к предыдущей строке внутри заданного раздела. Однако SQL использует NULL и для отсутствующего значения, и для результата, который невозможно получить из-за отсутствия строки, поэтому одного столбца результата недостаточно для различения этих ситуаций.
Пусть в каждой группе событий нужно сравнить значение текущей записи со значением предыдущей. У первой записи группы предыдущей строки нет, а у последующей записи предыдущая строка может существовать, но содержать NULL.
Если трактовать любой NULL от LAG как отсутствие предыдущей строки, можно ошибочно пометить реальную запись как первую. Это приводит к неверным изменениям, пропущенным переходам статуса и неправильной классификации данных.
Вычислите два оконных значения: предыдущее бизнес-значение и признак существования предыдущей строки. В качестве признака удобно использовать LAG по столбцу, который гарантированно не равен NULL, например по первичному ключу.
У первой строки previous_id равен NULL, потому что строки до неё в разделе нет. У второй строки previous_id содержит идентификатор первой записи, а previous_amount равен NULL: это уже существующая предыдущая строка с пропущенным значением.
Порядок в окне должен быть детерминированным. Если ts не уникален, добавьте уникальный ключ, как id в примере; иначе предыдущая строка может выбираться неоднозначно.
Третий аргумент LAG задаёт значение по умолчанию, когда строка с указанным смещением отсутствует. Но специальное значение может реально встретиться в данных, поэтому такой способ безопасен только при гарантии, что выбранный маркер не является допустимым значением. Проверка по непустому идентификатору обычно надёжнее.
Не следует пытаться отличить случаи выражением вида previous_amount IS NULL: оно проверяет только значение, но не существование строки. Для вычислений над значениями также важно отдельно определить бизнес-смысл NULL: это может быть неизвестное значение, неприменимость или специальное состояние.
В отчёте по истории тарифов нужно показать изменение цены относительно предыдущей записи каждого клиента. У первой записи клиента изменение не рассчитывается, а у следующей записи предыдущая цена может быть NULL, если цена ещё не была задана.
Вариант с проверкой только LAG(price) IS NULL прост, но неверно объединяет две ситуации: начало истории и реально отсутствующую цену. Вариант с третьим аргументом LAG позволяет использовать маркер, но требует гарантии, что маркер не встречается среди настоящих цен.
Выбранное решение — получать через LAG идентификатор предыдущей записи и отдельно её цену. Оно не зависит от допустимого диапазона цен и явно разделяет структурный признак наличия строки и содержательное значение. В результате первая запись корректно помечается как не имеющая предшественника, а последующая запись с NULL — как имеющая предшественника с неизвестной ценой.
Вопрос: Что произойдёт, если в ORDER BY оконной функции несколько строк имеют одинаковый порядок?
Ответ: Предыдущая строка может быть выбрана неоднозначно, если СУБД не получает полного детерминированного порядка. Нужно добавить уникальный tie-breaker, обычно первичный ключ или другой столбец, однозначно упорядочивающий записи.
Вопрос: Можно ли использовать LAG с третьим аргументом как универсальный признак отсутствия предыдущей строки?
Ответ: Только если значение по умолчанию гарантированно невозможно в исходных данных. Иначе реальное значение может совпасть с маркером, и результат потеряет различимость. Для надёжной проверки лучше использовать отдельный гарантированно непустой идентификатор строки.
Вопрос: Что изменится, если разделение выполнить по клиенту, а сортировку — только по дате?
Ответ: Предыдущая строка будет определяться отдельно внутри каждого клиента, но при одинаковых датах порядок может быть неопределённым. Если бизнес-логика различает события в одну дату, в оконный ORDER BY нужно включить дополнительный столбец, задающий точный порядок событий.