Skip to content

GENERATE_SERIES в SQL Server

В SQL Server 2022 появилась функция генерации числовой последовательности, GENERATE_SERIES. Подобная функция имеется в некоторых других СУБД, в частности, в PostgreSQL. Вот, например, как с помощью этой функции решалась следующая задача из учебника.

Вывести последовательность дат между датами первого и последнего полета пассажира с id_psg = 5.

Решение:

SELECT generate_series(MIN(date), MAX(date), '1 day')
FROM pass_in_trip
WHERE id_psg = 5;

Результат можно посмотреть, выполнив запрос в консоли учебника или на сайте sql-ex.ru (не забудьте выбрать PostgreSQL). Вот скриншот с консоли:





Несмотря на схожесть, реализация функции GENERATE_SERIES в SQL Server существенно отличается от реализации в PostgreSQL. Начать с того, что генерируется только числовая последовательность, и все аргументы функции относятся к числовым типам. Кроме того, поскольку функция создает одностолбцовую таблицу, она естественно используется в предложении FROM. Вот пример использования функции GENERATE_SERIES для создание ряда целых чисел в диапазоне от 1 до 50 с шагом приращения в 5, заимствованный из документации Microsoft:

SELECT value
FROM GENERATE_SERIES(1, 50, 5);

Чтобы получить решение нашей задачи в диалекте SQL Server, первое, что приходит в голову, - посчитать количество дней в интервале между MIN(date) и MAX(date) (назовем его cnt), а затем использовать это число в вызове функции:

GENERATE_SERIES(1, cnt, 1)

Т.е. можно предложить такое решение:

WITH gs AS 
(SELECT MIN(date) mind, DATEDIFF(day,MIN(date), MAX(date)) cnt
FROM pass_in_trip WHERE id_psg = 5)
SELECT DATEADD( DAY, value, mind) _date_
FROM gs CROSS APPLY GENERATE_SERIES(0, gs.cnt, 1);

Кстати, в качестве параметра функции GENERATE_SERIES можно использовать непосредственно подзапрос. Тогда я прихожу к такому решения задачи:

SELECT DATEADD( DAY, value, (SELECT MIN(date) FROM pass_in_trip WHERE id_psg = 5)) _date_
FROM GENERATE_SERIES(0, (SELECT DATEDIFF(day, MIN(date), MAX(date)) FROM pass_in_trip WHERE id_psg = 5), 1);

Тут мне пришлось дважды использовать фактически один и тот же подзапрос, тогда как в первом решении я избегаю этого с помощью CTE и CROSS APPLY. Можно предложить вариант с CTE и без CROSS APPLY:

WITH gs AS
(SELECT MIN(date) mind, DATEDIFF(day,MIN(date), MAX(date)) cnt
FROM pass_in_trip WHERE id_psg = 5)
SELECT DATEADD( DAY, value, (SELECT mind FROM gs)) _date_
FROM GENERATE_SERIES(0, (SELECT gs.cnt FROM gs), 1);

Так или иначе, но на мой взгляд решение в PostgreSQL выглядит более элегантным.

Важно. Для использования функции GENERATE_SERIES в SQL Server уровень совместимости базы данных должен быть не менее 160.

Ссылки по теме
1. Генерация числовой последовательности
2. Общие табличные выражения
3. Работа с данными временных рядов в SQL Server 2022 и Azure SQL
4. Мои любимые улучшения T-SQL в SQL Server 2022
5. CROSS APPLY / OUTER APPLY

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

No comments

The author does not allow comments to this entry

Add Comment

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Form options

Submitted comments will be subject to moderation before being displayed.