# Оконные функции


## Знакомство с окошками


Оконные функции позволяют выполнять вычисления над набором строк, связанных с текущей строкой, **не группируя** их в одну строку результата. Это как «заглянуть в окно» соседних строк, пока вы работаете с текущей.

### 🛒 Пример таблицы `products`

| emoji | product_name | category | price |
| --- | --- | --- | --- |
| 🍎 | Яблоки красные | Фрукты | 120 |
| 🍌 | Бананы спелые | Фрукты | 80 |
| 🥕 | Морковь свежая | Овощи | 60 |
| 🍫 | Шоколад молочный | Сладости | 150 |
| 🍓 | Клубника | Фрукты | 200 |
| 🍅 | Помидоры | Овощи | 90 |
| 🍬 | Конфеты ассорти | Сладости | 110 |

### ⚖️ Сравнение GROUP BY и оконных функций

#### GROUP BY - агрегация с потерей деталей

```sql
SELECT category, AVG(price) as avg_price, COUNT(*) as count FROM products GROUP BY category;
```

| category | avg_price | count |
| --- | --- | --- |
| Фрукты | 133 | 3 |
| Овощи | 75 | 2 |
| Сладости | 130 | 2 |

**Что произошло:** Все строки свернулись в группы, мы потеряли информацию об отдельных продуктах.

#### Оконные функции - анализ с сохранением деталей

```sql
SELECT
  product_id, emoji, product_name, category, price, AVG(price)
OVER( PARTITION BY category) as avg_in_category
FROM products;
```

| emoji | product_name | category | price | avg |
| --- | --- | --- | --- | --- |
| 🍎 | Яблоки красные | Фрукты | 120 | 133 |
| 🍌 | Бананы спелые | Фрукты | 80 | 133 |
| 🍓 | Клубника | Фрукты | 200 | 133 |
| 🥕 | Морковь свежая | Овощи | 60 | 75 |
| 🍅 | Помидоры | Овощи | 90 | 75 |
| 🍫 | Шоколад молочный | Сладости | 150 | 130 |
| 🍬 | Конфеты ассорти | Сладости | 110 | 130 |

**Что произошло:** Мы видим все исходные строки, но добавили столбец со средней ценой по категории!

### 🎯 Практические примеры оконных функций

#### 1. Ранжирование продуктов по цене внутри категории

```sql
SELECT
 emoji, product_name, category, price,
 RANK() OVER ( PARTITION BY  category ORDER BY  price  DESC )
 as rank_in_category
FROM  products;
```

| emoji | product_name | category | price | rank |
| --- | --- | --- | --- | --- |
| 🍓 | Клубника | Фрукты | 200 | 1 |
| 🍎 | Яблоки красные | Фрукты | 120 | 2 |
| 🍌 | Бананы спелые | Фрукты | 80 | 3 |
| 🍅 | Помидоры | Овощи | 90 | 1 |
| 🥕 | Морковь свежая | Овощи | 60 | 2 |
| 🍫 | Шоколад молочный | Сладости | 150 | 1 |
| 🍬 | Конфеты ассорти | Сладости | 110 | 2 |

#### 2. Накопительная сумма продаж по дате

```sql
SELECT
  emoji, product_name, sale_date, price,
  SUM (price) OVER ( ORDER BY sale_date) as running_total
FROM  products;
```

| emoji | product_name | price | total |
| --- | --- | --- | --- |
| 🍎 | Яблоки красные | 120 | 120 |
| 🍌 | Бананы спелые | 80 | 200 |
| 🥕 | Морковь свежая | 60 | 260 |
| 🍫 | Шоколад молочный | 150 | 410 |
| 🍓 | Клубника | 200 | 610 |
| 🍅 | Помидоры | 90 | 700 |
| 🍬 | Конфеты ассорти | 110 | 810 |

#### 3. Сравнение цены с самой дорогой в категории

```sql
SELECT
 emoji, product_name, category, price,
 MAX (price)  OVER  ( PARTITION BY  category) as max_price_in_category,
 price * 100.0 / MAX(price) OVER( PARTITION BY category) as percent_of_max
 FROM products;
```

| emoji | product_name | category | price | max | percent |
| --- | --- | --- | --- | --- | --- |
| 🍎 | Яблоки красные | Фрукты | 120 | 200 | 60.0 |
| 🍌 | Бананы спелые | Фрукты | 80 | 200 | 40.0 |
| 🍓 | Клубника | Фрукты | 200 | 200 | 100.0 |
| 🥕 | Морковь свежая | Овощи | 60 | 90 | 66.7 |
| 🍅 | Помидоры | Овощи | 90 | 90 | 100.0 |
| 🍫 | Шоколад молочный | Сладости | 150 | 150 | 100.0 |
| 🍬 | Конфеты ассорти | Сладости | 110 | 150 | 73.3 |

### 📊 Ключевые отличия

| GROUP BY | Оконные функции |
| --- | --- |
| Сворачивает строки в группы | Сохраняет все исходные строки |
| Возвращает только агрегированные данные | Возвращает исходные данные + вычисления |
| Используется для итогов и сводок | Используется для аналитики и ранжирования |
| Теряет детализацию | Сохраняет детализацию |

#### 🎯 Когда что использовать?

- **GROUP BY**: когда нужны итоги, суммы, средние цены по категориям
- **Оконные функции**: когда нужно анализировать продукты в контексте их категории, сохраняя детализацию

#### 💡 Простая аналогия:

- **GROUP BY** - это как получить отчет по отделам магазина (средние цены на фрукты, овощи, сладости)
- **Оконные функции** - это как видеть цену каждого продукта вместе со средней ценой по его категории

## Просто пронумеровать


Итак, в новой главе мы разберемся с анализом продаж кофе. Начнем с простой задачи: у нас было 5 продаж, но вот незадача - первичные ключи начинаются с 200. Пронумеруйте строки от 1  до 5. Используйте  ROW_NUMBER() OVER

## В порядке возрастания


Продолжаем нумеровать чашки с кофе с помощью ROW_NUMBER . Теперь пронумеруем с 1 до 5 по возрастанию цены.

## В обратном порядке


Иногда, для составления топов или рейтингов нам нужно начать с самого большого значения. Теперь пронумеруем тоже с 1 до 5 на на этот раз по убыванию, начиная с самого дорогого кофе.

## Сортировка по вычисляемому


Окей, немного более сложное задание: посчитаем, какой кофе стоит дороже в пересчете на миллилитры. Поделим стоимость на объем и отсортируем по этому значению. Значение округлим до 2 знаков после запятой.

## Несколько рангов


Одну и ту же таблицу можно ранжировать несколькими способами. В колонке p_rank сделаем нумерацию по цене (начиная от самой большой), а в колонке v_rank сделаем нумерацию по объему. Поскольку ROW_NUMBER нумерует последовательно, строки с одинаковыми значениями получат разные номера -- это нормально.

## Накопительный итог


Сегодня было продано 5 чашек кофе. Выручка одной продажи — это цена, умноженная на объём: price * vol. Выведите, как менялась выручка после продажи каждой чашки!

## Накопительный двух показателей


Теперь посчитаем накопительный итог по цене и по миллилитрам

## Вклад в общую сумму


Каждая чашка — это маленькая часть нашего успеха. Но насколько маленькая часть? Выручка одной продажи — это цена, умноженная на объём: price * vol. Посчитайте, какой процент от общей выручки составляет каждая продажа.

Выведите колонки id, name, price, vol и percent_of_total_revenue (процент, округлённый до двух знаков).
