Однажды пришлось решать некоторые аналитические задачи бизнеса. Как оказалось, новые оконные функции 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)
Комментарии
Отправить комментарий