Что приводит к тому, что LAST_VALUE в оконном отчёте часто возвращает значение текущей строки, а не последней строки раздела?
Причина — кадр окна по умолчанию. Если у оконной функции задано упорядочивание, но явно не указан конец кадра, он обычно заканчивается на текущей строке или её группе равных строк, поэтому LAST_VALUE возвращает последнее значение именно в этом кадре, а не во всём разделе.
Чтобы получить последнее значение всего раздела, нужно явно задать кадр, простирающийся до конца раздела: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.
Оконные функции появились как способ выполнять аналитические вычисления, не схлопывая исходные строки, как это делает GROUP BY. Это позволило сопоставлять строку с итогом по её разделу, предыдущей или следующей строкой, накопительным результатом и другими значениями.
Для таких вычислений SQL разделяет раздел окна, порядок строк и кадр окна. Кадр нужен, чтобы определить, какие строки из раздела участвуют в вычислении для текущей строки.
Предположим, отчёт показывает историю состояний объектов, отсортированную по времени. Требуется вывести для каждой строки состояние, которое было последним во всей истории объекта.
Если бездумно применить LAST_VALUE только с сортировкой по времени, функция может вернуть состояние текущей строки. Отчёт будет выглядеть правдоподобно, но значение «финального состояния» окажется неверным, что особенно опасно при расчёте конверсий и воронок.
LAST_VALUE возвращает последнее значение выражения в текущем кадре, а не автоматически в последней строке всего раздела. Раздел задаётся PARTITION BY, порядок — ORDER BY, а границы кадра — оконной рамкой.
При наличии ORDER BY многие СУБД используют кадр, эквивалентный диапазону от начала раздела до текущей строки. Для LAST_VALUE последняя строка такого кадра обычно является текущей строкой. Если у нескольких строк одинаковый ключ сортировки, семантика RANGE может включать всех строк с тем же ключом, поэтому результат зависит от группы равных значений.
Минимальный пример с явным полным кадром:
Здесь для каждой строки выбирается последнее значение status во всём разделе объекта. UNBOUNDED FOLLOWING принципиально отличается от конца по умолчанию: он разрешает кадру включать строки после текущей.
Нужно учитывать диалект SQL: детали кадра по умолчанию и поддержка отдельных вариантов синтаксиса могут различаться. Если результат зависит от границ окна, их следует указывать явно, а для повторяющихся ключей сортировки добавлять детерминирующий ключ, например идентификатор события.
Иногда вместо LAST_VALUE применяют MAX. Это допустимо только если максимальное значение одновременно означает последнее по бизнес-смыслу; для произвольного статуса, текста или значения, связанного с хронологией, MAX не является заменой последней записи.
В системе подписок аналитик строил отчёт по всем изменениям статуса клиента и хотел вывести рядом финальный статус каждой подписки. Первый вариант использовал LAST_VALUE с разделением по подписке и сортировкой по времени, но фактически часто показывал статус текущего события.
Рассматривались два решения. Можно было сначала найти последнюю дату отдельным агрегатным запросом, а затем присоединить соответствующую строку: это прозрачно, но требует дополнительного этапа и аккуратной обработки совпадающих дат. Можно было использовать ROW_NUMBER и оставить только последнюю строку, но тогда исчезала детализация истории.
Выбрали LAST_VALUE с явно заданным кадром от начала до конца раздела. История сохранилась построчно, а финальный статус стал одинаковым для всех событий одной подписки. Для одинаковых временных меток добавили уникальный идентификатор события в порядок сортировки.
Поможет ли добавление уникального идентификатора в ORDER BY исправить LAST_VALUE?
Оно устраняет неоднозначность порядка строк с одинаковым временем, но не меняет границу кадра. При кадре до текущей строки функция всё равно вернёт значение текущей строки, только теперь текущая строка будет определена однозначнее. Для получения значения из конца раздела необходимо изменить именно рамку окна.
Что обычно произойдёт, если у LAST_VALUE вообще нет ORDER BY?
Без упорядочивания понятие «последняя» строка не имеет содержательного хронологического смысла. Во многих СУБД окно без ORDER BY охватывает весь раздел, поэтому функция может вернуть значение строки, выбранной планом или внутренним порядком, который нельзя считать гарантированным. Для бизнес-логики, зависящей от последней записи, порядок нужно задавать явно.
Когда MAX может безопасно заменить LAST_VALUE?
Только когда значение, максимальное по обычному сравнению, совпадает с последним значением по нужному порядку. Например, для числового счётчика, который не уменьшается, MAX может дать тот же результат. Для статусов, цен, версий с особой сортировкой или значений, где последнее событие не является максимальным, такая замена меняет смысл запроса.