Полная теория SQL для Data Analyst (на базе Northwind)
1. Базовые SELECT запросы
Теория
SELECT — основа всех запросов в SQL. Позволяет выбирать данные из таблиц.
SELECT столбцы FROM таблица WHERE условия;
Ключевые элементы
Элемент |
Описание |
Пример |
|---|---|---|
|
Выбрать все столбцы |
|
|
Выбрать конкретные столбцы |
|
|
Убрать дубликаты |
|
|
Фильтрация строк |
|
|
Логические операторы |
|
|
Проверка на вхождение |
|
|
Диапазон значений |
|
|
Поиск по шаблону |
|
|
Проверка на NULL |
|
|
Сортировка |
|
|
Ограничение количества строк |
|
LIKE операторы
Оператор |
Значение |
|---|---|
|
Любое количество символов |
|
Один символ |
|
Диапазон символов (не во всех СУБД) |
|
Исключение (не во всех СУБД) |
Примеры
-- Все продукты с ценой от 20 до 100
SELECT product_name, unit_price
FROM products
WHERE unit_price BETWEEN 20 AND 100;
-- Клиенты с email на gmail (если есть поле email)
SELECT company_name
FROM customers
WHERE contact_name LIKE '%@gmail.com';
-- Сотрудники, нанятые в 1993 году
SELECT first_name, last_name
FROM employees
WHERE EXTRACT(YEAR FROM hire_date) = 1993;
2. Агрегирующие функции и GROUP BY
Теория
Агрегирующие функции выполняют вычисления над набором строк и возвращают одно значение.
Функция |
Описание |
Пример |
|---|---|---|
|
Количество строк |
|
|
Количество не-NULL значений |
|
|
Количество уникальных значений |
|
|
Сумма значений |
|
|
Среднее значение |
|
|
Максимальное значение |
|
|
Минимальное значение |
|
GROUP BY
Группирует строки с одинаковыми значениями для агрегации.
Важное правило: Все неагрегированные столбцы в SELECT должны быть в GROUP BY.
SELECT
category_id,
COUNT(*) as products_count,
AVG(unit_price) as avg_price,
MIN(unit_price) as min_price,
MAX(unit_price) as max_price
FROM products
GROUP BY category_id;
Примеры
-- Количество заказов по сотрудникам
SELECT
employee_id,
COUNT(order_id) as orders_count,
SUM(freight) as total_freight
FROM orders
GROUP BY employee_id
ORDER BY orders_count DESC;
-- Средняя сумма заказа по клиентам
SELECT
customer_id,
COUNT(order_id) as order_count,
AVG(freight) as avg_freight
FROM orders
GROUP BY customer_id;
3. JOIN операции
Теория
JOIN объединяет данные из двух или более таблиц на основе связанных столбцов.
Типы JOIN
Тип |
Описание |
Диаграмма Венна |
|---|---|---|
|
Только совпадающие строки в обеих таблицах |
Пересечение |
|
Все строки из левой + совпадающие из правой |
Левая + пересечение |
|
Все строки из правой + совпадающие из левой |
Правая + пересечение |
|
Все строки из обеих таблиц |
Объединение |
|
Декартово произведение |
Все комбинации |
Синтаксис
SELECT t1.column, t2.column
FROM table1 t1
[INNER | LEFT | RIGHT | FULL] JOIN table2 t2
ON t1.key = t2.key;
Примеры
-- INNER JOIN: только заказы с клиентами
SELECT o.order_id, c.company_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id;
-- LEFT JOIN: все продукты, даже те, что не продавались
SELECT p.product_name, SUM(od.quantity) as total_sold
FROM products p
LEFT JOIN order_details od ON p.product_id = od.product_id
GROUP BY p.product_name;
-- Множественные JOIN
SELECT
o.order_id,
c.company_name,
e.first_name || ' ' || e.last_name as employee_name,
s.company_name as shipper_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
INNER JOIN employees e ON o.employee_id = e.employee_id
INNER JOIN shippers s ON o.ship_via = s.shipper_id;
4. Подзапросы (Subqueries)
Теория
Подзапрос — это запрос внутри другого запроса SQL.
Типы подзапросов
Тип |
Место использования |
Пример |
|---|---|---|
Скалярный |
SELECT |
|
Строковый |
FROM |
|
Табличный |
WHERE/HAVING |
|
Коррелированный |
WHERE/EXISTS |
|
Примеры
-- Подзапрос в WHERE: продукты дороже среднего
SELECT product_name, unit_price
FROM products
WHERE unit_price > (SELECT AVG(unit_price) FROM products);
-- Подзапрос с IN: клиенты из стран с заказами
SELECT company_name, country
FROM customers
WHERE country IN (
SELECT DISTINCT ship_country
FROM orders
);
-- Коррелированный подзапрос: сотрудники с заказами > среднего
SELECT
employee_id,
COUNT(order_id) as order_count
FROM orders o1
GROUP BY employee_id
HAVING COUNT(order_id) > (
SELECT AVG(order_count)
FROM (SELECT COUNT(*) as order_count
FROM orders
GROUP BY employee_id) as avg_orders
);
-- EXISTS: клиенты, которые делали заказы
SELECT company_name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);
Коррелированные vs Некоррелированные
Тип |
Описание |
Производительность |
|---|---|---|
Некоррелированный |
Выполняется один раз |
Лучше |
Коррелированный |
Выполняется для каждой строки внешнего запроса |
Хуже |
5. Оконные функции (Window Functions)
Теория
Оконные функции выполняют вычисления над набором строк, связанных с текущей строкой, без группировки (сохраняют все строки).
Синтаксис
Функция() OVER (
[PARTITION BY column1, column2] -- Разбиение на группы
[ORDER BY column3, column4] -- Порядок внутри группы
[ROWS | RANGE между строками] -- Определение окна
)
Классификация оконных функций
1. Ранжирующие функции
Функция |
Описание |
|---|---|
|
Уникальный номер строки (1,2,3,4,5) |
|
Ранг с пропусками при связях (1,2,2,4,5) |
|
Ранг без пропусков (1,2,2,3,4) |
|
Разбивает на n групп |
2. Агрегирующие оконные функции
Функция |
Описание |
|---|---|
|
Накопительная сумма |
|
Скользящее среднее |
|
Накопительный счетчик |
|
Максимум/минимум в окне |
3. Функции смещения
Функция |
Описание |
|---|---|
|
Доступ к предыдущей строке |
|
Доступ к следующей строке |
|
Первое значение в окне |
|
Последнее значение в окне |
Примеры
-- Ранжирование продуктов по цене в категории
SELECT
product_name,
category_id,
unit_price,
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY unit_price DESC) as row_num,
RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC) as rank,
DENSE_RANK() OVER (PARTITION BY category_id ORDER BY unit_price DESC) as dense_rank
FROM products;
-- Накопительная сумма (running total)
SELECT
order_date,
freight,
SUM(freight) OVER (ORDER BY order_date) as running_total
FROM orders;
-- Скользящее среднее за 3 дня
SELECT
order_date,
freight,
AVG(freight) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as moving_avg_3
FROM orders;
-- LAG/LEAD: сравнение с предыдущим заказом
SELECT
order_id,
order_date,
freight,
LAG(freight, 1) OVER (ORDER BY order_date) as prev_freight,
freight - LAG(freight, 1) OVER (ORDER BY order_date) as freight_diff
FROM orders;
-- Процент от общего
SELECT
category_id,
SUM(unit_price) as category_sum,
SUM(unit_price) / SUM(SUM(unit_price)) OVER() * 100 as pct_of_total
FROM products
GROUP BY category_id;
Оконные функции vs GROUP BY
Характеристика |
GROUP BY |
Оконные функции |
|---|---|---|
Количество строк |
Уменьшается |
Сохраняется |
Использование |
Простая агрегация |
Сложные аналитические расчеты |
Пример |
Среднее по категориям |
Ранжирование, скользящее среднее |
6. Работа с датами
Теория
Функции для работы с датами различаются в разных СУБД.
PostgreSQL
Функция |
Описание |
Пример |
|---|---|---|
|
Извлечение части даты |
|
|
Округление даты |
|
|
Разница между датами |
|
|
Разница в днях |
|
|
Добавление интервала |
|
|
Текущая дата |
|
|
Текущие дата и время |
|
MySQL
Функция |
Описание |
Пример |
|---|---|---|
|
Год |
|
|
Месяц |
|
|
День |
|
|
Добавление интервала |
|
|
Разница в днях |
|
|
Форматирование |
|
Примеры
-- Извлечение частей даты
SELECT
order_id,
order_date,
EXTRACT(YEAR FROM order_date) as year,
EXTRACT(MONTH FROM order_date) as month,
EXTRACT(DOW FROM order_date) as day_of_week,
EXTRACT(QUARTER FROM order_date) as quarter
FROM orders;
-- Группировка по месяцам
SELECT
DATE_TRUNC('month', order_date) as month,
COUNT(*) as orders_count
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;
-- Разница в днях
SELECT
order_id,
order_date,
shipped_date,
shipped_date - order_date as days_to_ship
FROM orders
WHERE shipped_date IS NOT NULL;
-- Возраст сотрудников
SELECT
first_name,
last_name,
birth_date,
AGE(CURRENT_DATE, birth_date) as age_interval,
EXTRACT(YEAR FROM AGE(CURRENT_DATE, birth_date)) as age_years
FROM employees;
-- Фильтрация по дате
SELECT *
FROM orders
WHERE order_date BETWEEN '1997-01-01' AND '1997-12-31';
-- Добавление интервала
SELECT
order_date,
required_date,
order_date + INTERVAL '7 days' as plus_7_days
FROM orders;
7. CASE выражения
Теория
CASE — условная логика в SQL (аналог IF-ELSE).
Синтаксис
CASE
WHEN условие1 THEN результат1
WHEN условие2 THEN результат2
ELSE результат_по_умолчанию
END
Простой vs Поисковый CASE
Тип |
Синтаксис |
Когда использовать |
|---|---|---|
Простой |
|
Равенство одному столбцу |
Поисковый |
|
Сложные условия |
Примеры
-- Классификация по цене
SELECT
product_name,
unit_price,
CASE
WHEN unit_price < 10 THEN 'Low'
WHEN unit_price BETWEEN 10 AND 50 THEN 'Medium'
WHEN unit_price > 50 THEN 'High'
ELSE 'Unknown'
END as price_category
FROM products;
-- Создание бакетов (группировка)
SELECT
CASE
WHEN EXTRACT(YEAR FROM AGE(birth_date)) < 30 THEN 'Junior'
WHEN EXTRACT(YEAR FROM AGE(birth_date)) BETWEEN 30 AND 45 THEN 'Middle'
ELSE 'Senior'
END as age_group,
COUNT(*) as employees_count
FROM employees
GROUP BY age_group;
-- Простой CASE
SELECT
order_id,
ship_country,
CASE ship_country
WHEN 'USA' THEN 'Domestic'
WHEN 'Canada' THEN 'North America'
WHEN 'Mexico' THEN 'North America'
ELSE 'International'
END as region
FROM orders;
-- PIVOT через CASE (транспонирование)
SELECT
EXTRACT(YEAR FROM order_date) as year,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 1 THEN freight ELSE 0 END) as jan,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 2 THEN freight ELSE 0 END) as feb,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 3 THEN freight ELSE 0 END) as mar
FROM orders
GROUP BY EXTRACT(YEAR FROM order_date);
-- CASE в ORDER BY
SELECT
product_name,
units_in_stock,
discontinued
FROM products
ORDER BY
CASE discontinued
WHEN 1 THEN 1
ELSE 0
END,
units_in_stock DESC;
8. CTE (Common Table Expressions)
Теория
CTE (WITH) — временный именованный набор результатов, существующий только в рамках одного запроса.
Синтаксис
WITH cte_name AS (
SELECT ...
)
SELECT * FROM cte_name;
Преимущества CTE
Преимущество |
Описание |
|---|---|
Читаемость |
Разбивает сложные запросы на логические блоки |
Многократное использование |
Можно использовать CTE несколько раз в запросе |
Рекурсия |
Поддержка рекурсивных запросов |
Альтернатива подзапросам |
Часто понятнее вложенных подзапросов |
Примеры
-- Базовое CTE
WITH high_value_orders AS (
SELECT
order_id,
customer_id,
SUM(unit_price * quantity * (1 - discount)) as order_total
FROM orders o
INNER JOIN order_details od ON o.order_id = od.order_id
GROUP BY order_id, customer_id
HAVING SUM(unit_price * quantity * (1 - discount)) > 1000
)
SELECT
c.company_name,
COUNT(hvo.order_id) as high_value_orders_count
FROM high_value_orders hvo
INNER JOIN customers c ON hvo.customer_id = c.customer_id
GROUP BY c.company_name
ORDER BY high_value_orders_count DESC;
-- Множественные CTE
WITH
monthly_sales AS (
SELECT
DATE_TRUNC('month', order_date) as month,
SUM(freight) as total_freight
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
),
monthly_growth AS (
SELECT
month,
total_freight,
LAG(total_freight) OVER (ORDER BY month) as prev_month_freight
FROM monthly_sales
)
SELECT
month,
total_freight,
prev_month_freight,
ROUND((total_freight - prev_month_freight) / prev_month_freight * 100, 2) as growth_pct
FROM monthly_growth
WHERE prev_month_freight IS NOT NULL;
-- Рекурсивное CTE (иерархия сотрудников)
WITH RECURSIVE employee_hierarchy AS (
-- Anchor: базовый уровень
SELECT
employee_id,
first_name || ' ' || last_name as employee_name,
reports_to,
0 as level
FROM employees
WHERE reports_to IS NULL
UNION ALL
-- Recursive: подчиненные
SELECT
e.employee_id,
e.first_name || ' ' || e.last_name,
e.reports_to,
eh.level + 1
FROM employees e
INNER JOIN employee_hierarchy eh ON e.reports_to = eh.employee_id
)
SELECT
LPAD(' ', level * 2) || employee_name as indented_name,
level
FROM employee_hierarchy
ORDER BY level, employee_name;
CTE vs Подзапрос
Критерий |
CTE |
Подзапрос |
|---|---|---|
Читаемость |
Высокая |
Снижается при вложенности |
Многократное использование |
Да |
Нет (только копированием) |
Рекурсия |
Да |
Нет |
Область видимости |
Только в запросе |
В родительском запросе |
9. HAVING vs WHERE
Теория
Ключевое слово |
Когда применяется |
Что фильтрует |
|---|---|---|
|
До |
Отдельные строки |
|
После |
Группы строк |
Порядок выполнения запроса
1. FROM / JOIN
2. WHERE ← фильтрация строк
3. GROUP BY ← группировка
4. HAVING ← фильтрация групп
5. SELECT
6. ORDER BY
7. LIMIT
Примеры
-- WHERE фильтрует до группировки
SELECT
country,
COUNT(*) as customers_count
FROM customers
WHERE country IN ('USA', 'Canada', 'UK') -- фильтруем строки
GROUP BY country
HAVING COUNT(*) > 2; -- фильтруем группы
-- Ошибка: нельзя использовать агрегацию в WHERE
-- WHERE COUNT(*) > 2 -- ОШИБКА!
-- Практический пример: продукты с ценой > 20,
-- у которых общая выручка > 5000
SELECT
p.product_name,
SUM(od.unit_price * od.quantity) as total_revenue
FROM products p
INNER JOIN order_details od ON p.product_id = od.product_id
WHERE p.unit_price > 20 -- фильтруем продукты ДО агрегации
GROUP BY p.product_name
HAVING SUM(od.unit_price * od.quantity) > 5000; -- фильтруем после агрегации
10. Производительность запросов
Теория
Оптимизация SQL запросов критически важна для работы с большими объемами данных.
Индексы
Индекс — структура данных, ускоряющая поиск строк.
-- Создание индекса
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_orders_date ON orders(order_date);
-- Составной индекс
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
-- Уникальный индекс
CREATE UNIQUE INDEX idx_customers_email ON customers(email);
Правила создания индексов:
Поле |
Индексировать |
Не индексировать |
|---|---|---|
|
Да |
- |
|
Да |
- |
|
Да |
- |
|
Да |
- |
Мало уникальных значений |
- |
Нет (например, boolean) |
Часто обновляемые |
- |
Нет (замедляет UPDATE) |
Анализ запросов
-- PostgreSQL: анализ плана выполнения
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 'ALFKI';
-- Включение таймингов
EXPLAIN (ANALYZE, BUFFERS, TIMING)
SELECT * FROM orders WHERE customer_id = 'ALFKI';
Проблемные паттерны
Паттерн |
Проблема |
Решение |
|---|---|---|
|
Избыточные данные |
Указывать только нужные столбцы |
|
Не использует индекс |
Избегать ведущего % |
|
Может не использовать индекс |
Использовать |
|
Неожиданные результаты |
Использовать |
Функции в WHERE |
Не использует индекс |
|
Пример оптимизации
-- Медленный запрос
SELECT *
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
WHERE c.country = 'USA'
AND EXTRACT(YEAR FROM o.order_date) = 1997
AND o.freight > 50;
-- Оптимизированный запрос
SELECT
o.order_id,
o.order_date,
o.freight,
c.company_name,
c.contact_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
WHERE c.country = 'USA'
AND o.order_date >= '1997-01-01'
AND o.order_date < '1998-01-01'
AND o.freight > 50;
-- Создание индексов для оптимизации
CREATE INDEX idx_customers_country ON customers(country);
CREATE INDEX idx_orders_date_freight ON orders(order_date, freight);
11. Нормализация базы данных
Теория
Нормализация — процесс организации данных для уменьшения избыточности и зависимостей.
Нормальные формы
НФ |
Определение |
Нарушение |
Решение |
|---|---|---|---|
1NF |
Атомарные значения, уникальные строки |
Массив в ячейке |
Вынести в отдельную таблицу |
2NF |
Нет частичных зависимостей (зависимость от части составного ключа) |
|
Вынести в отдельную таблицу |
3NF |
Нет транзитивных зависимостей (зависимость от неключевого поля) |
|
Вынести категории в отдельную таблицу |
Пример Northwind (3NF)
customers (customer_id PK)
↓
orders (order_id PK, customer_id FK)
↓
order_details (order_id PK, product_id PK)
↓
products (product_id PK, category_id FK, supplier_id FK)
↓ ↓
categories (category_id PK) suppliers (supplier_id PK)
Денормализация
Иногда для производительности намеренно нарушают нормализацию:
Создание витрин данных
Дублирование часто запрашиваемых полей
Предварительная агрегация
12. Типы данных в SQL
Основные типы
Категория |
PostgreSQL |
MySQL |
Описание |
|---|---|---|---|
Числовые |
|
|
Целые числа |
|
|
Точные десятичные |
|
|
|
Приближенные |
|
Строковые |
|
|
Фиксированная/переменная длина |
|
|
Длинный текст |
|
Дата/Время |
|
|
Дата, время, метка времени |
|
- |
Интервалы |
|
Булевы |
|
|
TRUE/FALSE |
Бинарные |
|
|
Бинарные данные |
Выбор типа данных
-- Правильно
customer_id VARCHAR(5) -- Фиксированный формат
unit_price DECIMAL(10,2) -- Деньги требуют точности
quantity SMALLINT -- Не может быть отрицательным
order_date DATE -- Только дата, без времени
is_active BOOLEAN -- Только два значения
-- Неправильно
customer_id TEXT -- Избыточно
unit_price FLOAT -- Проблемы с округлением
quantity VARCHAR(10) -- Нельзя суммировать
order_date TIMESTAMP -- Избыточная точность
is_active VARCHAR(3) -- Допускает 'yes', 'no', 'Y'...
13. Транзакции и ACID
Теория
Транзакция — последовательность операций, выполняемая как единое целое.
ACID
Свойство |
Описание |
Пример |
|---|---|---|
Atomicity (Атомарность) |
Все операции выполняются или ни одна |
Перевод денег: списание + зачисление |
Consistency (Согласованность) |
Данные остаются корректными |
После перевода сумма не изменилась |
Isolation (Изоляция) |
Параллельные транзакции не влияют друг на друга |
Два перевода не пересекаются |
Durability (Долговечность) |
Изменения сохраняются при сбое |
После подтверждения данные не теряются |
Уровни изоляции
Уровень |
Dirty Read |
Non-repeatable Read |
Phantom Read |
|---|---|---|---|
READ UNCOMMITTED |
✅ |
✅ |
✅ |
READ COMMITTED |
❌ |
✅ |
✅ |
REPEATABLE READ |
❌ |
❌ |
✅ |
SERIALIZABLE |
❌ |
❌ |
❌ |
Пример транзакции
BEGIN;
-- Создание заказа
INSERT INTO orders (customer_id, order_date, freight)
VALUES ('ALFKI', CURRENT_DATE, 20.00)
RETURNING order_id;
-- Добавление товаров в заказ
INSERT INTO order_details (order_id, product_id, unit_price, quantity, discount)
VALUES (100, 1, 18.00, 2, 0);
-- Обновление остатков
UPDATE products
SET units_in_stock = units_in_stock - 2
WHERE product_id = 1;
-- Проверка
SELECT * FROM products WHERE product_id = 1;
COMMIT; -- или ROLLBACK при ошибке
14. Управление доступом
Теория
Управление доступом определяет, кто может выполнять какие операции.
Основные привилегии
Привилегия |
Описание |
|---|---|
|
Чтение данных |
|
Добавление данных |
|
Обновление данных |
|
Удаление данных |
|
Создание объектов |
|
Удаление объектов |
|
Изменение структуры |
|
Выполнение функций |
Примеры
-- Создание пользователя
CREATE USER analyst WITH PASSWORD 'secure_password';
-- Предоставление прав на чтение
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analyst;
-- Предоставление прав на конкретные таблицы
GRANT SELECT, INSERT, UPDATE ON orders, order_details TO analyst;
-- Запрет на удаление
REVOKE DELETE ON orders FROM analyst;
-- Роли
CREATE ROLE data_analyst;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO data_analyst;
GRANT data_analyst TO analyst;
15. Распространенные ошибки на собеседовании
Ошибка |
Правильно |
|---|---|
Использование |
|
Агрегация без GROUP BY |
|
Фильтрация агрегатов в WHERE |
Использовать |
Сравнение разных типов |
|
|
|
Пропуск |
|
Шпаргалка для собеседования
Топ-10 запросов для практики
Средний чек по клиентам
Топ-10 продуктов по выручке
Динамика продаж по месяцам
RFM-анализ клиентов
Когортный анализ удержания
Скользящее среднее
Сравнение с предыдущим периодом (LAG)
Ранжирование внутри групп
Процент от общего
Иерархия сотрудников (рекурсивный CTE)
Ключевые концепции
INNER JOINvsLEFT JOINWHEREvsHAVINGRANK()vsDENSE_RANK()vsROW_NUMBER()UNIONvsUNION ALLEXISTSvsINCTE vs подзапрос
Индексы и их влияние
Эта теория покрывает все необходимые темы для успешного прохождения собеседования на позицию Data Analyst. Удачи! 🚀