Однажды пришлось решать некоторые аналитические задачи бизнеса. Как оказалось, новые оконные функции SQL Server 2012 оказались очень кстати.
Задача 1.
Дан скрипт создания таблицы, содержащей данные о платежах клиента:
CREATE TABLE dbo.pays ( -- Идентификатор клиента client_id int not null, -- Дата и время платежа [date] datetime not null, -- Сумма платежа pay_sum decimal(19,2) not null )Напишите запрос, который выбирал бы все данные из таблицы с добавлением 2 полей:
- сумма платежа нарастающим итогом
- количество записей выше текущей для записей с тем же идентификатором клиента
Решение
SELECT
[client_id],
[date],
[pay_sum],
SUM([pay_sum]) OVER
(
PARTITION BY [client_id]
ORDER BY [date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS grow_sum,
COUNT([client_id]) OVER
(
PARTITION BY [client_id]
ORDER BY [date]
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS prec_count
FROM [dbo].[pays]
ORDER BY [date], [client_id]
Задача 2. Дан следующий скрипт создания таблицы хранения тиковых котировок (изменения цен всех ценных бумаг):
CREATE TABLE dbo.tics ( -- Дата и время тиковой котировки с точностью до секунд [date] datetime not null, -- Код ценной бумаги (ЦБ) contract varchar(10) not null, -- Цена price decimal(19,6) not null )Для следующих входных параметров:
DECLARE -- Длина периода в секундах @period_length int = 300, -- Начало временного промежутка @date_begin datetime = '2007-05-01 10:12', -- Конец временного промежутка @date_end datetime = '2007-05-01 11:12'напишите запрос к данной таблице, который выбирал бы из этой таблицы агрегированные показатели по временным периодам внутри временного промежутка. Длина периода задана в переменной @period_length. Временной промежуток задан началом (@date_begin) и концом (@date_end). Необходимые показатели - первая цена, последняя цена, максимальное и минимальное значение цены в разрезе ЦБ за период. Начало первого периода – 2001-01-01 00:00:00. Если начало временного промежутка не попадает на начало очередного периода, в выборку должен попасть период, между началом (включительно) и концом (исключая) которого попадает начало промежутка. Аналогично для конца периода. Результат запроса должен включать:
- начало периода
- код ЦБ
- первая цена
- последняя цена
- максимальное значение
- минимальное значение
Решение (ключевая часть кода)
DECLARE
@date_start smalldatetime = '20010101',
@date_stop smalldatetime = '20010101'
-- Ближайший период перед временным промежутком
SELECT @date_start = DATEADD(ss, FLOOR(DATEDIFF(ss, @date_start, @date_begin) / @period_length) * @period_length, @date_start)
SELECT @date_stop = DATEADD(ss, @period_length, @date_start)
-- Выборка за один временной промежуток
SELECT
@date_start,
[contract],
ROW_NUMBER() OVER
(
PARTITION BY [contract]
ORDER BY [date]
),
FIRST_VALUE([price]) OVER
(
PARTITION BY [contract]
ORDER BY [date]
),
LAST_VALUE([price]) OVER
(
PARTITION BY [contract]
ORDER BY [date]
RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
),
MIN([price]) OVER (PARTITION BY [contract]),
MAX([price]) OVER (PARTITION BY [contract])
FROM [dbo].[pays]
WHERE [date] >= @date_start and [date] < @date_stop
Задача 3. Дана таблица:
CREATE TABLE dbo.docs ( -- Клиент client_id int not null, -- Тип договора type_id int not null, -- Первый день действия договора first_date date not null, -- Последний день действия договора last_date date null )Написать запрос, который для данных:
DECLARE -- Тип договора @type_id int, -- Начало периода @first_date date, -- Конец периода @last_date dateВернет набор данных вида:
- client_id
- first_date
- last_date
Решение
DECLARE
@type_id int = 1,
@first_date date = '20130115',
@last_date date = '20130415';
WITH cte_dates AS
(
SELECT
client_id,
first_date,
LEAD (first_date) OVER
(
PARTITION BY client_id
ORDER BY first_date
) AS new_first_date,
LAG (last_date) OVER
(
PARTITION BY client_id
ORDER BY last_date
) AS old_last_date,
last_date
FROM dbo.docs
WHERE [type_id] = @type_id
AND first_date <= @last_date
AND (last_date IS NULL OR last_date >= @first_date)
)
SELECT
client_id,
IIF(first_date < @first_date, @first_date, first_date) AS first_date,
IIF(last_date > @last_date, @last_date, last_date) AS last_date
FROM cte_dates
WHERE
(
ABS(DATEDIFF(DAY, first_date, old_last_date)) > 1
AND
ABS(DATEDIFF(DAY, last_date, new_first_date)) > 1
)
OR (old_last_date IS NULL AND new_first_date IS NULL)
Комментарии
Отправить комментарий