Введение
SQL (Structured Query Language) — это язык структурированных запросов, который является стандартным инструментом для работы с реляционными базами данных. Для аналитика владение SQL — это не просто полезный навык, а необходимый инструмент ежедневной работы, позволяющий извлекать, анализировать и трансформировать данные без зависимости от разработчиков или специализированных BI-инструментов.
В современном мире данных SQL остается одним из самых востребованных навыков. По статистике рынка труда, более 80% вакансий для аналитиков требуют знания SQL. Это объясняется универсальностью языка — он работает практически со всеми популярными системами управления базами данных: PostgreSQL, MySQL, Oracle, SQL Server, SQLite и многими другими.
Основы реляционных баз данных
Ключевые понятия
Прежде чем погрузиться в изучение запросов, важно понимать основную терминологию:
Термин | Определение | Аналогия с Excel |
---|---|---|
База данных (Database) | Организованная коллекция данных | Файл Excel |
Таблица (Table) | Структурированный набор данных одного типа | Лист в Excel |
Строка (Row/Record) | Одна запись в таблице | Строка в Excel |
Столбец (Column) | Атрибут или поле данных | Столбец в Excel |
Первичный ключ (Primary Key) | Уникальный идентификатор строки | Уникальный ID строки |
Внешний ключ (Foreign Key) | Связь между таблицами | Ссылка на другой лист |
Индекс (Index) | Структура для ускорения поиска | Оглавление книги |
Структура данных для примеров
Для демонстрации SQL-запросов будем использовать базу данных интернет-магазина со следующими таблицами:
Таблица: customers (клиенты)
customer_id | first_name | last_name | city | registration_date | |
---|---|---|---|---|---|
1 | Иван | Петров | ivan@mail.ru | Москва | 2023-01-15 |
2 | Мария | Сидорова | maria@gmail.com | Санкт-Петербург | 2023-02-20 |
3 | Алексей | Иванов | alex@yandex.ru | Москва | 2023-03-10 |
Таблица: orders (заказы)
order_id | customer_id | order_date | total_amount | status |
---|---|---|---|---|
101 | 1 | 2023-06-01 | 5000.00 | completed |
102 | 2 | 2023-06-02 | 3500.00 | completed |
103 | 1 | 2023-06-03 | 2000.00 | cancelled |
Таблица: products (товары)
product_id | product_name | category | price | stock_quantity |
---|---|---|---|---|
1 | Ноутбук | Электроника | 45000.00 | 10 |
2 | Мышь | Аксессуары | 1500.00 | 50 |
3 | Клавиатура | Аксессуары | 3000.00 | 30 |
Таблица: order_items (позиции заказов)
order_item_id | order_id | product_id | quantity | price |
---|---|---|---|---|
1 | 101 | 1 | 1 | 45000.00 |
2 | 101 | 2 | 2 | 1500.00 |
3 | 102 | 3 | 1 | 3000.00 |
10 основных SQL-запросов для аналитика
1. SELECT — Выборка данных
SELECT — это фундаментальная команда SQL, которая используется для извлечения данных из таблиц. Это первый и самый важный запрос, который должен освоить каждый аналитик.
Базовый синтаксис:
sql
-- Выбор всех столбцов SELECT * FROM customers; -- Выбор конкретных столбцов SELECT first_name, last_name, email FROM customers; -- Выбор с псевдонимами SELECT first_name AS Имя, last_name AS Фамилия, email AS "Электронная почта" FROM customers;
Практические примеры:
sql
-- Получить список всех товаров с ценой и остатками SELECT product_name, price, stock_quantity FROM products; -- Вывести информацию о заказах с форматированием SELECT order_id AS "Номер заказа", order_date AS "Дата заказа", total_amount AS "Сумма", status AS "Статус" FROM orders;
Работа с вычисляемыми полями:
sql
-- Вычисление общей стоимости товаров на складе SELECT product_name, price, stock_quantity, price * stock_quantity AS total_value FROM products; -- Конкатенация строк SELECT first_name || ' ' || last_name AS full_name, email, city FROM customers;
2. WHERE — Фильтрация данных
WHERE позволяет фильтровать строки по определенным условиям. Это критически важный инструмент для работы с большими объемами данных.
Операторы сравнения:
Оператор | Описание | Пример использования |
---|---|---|
= | Равно | WHERE city = 'Москва' |
!= или <> | Не равно | WHERE status != 'cancelled' |
> | Больше | WHERE price > 1000 |
< | Меньше | WHERE quantity < 10 |
>= | Больше или равно | WHERE order_date >= '2023-01-01' |
<= | Меньше или равно | WHERE total_amount <= 5000 |
BETWEEN | В диапазоне | WHERE price BETWEEN 1000 AND 5000 |
IN | В списке значений | WHERE city IN ('Москва', 'Санкт-Петербург') |
LIKE | Поиск по шаблону | WHERE email LIKE '%@gmail.com' |
IS NULL | Пустое значение | WHERE phone IS NULL |
Примеры использования:
sql
-- Клиенты из Москвы SELECT * FROM customers WHERE city = 'Москва'; -- Заказы на сумму от 3000 до 10000 рублей SELECT * FROM orders WHERE total_amount BETWEEN 3000 AND 10000; -- Товары категории "Электроника" с ценой выше 10000 SELECT product_name, price FROM products WHERE category = 'Электроника' AND price > 10000; -- Клиенты с email на gmail или yandex SELECT first_name, last_name, email FROM customers WHERE email LIKE '%@gmail.com' OR email LIKE '%@yandex.ru';
Логические операторы:
sql
-- Комбинация условий с AND SELECT * FROM orders WHERE order_date >= '2023-06-01' AND order_date <= '2023-06-30' AND status = 'completed'; -- Использование OR и скобок для группировки SELECT * FROM products WHERE (category = 'Электроника' OR category = 'Аксессуары') AND price < 5000; -- Отрицание с NOT SELECT * FROM customers WHERE NOT city = 'Москва';
3. JOIN — Объединение таблиц
JOIN — это мощный инструмент для объединения данных из нескольких таблиц. Понимание различных типов JOIN критически важно для аналитика.
Типы JOIN:
Тип JOIN | Описание | Когда использовать |
---|---|---|
INNER JOIN | Возвращает только совпадающие записи из обеих таблиц | Когда нужны только связанные данные |
LEFT JOIN | Все записи из левой таблицы + совпадения из правой | Когда нужны все записи основной таблицы |
RIGHT JOIN | Все записи из правой таблицы + совпадения из левой | Редко используется, можно заменить LEFT JOIN |
FULL OUTER JOIN | Все записи из обеих таблиц | Когда нужны все данные независимо от связей |
CROSS JOIN | Декартово произведение таблиц | Для генерации всех возможных комбинаций |
INNER JOIN — Внутреннее объединение:
sql
-- Получить информацию о заказах с данными клиентов SELECT o.order_id, o.order_date, o.total_amount, c.first_name, c.last_name, c.email FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id; -- Детали заказов с информацией о товарах SELECT oi.order_id, p.product_name, p.category, oi.quantity, oi.price, oi.quantity * oi.price AS item_total FROM order_items oi INNER JOIN products p ON oi.product_id = p.product_id;
LEFT JOIN — Левое объединение:
sql
-- Все клиенты и их заказы (включая клиентов без заказов) SELECT c.customer_id, c.first_name, c.last_name, o.order_id, o.order_date, o.total_amount FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id; -- Найти клиентов без заказов SELECT c.customer_id, c.first_name, c.last_name, c.registration_date FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id WHERE o.order_id IS NULL;
Множественные JOIN:
sql
-- Полная информация о заказах SELECT o.order_id, o.order_date, c.first_name || ' ' || c.last_name AS customer_name, c.email, p.product_name, oi.quantity, oi.price FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id INNER JOIN order_items oi ON o.order_id = oi.order_id INNER JOIN products p ON oi.product_id = p.product_id WHERE o.status = 'completed';
4. GROUP BY — Группировка данных
GROUP BY используется для группировки строк с одинаковыми значениями в указанных столбцах. Это основа для агрегирования данных.
Агрегатные функции:
Функция | Описание | Пример |
---|---|---|
COUNT() | Количество строк | COUNT(*), COUNT(column) |
SUM() | Сумма значений | SUM(total_amount) |
AVG() | Среднее значение | AVG(price) |
MAX() | Максимальное значение | MAX(order_date) |
MIN() | Минимальное значение | MIN(price) |
Примеры группировки:
sql
-- Количество заказов по статусам SELECT status, COUNT(*) AS order_count FROM orders GROUP BY status; -- Сумма продаж по категориям товаров SELECT p.category, SUM(oi.quantity * oi.price) AS total_sales, COUNT(DISTINCT oi.order_id) AS order_count, SUM(oi.quantity) AS items_sold FROM order_items oi INNER JOIN products p ON oi.product_id = p.product_id GROUP BY p.category; -- Статистика по клиентам SELECT c.city, COUNT(DISTINCT c.customer_id) AS customer_count, COUNT(o.order_id) AS total_orders, AVG(o.total_amount) AS avg_order_value, SUM(o.total_amount) AS total_revenue FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.city;
Группировка по нескольким полям:
sql
-- Продажи по месяцам и категориям SELECT DATE_TRUNC('month', o.order_date) AS month, p.category, COUNT(DISTINCT o.order_id) AS orders, SUM(oi.quantity) AS units_sold, SUM(oi.quantity * oi.price) AS revenue FROM orders o INNER JOIN order_items oi ON o.order_id = oi.order_id INNER JOIN products p ON oi.product_id = p.product_id WHERE o.status = 'completed' GROUP BY DATE_TRUNC('month', o.order_date), p.category ORDER BY month, category;
5. HAVING — Фильтрация групп
HAVING используется для фильтрации результатов после группировки. В отличие от WHERE, который фильтрует строки до группировки, HAVING фильтрует уже сгруппированные данные.
Различие между WHERE и HAVING:
sql
-- WHERE фильтрует до группировки SELECT city, COUNT(*) AS customer_count FROM customers WHERE registration_date >= '2023-01-01' -- Фильтр до группировки GROUP BY city; -- HAVING фильтрует после группировки SELECT city, COUNT(*) AS customer_count FROM customers GROUP BY city HAVING COUNT(*) > 1; -- Фильтр после группировки
Практические примеры:
sql
-- Категории товаров с общей стоимостью на складе > 100000 SELECT category, SUM(price * stock_quantity) AS total_value, COUNT(*) AS product_count FROM products GROUP BY category HAVING SUM(price * stock_quantity) > 100000; -- Клиенты с более чем 2 заказами SELECT c.customer_id, c.first_name, c.last_name, COUNT(o.order_id) AS order_count, SUM(o.total_amount) AS total_spent FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id WHERE o.status = 'completed' GROUP BY c.customer_id, c.first_name, c.last_name HAVING COUNT(o.order_id) > 2; -- Товары со средним количеством в заказе > 2 SELECT p.product_id, p.product_name, AVG(oi.quantity) AS avg_quantity, COUNT(DISTINCT oi.order_id) AS times_ordered FROM products p INNER JOIN order_items oi ON p.product_id = oi.product_id GROUP BY p.product_id, p.product_name HAVING AVG(oi.quantity) > 2;
6. ORDER BY — Сортировка результатов
ORDER BY используется для сортировки результатов запроса по одному или нескольким столбцам.
Синтаксис и опции:
sql
-- Сортировка по возрастанию (по умолчанию) SELECT * FROM products ORDER BY price; -- Явное указание направления сортировки SELECT * FROM products ORDER BY price ASC; -- По возрастанию -- Сортировка по убыванию SELECT * FROM products ORDER BY price DESC; -- Сортировка по нескольким столбцам SELECT * FROM products ORDER BY category ASC, price DESC;
Практические примеры:
sql
-- Топ-5 самых дорогих товаров SELECT product_name, category, price FROM products ORDER BY price DESC LIMIT 5; -- Последние заказы SELECT o.order_id, o.order_date, c.first_name || ' ' || c.last_name AS customer, o.total_amount FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id ORDER BY o.order_date DESC LIMIT 10; -- Рейтинг клиентов по сумме покупок SELECT c.customer_id, c.first_name, c.last_name, COUNT(o.order_id) AS orders_count, SUM(o.total_amount) AS total_spent FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.first_name, c.last_name ORDER BY total_spent DESC NULLS LAST;
Сортировка с вычисляемыми полями:
sql
-- Сортировка по вычисляемому полю SELECT product_name, price, stock_quantity, price * stock_quantity AS total_value FROM products ORDER BY total_value DESC; -- Сортировка с использованием CASE SELECT order_id, total_amount, status, CASE WHEN status = 'pending' THEN 1 WHEN status = 'processing' THEN 2 WHEN status = 'completed' THEN 3 WHEN status = 'cancelled' THEN 4 END AS status_priority FROM orders ORDER BY status_priority, order_date DESC;
7. SUBQUERY — Вложенные запросы
Подзапросы позволяют использовать результат одного запроса внутри другого. Это мощный инструмент для решения сложных аналитических задач.
Подзапросы в WHERE:
sql
-- Заказы с суммой выше средней SELECT * FROM orders WHERE total_amount > ( SELECT AVG(total_amount) FROM orders ); -- Клиенты, которые делали заказы SELECT * FROM customers WHERE customer_id IN ( SELECT DISTINCT customer_id FROM orders ); -- Товары, которые ни разу не заказывали SELECT * FROM products WHERE product_id NOT IN ( SELECT DISTINCT product_id FROM order_items );
Подзапросы в SELECT:
sql
-- Добавление вычисляемого поля с подзапросом SELECT c.customer_id, c.first_name, c.last_name, (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.customer_id) AS order_count, (SELECT SUM(total_amount) FROM orders o WHERE o.customer_id = c.customer_id) AS total_spent FROM customers c; -- Процент от общей суммы продаж SELECT category, SUM(price * stock_quantity) AS category_value, ROUND( SUM(price * stock_quantity) * 100.0 / (SELECT SUM(price * stock_quantity) FROM products), 2 ) AS percentage FROM products GROUP BY category;
Подзапросы в FROM:
sql
-- Использование подзапроса как временной таблицы SELECT month, revenue, revenue - LAG(revenue) OVER (ORDER BY month) AS month_over_month FROM ( SELECT DATE_TRUNC('month', order_date) AS month, SUM(total_amount) AS revenue FROM orders WHERE status = 'completed' GROUP BY DATE_TRUNC('month', order_date) ) AS monthly_revenue; -- Анализ распределения заказов по клиентам SELECT order_frequency, COUNT(*) AS customer_count FROM ( SELECT customer_id, COUNT(*) AS order_frequency FROM orders GROUP BY customer_id ) AS customer_orders GROUP BY order_frequency ORDER BY order_frequency;
8. UNION — Объединение результатов
UNION используется для объединения результатов нескольких SELECT-запросов в один результирующий набор.
Типы объединений:
Оператор | Описание | Дубликаты |
---|---|---|
UNION | Объединяет и удаляет дубликаты | Удаляет |
UNION ALL | Объединяет все строки | Сохраняет |
INTERSECT | Возвращает общие строки | Удаляет |
EXCEPT | Возвращает строки из первого запроса, которых нет во втором | Удаляет |
Примеры использования UNION:
sql
-- Объединение клиентов и поставщиков в один список SELECT 'Customer' AS type, first_name || ' ' || last_name AS name, email, city FROM customers UNION SELECT 'Supplier' AS type, supplier_name AS name, supplier_email AS email, supplier_city AS city FROM suppliers; -- Все транзакции (покупки и возвраты) SELECT order_id AS transaction_id, 'Sale' AS transaction_type, order_date AS transaction_date, total_amount AS amount FROM orders WHERE status = 'completed' UNION ALL SELECT return_id AS transaction_id, 'Return' AS transaction_type, return_date AS transaction_date, -return_amount AS amount FROM returns;
Практические сценарии:
sql
-- Создание сводного отчета по активности SELECT 'Новые клиенты' AS metric, COUNT(*) AS value FROM customers WHERE registration_date >= CURRENT_DATE - INTERVAL '30 days' UNION ALL SELECT 'Новые заказы' AS metric, COUNT(*) AS value FROM orders WHERE order_date >= CURRENT_DATE - INTERVAL '30 days' UNION ALL SELECT 'Отмененные заказы' AS metric, COUNT(*) AS value FROM orders WHERE status = 'cancelled' AND order_date >= CURRENT_DATE - INTERVAL '30 days'; -- Комбинированный поиск по разным таблицам SELECT 'Product' AS result_type, product_id AS id, product_name AS name, 'Category: ' || category AS description FROM products WHERE product_name LIKE '%ноут%' UNION SELECT 'Customer' AS result_type, customer_id AS id, first_name || ' ' || last_name AS name, 'Email: ' || email AS description FROM customers WHERE first_name LIKE '%ноут%' OR last_name LIKE '%ноут%';
9. WINDOW FUNCTIONS — Оконные функции
Оконные функции выполняют вычисления по набору строк, связанных с текущей строкой, но в отличие от GROUP BY, они не сворачивают строки.
Основные оконные функции:
Функция | Описание | Применение |
---|---|---|
ROW_NUMBER() | Номер строки | Нумерация, удаление дубликатов |
RANK() | Ранг с пропусками | Рейтинги с учетом одинаковых значений |
DENSE_RANK() | Ранг без пропусков | Плотные рейтинги |
LAG() / LEAD() | Предыдущее/следующее значение | Сравнение с предыдущими периодами |
SUM() OVER | Накопительная сумма | Running total |
AVG() OVER | Скользящее среднее | Moving average |
Примеры ранжирования:
sql
-- Нумерация заказов по клиентам SELECT customer_id, order_id, order_date, total_amount, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) AS order_number FROM orders; -- Рейтинг продуктов по цене в каждой категории SELECT product_name, category, price, RANK() OVER (PARTITION BY category ORDER BY price DESC) AS price_rank, DENSE_RANK() OVER (PARTITION BY category ORDER BY price DESC) AS dense_rank FROM products; -- Топ-3 товара в каждой категории WITH ranked_products AS ( SELECT product_name, category, price, ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn FROM products ) SELECT * FROM ranked_products WHERE rn <= 3;
Аналитические функции:
sql
-- Сравнение с предыдущим периодом SELECT order_date, total_amount, LAG(total_amount, 1) OVER (ORDER BY order_date) AS previous_order, total_amount - LAG(total_amount, 1) OVER (ORDER BY order_date) AS difference FROM orders; -- Накопительная сумма продаж SELECT order_date, total_amount, SUM(total_amount) OVER (ORDER BY order_date) AS running_total, AVG(total_amount) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7 FROM orders WHERE status = 'completed'; -- Процент от общей суммы SELECT category, product_name, price, SUM(price) OVER (PARTITION BY category) AS category_total, ROUND(price * 100.0 / SUM(price) OVER (PARTITION BY category), 2) AS percentage FROM products;
10. CTE (Common Table Expressions) — Временные именованные результаты
CTE позволяют создавать временные именованные результаты запросов, которые существуют только в рамках одного SQL-выражения.
Синтаксис и преимущества:
sql
-- Базовый синтаксис CTE WITH cte_name AS ( SELECT ... ) SELECT * FROM cte_name; -- Множественные CTE WITH first_cte AS ( SELECT ... ), second_cte AS ( SELECT ... ) SELECT * FROM first_cte JOIN second_cte ON ...;
Практические примеры:
sql
-- Анализ клиентской базы с использованием CTE WITH customer_stats AS ( SELECT c.customer_id, c.first_name, c.last_name, COUNT(o.order_id) AS order_count, SUM(o.total_amount) AS total_spent, AVG(o.total_amount) AS avg_order_value FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.first_name, c.last_name ), customer_segments AS ( SELECT *, CASE WHEN total_spent > 10000 THEN 'VIP' WHEN total_spent > 5000 THEN 'Gold' WHEN total_spent > 0 THEN 'Regular' ELSE 'Inactive' END AS segment FROM customer_stats ) SELECT segment, COUNT(*) AS customer_count, AVG(order_count) AS avg_orders, AVG(total_spent) AS avg_revenue FROM customer_segments GROUP BY segment ORDER BY avg_revenue DESC;
Рекурсивные CTE:
sql
-- Иерархическая структура категорий WITH RECURSIVE category_tree AS ( -- Базовый случай: корневые категории SELECT category_id, category_name, parent_id, 0 AS level, category_name AS path FROM categories WHERE parent_id IS NULL UNION ALL -- Рекурсивная часть SELECT c.category_id, c.category_name, c.parent_id, ct.level + 1, ct.path || ' > ' || c.category_name FROM categories c INNER JOIN category_tree ct ON c.parent_id = ct.category_id ) SELECT * FROM category_tree ORDER BY path;
Сложные аналитические запросы с CTE:
sql
-- Когортный анализ удержания клиентов WITH cohorts AS ( SELECT customer_id, DATE_TRUNC('month', registration_date) AS cohort_month FROM customers ), customer_orders AS ( SELECT o.customer_id, DATE_TRUNC('month', o.order_date) AS order_month, c.cohort_month FROM orders o INNER JOIN cohorts c ON o.customer_id = c.customer_id ), cohort_data AS ( SELECT cohort_month, order_month, COUNT(DISTINCT customer_id) AS customers FROM customer_orders GROUP BY cohort_month, order_month ), cohort_size AS ( SELECT cohort_month, COUNT(DISTINCT customer_id) AS total_customers FROM cohorts GROUP BY cohort_month ) SELECT cd.cohort_month, cd.order_month, cd.customers, cs.total_customers, ROUND(cd.customers * 100.0 / cs.total_customers, 2) AS retention_rate FROM cohort_data cd INNER JOIN cohort_size cs ON cd.cohort_month = cs.cohort_month ORDER BY cd.cohort_month, cd.order_month;
Практические кейсы для аналитиков
Кейс 1: Анализ продаж по периодам
sql
-- Сравнение продаж по месяцам WITH monthly_sales AS ( SELECT DATE_TRUNC('month', order_date) AS month, COUNT(DISTINCT order_id) AS orders, COUNT(DISTINCT customer_id) AS unique_customers, SUM(total_amount) AS revenue, AVG(total_amount) AS avg_order_value FROM orders WHERE status = 'completed' GROUP BY DATE_TRUNC('month', order_date) ) SELECT month, orders, unique_customers, revenue, avg_order_value, LAG(revenue) OVER (ORDER BY month) AS prev_month_revenue, ROUND((revenue - LAG(revenue) OVER (ORDER BY month)) * 100.0 / NULLIF(LAG(revenue) OVER (ORDER BY month), 0), 2) AS growth_rate FROM monthly_sales ORDER BY month;
Кейс 2: ABC-анализ товаров
sql
-- ABC-анализ товарного ассортимента WITH product_sales AS ( SELECT p.product_id, p.product_name, p.category, SUM(oi.quantity) AS units_sold, SUM(oi.quantity * oi.price) AS revenue FROM products p LEFT JOIN order_items oi ON p.product_id = oi.product_id GROUP BY p.product_id, p.product_name, p.category ), product_ranking AS ( SELECT *, SUM(revenue) OVER (ORDER BY revenue DESC) AS cumulative_revenue, SUM(revenue) OVER () AS total_revenue FROM product_sales ) SELECT product_id, product_name, category, units_sold, revenue, ROUND(revenue * 100.0 / NULLIF(total_revenue, 0), 2) AS revenue_percentage, ROUND(cumulative_revenue * 100.0 / NULLIF(total_revenue, 0), 2) AS cumulative_percentage, CASE WHEN cumulative_revenue * 100.0 / NULLIF(total_revenue, 0) <= 80 THEN 'A' WHEN cumulative_revenue * 100.0 / NULLIF(total_revenue, 0) <= 95 THEN 'B' ELSE 'C' END AS abc_category FROM product_ranking ORDER BY revenue DESC;
Кейс 3: Анализ воронки продаж
sql
-- Построение воронки конверсии WITH funnel_stages AS ( SELECT 'Посетители сайта' AS stage, 1 AS stage_order, 10000 AS users UNION ALL SELECT 'Зарегистрировались', 2, (SELECT COUNT(*) FROM customers) UNION ALL SELECT 'Сделали первый заказ', 3, (SELECT COUNT(DISTINCT customer_id) FROM orders) UNION ALL SELECT 'Повторная покупка', 4, (SELECT COUNT(DISTINCT customer_id) FROM (SELECT customer_id, COUNT(*) AS orders FROM orders GROUP BY customer_id HAVING COUNT(*) > 1) t) ) SELECT stage, users, LAG(users) OVER (ORDER BY stage_order) AS prev_stage_users, ROUND(users * 100.0 / FIRST_VALUE(users) OVER (ORDER BY stage_order), 2) AS total_conversion, ROUND(users * 100.0 / NULLIF(LAG(users) OVER (ORDER BY stage_order), 0), 2) AS stage_conversion FROM funnel_stages ORDER BY stage_order;
Оптимизация запросов
Основные принципы оптимизации
Проблема | Решение | Пример |
---|---|---|
Полное сканирование таблицы | Использование индексов | CREATE INDEX idx_customer_email ON customers(email); |
SELECT * | Выбор только нужных столбцов | SELECT id, name вместо SELECT * |
Множественные подзапросы | Использование JOIN или CTE | WITH ... SELECT вместо вложенных SELECT |
Отсутствие фильтрации | Добавление WHERE | WHERE date >= '2023-01-01' |
Неоптимальный JOIN | Правильный порядок таблиц | Сначала маленькие таблицы |
Использование EXPLAIN
sql
-- Анализ плана выполнения запроса EXPLAIN (ANALYZE, BUFFERS) SELECT c.customer_id, COUNT(o.order_id) AS orders FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id;
Индексы и их влияние
sql
-- Создание индексов для часто используемых полей CREATE INDEX idx_orders_customer_id ON orders(customer_id); CREATE INDEX idx_orders_date_status ON orders(order_date, status); CREATE INDEX idx_products_category_price ON products(category, price); -- Составной индекс для сложных условий CREATE INDEX idx_orders_composite ON orders(customer_id, order_date, status) WHERE status = 'completed';
Полезные функции и операторы
Работа с датами
sql
-- Извлечение частей даты SELECT order_date, EXTRACT(YEAR FROM order_date) AS year, EXTRACT(MONTH FROM order_date) AS month, EXTRACT(DAY FROM order_date) AS day, EXTRACT(WEEK FROM order_date) AS week, EXTRACT(QUARTER FROM order_date) AS quarter FROM orders; -- Арифметика дат SELECT order_date, order_date + INTERVAL '7 days' AS week_later, order_date - INTERVAL '1 month' AS month_ago, CURRENT_DATE - order_date AS days_ago FROM orders; -- Форматирование дат SELECT order_date, TO_CHAR(order_date, 'DD.MM.YYYY') AS formatted_date, TO_CHAR(order_date, 'Day, DD Month YYYY') AS long_date, TO_CHAR(order_date, 'Q') AS quarter FROM orders;
Работа со строками
sql
-- Функции обработки строк SELECT first_name, UPPER(first_name) AS uppercase, LOWER(first_name) AS lowercase, LENGTH(first_name) AS length, SUBSTRING(email FROM 1 FOR POSITION('@' IN email) - 1) AS email_name, REPLACE(email, '@', ' at ') AS replaced_email, TRIM(' ' FROM first_name) AS trimmed FROM customers; -- Регулярные выражения SELECT * FROM customers WHERE email ~ '^[a-zA-Z0-9._%+-]+@gmail\.com$';
Условная логика
sql
-- CASE выражения SELECT order_id, total_amount, CASE WHEN total_amount < 1000 THEN 'Small' WHEN total_amount < 5000 THEN 'Medium' WHEN total_amount < 10000 THEN 'Large' ELSE 'VIP' END AS order_size, CASE status WHEN 'completed' THEN 'Завершен' WHEN 'cancelled' THEN 'Отменен' WHEN 'pending' THEN 'Ожидает' ELSE 'Неизвестно' END AS status_ru FROM orders; -- COALESCE и NULLIF SELECT product_name, COALESCE(discount_price, price) AS final_price, COALESCE(description, 'Описание отсутствует') AS description, NULLIF(stock_quantity, 0) AS available_stock FROM products;
Работа с различными СУБД
Особенности синтаксиса разных СУБД
Функция | PostgreSQL | MySQL | SQL Server | Oracle |
---|---|---|---|---|
Ограничение строк | LIMIT 10 | LIMIT 10 | TOP 10 | ROWNUM <= 10 |
Текущая дата | CURRENT_DATE | CURDATE() | GETDATE() | SYSDATE |
Конкатенация | || | CONCAT() | + | || |
Извлечение года | EXTRACT(YEAR FROM date) | YEAR(date) | YEAR(date) | EXTRACT(YEAR FROM date) |
Преобразование типов | CAST(x AS type) | CAST(x AS type) | CAST(x AS type) | TO_NUMBER, TO_DATE |
Примеры адаптации запросов
sql
-- PostgreSQL SELECT first_name || ' ' || last_name AS full_name FROM customers LIMIT 10; -- MySQL SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM customers LIMIT 10; -- SQL Server SELECT TOP 10 first_name + ' ' + last_name AS full_name FROM customers; -- Oracle SELECT first_name || ' ' || last_name AS full_name FROM customers WHERE ROWNUM <= 10;
Практические задания для закрепления
Задание 1: Базовые запросы
- Выберите всех клиентов из Москвы, зарегистрированных в 2023 году
- Найдите товары с ценой от 1000 до 5000 рублей
- Отсортируйте заказы по дате в обратном порядке
Задание 2: Группировка и агрегация
- Посчитайте количество заказов и общую сумму продаж по месяцам
- Найдите среднюю стоимость товара в каждой категории
- Определите топ-5 клиентов по сумме покупок
Задание 3: Объединения таблиц
- Получите список заказов с информацией о клиентах
- Найдите товары, которые ни разу не заказывали
- Создайте полный отчет по заказам с деталями
Задание 4: Оконные функции
- Пронумеруйте заказы каждого клиента
- Рассчитайте накопительную сумму продаж по дням
- Найдите разницу между текущим и предыдущим заказом
Задание 5: Комплексный анализ
- Проведите RFM-анализ клиентской базы
- Постройте когортный анализ по месяцам регистрации
- Создайте дашборд основных метрик продаж
Типичные ошибки начинающих
Ошибки в синтаксисе и логике
Ошибка | Неправильно | Правильно |
---|---|---|
Группировка без агрегации | SELECT name, city FROM customers GROUP BY city | SELECT city, COUNT(*) FROM customers GROUP BY city |
NULL в сравнениях | WHERE phone = NULL | WHERE phone IS NULL |
Деление на ноль | revenue / users | revenue / NULLIF(users, 0) |
Неправильный JOIN | FROM orders, customers | FROM orders JOIN customers ON ... |
HAVING вместо WHERE | SELECT * FROM orders HAVING status = 'completed' | SELECT * FROM orders WHERE status = 'completed' |
Проблемы производительности
sql
-- Плохо: подзапрос выполняется для каждой строки SELECT *, (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.customer_id) AS orders FROM customers c; -- Хорошо: JOIN с группировкой SELECT c.*, COALESCE(o.order_count, 0) AS orders FROM customers c LEFT JOIN ( SELECT customer_id, COUNT(*) AS order_count FROM orders GROUP BY customer_id ) o ON c.customer_id = o.customer_id;
Советы по изучению SQL
План обучения для начинающих
- Неделя 1-2: Основы SELECT, WHERE, ORDER BY
- Неделя 3-4: JOIN, GROUP BY, агрегатные функции
- Неделя 5-6: Подзапросы, UNION, CASE
- Неделя 7-8: Оконные функции, CTE
- Далее: Оптимизация, специфика СУБД, практические проекты
Рекомендации по практике
- Используйте реальные данные — работайте с открытыми датасетами
- Решайте практические задачи — участвуйте в SQL-челленджах
- Анализируйте чужой код — изучайте решения опытных аналитиков
- Документируйте запросы — пишите понятные комментарии
- Тестируйте на разных данных — проверяйте граничные случаи
Ресурсы для самостоятельного изучения
Онлайн-платформы:
- SQLZoo — интерактивные уроки
- HackerRank SQL — задачи разной сложности
- LeetCode Database — подготовка к собеседованиям
- Mode Analytics SQL Tutorial — практические кейсы
- W3Schools SQL — справочник с примерами
Практические датасеты:
- Kaggle Datasets — тысячи реальных датасетов
- Google BigQuery Public Datasets — большие данные
- PostgreSQL Sample Databases — учебные базы
- MySQL Sample Databases — примеры схем
Заключение
SQL остается фундаментальным навыком для любого аналитика данных. Освоение представленных 10 типов запросов обеспечит прочную основу для решения большинства аналитических задач. Начните с простых SELECT-запросов, постепенно добавляя сложность через JOIN, группировки и оконные функции.
Ключевые принципы успешного освоения SQL:
- Регулярная практика — пишите запросы каждый день
- Понимание данных — изучайте структуру и связи таблиц
- Оптимизация — думайте о производительности
- Читаемость — пишите понятный и документированный код
- Непрерывное обучение — изучайте новые функции и подходы
Помните, что SQL — это не просто язык запросов, а мощный инструмент для превращения сырых данных в ценные бизнес-инсайты. Каждый освоенный тип запроса расширяет ваши возможности как аналитика и повышает вашу ценность на рынке труда.
Начните применять полученные знания на практике уже сегодня, и через несколько месяцев вы будете уверенно решать сложные аналитические задачи с помощью SQL!