← К оглавлению занятия

🏠 Домашнее задание

📁 Блок: SQL / MySQL / Оконные функции ⏱️ Время изучения: ~90 мин 🎯 Сложность: Продвинутая

⚡ Кратко: домашка

Функции смещения и выбора позволяют смотреть не только на текущую строку, но и на соседние или крайние строки внутри окна.

LAG() возвращает значение из предыдущей строки, а LEAD() — из следующей строки в порядке, заданном внутри OVER.

FIRST_VALUE(), LAST_VALUE() и NTH_VALUE() выбирают первое, последнее или n-е значение в окне.

Ключевая конструкция урока: function(column) OVER (PARTITION BY group_column ORDER BY sort_column).

Минимальный пример: LAG(creation_date) OVER (PARTITION BY supplier_id ORDER BY creation_date) показывает предыдущую дату заказа поставщика.

Топ-3 ошибки: забыть ORDER BY, перепутать направление LEAD/LAG, использовать LAST_VALUE без явной рамки окна.

🏠 Домашнее задание

Домашнее задание из источника выполняется на таблице purchase_order_details.

  1. Для каждого product_id вывести inventory_id, предыдущий и последующий inventory_id по убыванию quantity.
  2. Вывести максимальный и минимальный unit_price для каждого order_id с помощью FIRST_VALUE.
  3. Вывести order_id и разницу между unit_price заказа и минимальным unit_price в рамках одного заказа двумя способами: через FIRST_VALUE и MIN.
  4. Присвоить ранг каждой строке с помощью RANK по убыванию quantity.
  5. Из предыдущего запроса выбрать только строки с рангом до 10 включительно.

Пошаговое решение и проверка

ДЗ 1

SELECT
    product_id,
    inventory_id,
    quantity,
    LAG(inventory_id) OVER (
        PARTITION BY product_id
        ORDER BY quantity DESC
    ) AS previous_inventory_id,
    LEAD(inventory_id) OVER (
        PARTITION BY product_id
        ORDER BY quantity DESC
    ) AS next_inventory_id
FROM purchase_order_details;

Окно делится по продукту, а порядок задаётся количеством по убыванию.

ДЗ 2

SELECT
    order_id,
    unit_price,
    FIRST_VALUE(unit_price) OVER (
        PARTITION BY order_id
        ORDER BY unit_price DESC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS max_unit_price,
    FIRST_VALUE(unit_price) OVER (
        PARTITION BY order_id
        ORDER BY unit_price ASC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS min_unit_price
FROM purchase_order_details;

Один и тот же FIRST_VALUE даёт максимум или минимум в зависимости от сортировки.

ДЗ 3

SELECT
    order_id,
    unit_price,
    unit_price - FIRST_VALUE(unit_price) OVER (
        PARTITION BY order_id
        ORDER BY unit_price ASC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS diff_by_first_value,
    unit_price - MIN(unit_price) OVER (
        PARTITION BY order_id
    ) AS diff_by_min
FROM purchase_order_details;

Оба способа должны дать одинаковую разницу, потому что минимальная цена заказа одна и та же для всех строк заказа.

ДЗ 4-5

WITH ranked_details AS (
    SELECT
        *,
        RANK() OVER (
            ORDER BY quantity DESC
        ) AS quantity_rank
    FROM purchase_order_details
)
SELECT *
FROM ranked_details
WHERE quantity_rank <= 10;

CTE нужен, чтобы отфильтровать результат ранжирования во внешнем запросе.