FIRST VALUE: примеры (SQL)
FIRST_VALUE(scalar_expression OVER [PARTITION BY ] ORDER BY [ROWS/ RANGE ]): Same as scalar_expressionОписание функции FIRST_VALUE
Функция FIRST_VALUE является оконной функцией в Microsoft SQL Server, которая возвращает первое значение из упорядоченного набора строк в рамках окна, определенного предложением OVER. Эта функция появилась в SQL Server 2012 и относится к категории аналитических функций, работающих с окнами данных.
Основное применение функции связано с необходимостью доступа к значению из первой строки окна без использования самосоединений или коррелированных подзапросов. Она часто используется для анализа временных рядов, сравнения текущих значений с начальными или для вычисления разностей.
Синтаксис и аргументы
FIRST_VALUE ( [scalar_expression ] )
OVER ( [ partition_by_clause ] order_by_clause [ rows_range_clause ] )- scalar_expression - Выражение, значение которого возвращается из первой строки окна. Может быть столбцом, константой, переменной, скалярным подзапросом или функцией. Не может быть другой оконной функцией или агрегатной функцией.
- OVER ( [ partition_by_clause ] order_by_clause [ rows_range_clause ] ) - Предложение, определяющее окно для функции.
partition_by_clause- Разделяет результирующий набор на секции (окна), к которым применяется функция. Если не указано, функция обрабатывает все строки как одну секцию.order_by_clause- Обязательный аргумент. Определяет логический порядок строк в каждой секции, по которому определяется первая строка.rows_range_clause- Дополнительно ограничивает строки в рамках секции, указывая начальную и конечную точки. ДляFIRST_VALUEчасто используетсяROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWилиROWS UNBOUNDED PRECEDING.
Функция возвращает значение того же типа, что и переданное scalar_expression. Если секция не содержит строк, возвращается NULL.
Базовые примеры использования
Простой пример с выводом первого значения в упорядоченном наборе.
SELECT
ProductID,
Name,
ListPrice,
FIRST_VALUE(Name) OVER (ORDER BY ListPrice) as FirstProduct
FROM Production.Product
WHERE ProductSubcategoryID = 1;ProductID Name ListPrice FirstProduct --------- ----------------------- ---------- -------------- 707 Sport-100 Helmet, Red 33.6442 Sport-100 Helmet, Red ...
Использование секционирования (PARTITION BY).
SELECT
TerritoryID,
SalesOrderID,
TotalDue,
FIRST_VALUE(TotalDue) OVER (
PARTITION BY TerritoryID
ORDER BY OrderDate
) as FirstOrderInTerritory
FROM Sales.SalesOrderHeader
WHERE OrderDate >= '2011-01-01';TerritoryID SalesOrderID TotalDue FirstOrderInTerritory ----------- ------------ ---------- ----------------------- 1 43659 23153.2339 23153.2339 1 43660 1457.3288 23153.2339 ...
Пример с явным указанием рамки окна (ROWS UNBOUNDED PRECEDING).
SELECT
EmployeeID,
Rate,
RateChangeDate,
FIRST_VALUE(Rate) OVER (
PARTITION BY EmployeeID
ORDER BY RateChangeDate
ROWS UNBOUNDED PRECEDING
) as StartingRate
FROM HumanResources.EmployeePayHistory;EmployeeID Rate RateChangeDate StartingRate ---------- -------- ---------------- ------------ 1 12.45 2009-01-14 12.45 1 25.50 2009-05-31 12.45 ...
Похожие функции в MS SQL
- LAST_VALUE - Возвращает последнее значение из окна. Имеет аналогичный синтаксис, но без дополнительной рамки окна (например,
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) может вести себя неинтуитивно, возвращая текущее значение. - LAG - Обращается к данным из предыдущей строки относительно текущей на заданное количество шагов назад. Более гибкая, чем
FIRST_VALUE, для последовательного доступа к предыдущим строкам, но менее удобна для получения самого первого значения в секции. - NTH_VALUE - Доступна начиная с SQL Server 2012. Позволяет получить значение из строки с произвольным порядковым номером в окне (например, второе или третье).
FIRST_VALUE(expr)логически эквивалентнаNTH_VALUE(expr, 1). - MIN или MAX как оконные функции - Могут давать тот же результат, если порядок строк не важен, а нужно просто минимальное или максимальное значение в окне. Однако
FIRST_VALUEстрого зависит от порядка, заданного вORDER BY.
Выбор функции зависит от задачи. FIRST_VALUE предпочтительнее, когда важен именно порядок, заданный ORDER BY, а не экстремальное значение столбца. Для получения предыдущего/следующего значения удобнее LAG/LEAD. Для произвольной позиции в окне используется NTH_VALUE.
Распространенные ошибки
Отсутствие обязательного предложения ORDER BY в OVER().
-- ОШИБКА: Предложение ORDER BY обязательно для оконных функций смещения.
SELECT
FIRST_VALUE(Name) OVER (PARTITION BY ProductSubcategoryID)
FROM Production.Product;Неправильное понимание рамки окна по умолчанию для FIRST_VALUE. По умолчанию используется RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, что для некоторых задач может привести к неожиданным результатам, если в порядке есть одинаковые значения.
-- Если OrderDate имеет дубликаты, FIRST_VALUE может вести себя не как ожидается при RANGE.
SELECT
CustomerID,
OrderDate,
TotalDue,
FIRST_VALUE(TotalDue) OVER (
PARTITION BY CustomerID
ORDER BY OrderDate
-- Без явного указания ROWS/ RANGE
) as FirstOrder
FROM Sales.SalesOrderHeader;Для точного контроля лучше явно указывать ROWS UNBOUNDED PRECEDING.
Использование функции в неподдерживаемых контекстах, например, в предложении WHERE или HAVING без вложенного запроса.
-- ОШИБКА: Оконные функции можно использовать только в SELECT или ORDER BY.
SELECT EmployeeID, Rate
FROM HumanResources.EmployeePayHistory
WHERE FIRST_VALUE(Rate) OVER (PARTITION BY EmployeeID ORDER BY RateChangeDate) > 10;Для фильтрации по результату оконной функции требуется обернуть запрос.
История изменений
- SQL Server 2012 (11.x) - Функция
FIRST_VALUEбыла впервые представлена вместе с другими оконными функциями смещения (LAST_VALUE,LAG,LEAD). - SQL Server 2017 (14.x) и более поздние - Существенных изменений в синтаксисе или поведении функции не было. Все улучшения связаны с общей оптимизацией оконных функций и производительности обработки запросов, особенно при использовании индексов, подходящих для
ORDER BYв предложенииOVER. - Стандартизация функции соответствует стандарту ANSI SQL:2003.
Расширенные примеры
Расчет накопленного итога и сравнение с первым значением.
SELECT
MONTH(OrderDate) as OrderMonth,
SUM(TotalDue) as MonthlySales,
SUM(SUM(TotalDue)) OVER (ORDER BY MONTH(OrderDate) ROWS UNBOUNDED PRECEDING) as RunningTotal,
FIRST_VALUE(SUM(TotalDue)) OVER (ORDER BY MONTH(OrderDate)) as FirstMonthSales,
(SUM(SUM(TotalDue)) OVER (ORDER BY MONTH(OrderDate) ROWS UNBOUNDED PRECEDING) -
FIRST_VALUE(SUM(TotalDue)) OVER (ORDER BY MONTH(OrderDate))) as DiffFromFirst
FROM Sales.SalesOrderHeader
WHERE YEAR(OrderDate) = 2011
GROUP BY MONTH(OrderDate)
ORDER BY OrderMonth;OrderMonth MonthlySales RunningTotal FirstMonthSales DiffFromFirst ---------- ------------ ------------ --------------- ------------- 1 126317.6879 126317.6879 126317.6879 0.0000 2 158044.9386 284362.6265 126317.6879 158044.9386 ...
Использование в составе CASE для логического разделения.
SELECT
SalesOrderID,
CustomerID,
OrderDate,
TotalDue,
CASE
WHEN TotalDue >= FIRST_VALUE(TotalDue) OVER (PARTITION BY CustomerID ORDER BY OrderDate) * 1.5
THEN 'Значительно вырос'
ELSE 'В пределах нормы'
END as GrowthStatus
FROM Sales.SalesOrderHeader
ORDER BY CustomerID, OrderDate;Поиск даты первого заказа и сравнение с текущим интервалом.
SELECT
CustomerID,
OrderDate,
TotalDue,
DATEDIFF(day,
FIRST_VALUE(OrderDate) OVER (PARTITION BY CustomerID ORDER BY OrderDate),
OrderDate
) as DaysSinceFirstOrder
FROM Sales.SalesOrderHeader
WHERE CustomerID IN (11000, 11001);CustomerID OrderDate TotalDue DaysSinceFirstOrder ---------- ---------- ---------- -------------------- 11000 2009-05-31 3953.9884 0 11000 2009-07-01 3953.9884 31 ...
Комбинирование с другими оконными функциями для комплексного анализа.
SELECT
ProductID,
LocationID,
Quantity,
FIRST_VALUE(Quantity) OVER (PARTITION BY ProductID ORDER BY Quantity) as MinStock,
LAST_VALUE(Quantity) OVER (PARTITION BY ProductID ORDER BY Quantity ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as MaxStock,
AVG(Quantity) OVER (PARTITION BY ProductID) as AvgStock
FROM Production.ProductInventory
ORDER BY ProductID, Quantity;Применение к строковым данным для нахождения первой по алфавиту записи в группе.
SELECT
Department,
Employee,
Salary,
FIRST_VALUE(Employee) OVER (PARTITION BY Department ORDER BY Employee) as FirstEmployeeAlphabetically
FROM dbo.EmployeeTable
ORDER BY Department, Employee;Аналоги в других СУБД и языках
PostgreSQL
Поддерживает FIRST_VALUE с синтаксисом, идентичным SQL Server.
-- PostgreSQL
SELECT
FIRST_VALUE(amount) OVER (PARTITION BY customer_id ORDER BY payment_date)
FROM payment;Oracle
Также имеет функцию FIRST_VALUE. Отличие может быть в обработке NULL значений с помощью предложения IGNORE NULLS, которое в SQL Server для этой функции недоступно.
-- Oracle с IGNORE NULLS
FIRST_VALUE(column_name IGNORE NULLS) OVER (ORDER BY ...)MySQL (с версии 8.0)
Реализация соответствует стандарту SQL и аналогична SQL Server.
-- MySQL 8.0
SELECT
FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY hire_date)
FROM employees;SQLite (с версии 3.25.0)
Добавлена поддержка оконных функций, включая FIRST_VALUE.
-- SQLite
SELECT
FIRST_VALUE(quantity) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING)
FROM orders;Системы без прямой поддержки
В Sybase ASE до определенных версий или в более старых СУБД можно эмулировать поведение с помощью подзапросов:
-- Эмуляция через коррелированный подзапрос
SELECT
t1.id,
t1.value,
t1.date,
(SELECT TOP 1 t2.value
FROM table t2
WHERE t2.id = t1.id
ORDER BY t2.date) as first_value
FROM table t1;