Здравствуйте, друзья! В сегодняшней статье мы рассмотрим SQL как инструмент для анализа данных и ответим на самые частые вопросы начинающих. Вы узнаете, что такое SQL и почему он так важен в работе с данными, как с его помощью извлекать и фильтровать информацию из баз данных, объединять таблицы через JOIN, а также выполнять агрегирование и более сложные аналитические расчёты с помощью оконных функций и подзапросов.
Мы объясним отличия между популярными системами баз данных (MySQL, PostgreSQL, SQL Server, Oracle) и расскажем, как SQL сочетается с другими инструментами анализа — например, языком Python, Excel или BI-платформами. Вы увидите примеры типичных SQL-запросов для решения аналитических задач (по анализу продаж, поведению пользователей и т.д.).
Отдельно обсудим, какие онлайн-курсы по SQL для анализа данных представлены на платформе «Учись Онлайн Ру» и как выбрать подходящий курс, упомянем полезную литературу для самостоятельного изучения, и дадим советы, как избежать распространённых ошибок. Приступим!
SQL (от англ. Structured Query Language — «язык структурированных запросов») – это язык для управления и извлечения данных в реляционных базах данных. Проще говоря, с помощью SQL можно из больших таблиц выбрать нужные данные, фильтровать их по определённым условиям, сортировать результаты и представлять их в удобном виде1. SQL используется повсеместно: запросы на этом языке лежат в основе работы онлайн-сервисов (интернет-магазины, банковские системы, соцсети) – везде, где информация хранится в табличном формате.
Для специалистов по данным SQL давно стал базовым и незаменимым инструментом. Аналитики данных, разработчики, тестировщики, администраторы баз данных и даже маркетологи регулярно используют SQL в работе1. Грамотно составленные SQL-запросы помогают находить ценные инсайты (например, выявлять поведение клиентов), оценивать эффективность бизнес-процессов и автоматизировать отчёты1. Неудивительно, что знание SQL входит в список обязательных навыков для начинающих аналитиков. По данным Glassdoor (2024 год), SQL назван самым востребованным техническим навыком и упоминается более чем в 2 200 вакансиях в сфере ИТ1. Иными словами, если вы планируете работать с данными, без владения SQL не обойтись.
При анализе данных в SQL чаще всего применяются операторы выборки (запросы SELECT). Ниже перечислены ключевые части типичного SQL-запроса и их назначение:
SELECT – выбрать определённые столбцы или вычисляемые показатели из таблицы. Пример: SELECT name, age FROM users; извлекает колонки name и age из таблицы users.
FROM – указать, из какой таблицы (или таблиц) брать данные.
WHERE – задать условие фильтрации строк. Пример: ... WHERE age > 30 ограничит выборку записями, где значение поля age превышает 30.
ORDER BY – отсортировать полученные результаты по одному или нескольким полям (по возрастанию ASC или убыванию DESC). По умолчанию сортировка – по возрастанию.
GROUP BY – сгруппировать результаты по указанному полю(полям) для вычисления агрегатных показателей по группам (например, сумм, средних значений).
HAVING – установить условие фильтрации для уже сгруппированных данных (аналогично WHERE, но применяется к агрегированным результатам).
JOIN – объединить записи из нескольких таблиц по общему ключу. С помощью JOIN можно связать данные, распределённые по разным таблицам (подробно см. вопрос №5).
Эти команды составляют основу синтаксиса SQL-запросов для анализа. Как правило, типичный запрос аналитика выглядит как сочетание: SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... (при необходимости дополняется соединением таблиц через JOIN). Понимая назначение каждого из этих ключевых слов, вы сможете читать и составлять запросы для самых разных задач.
Для отбора нужных строк из таблицы SQL предлагает условное выражение WHERE в запросах SELECT. Клаузу WHERE указывают после списка таблиц (FROM) и перед остальными частями запроса. В условии можно задавать разнообразные критерии:
Проверка равенства/неравенства (= и <>), сравнение чисел (>, <, >=, <=).
Поиск значений из заданного списка: оператор IN (...) проверяет принадлежность значения к одному из перечисленных.
Фильтрация по диапазону: оператор BETWEEN ... AND ... отберёт значения в заданном интервале (включительно).
Поиск по шаблону текста: оператор LIKE (с символами подстановки % и _) позволяет найти строки по части текста, например WHERE name LIKE 'Андре%' найдёт имена, начинающиеся на «Андре».
Проверка на пустые значения: IS NULL (или IS NOT NULL) – проверяет, имеет ли поле значение NULL (не задано).
Условия можно комбинировать с помощью логических операторов AND (логическое И) и OR (логическое ИЛИ), а также группировать скобками для нужного порядка вычисления. Например, запрос:
SELECT product_name, price FROM products WHERE category = 'Electronics' AND (price BETWEEN 1000 AND 5000 OR rating >= 4); отберёт товары из категории Electronics, у которых цена лежит в диапазоне 1000–5000 либо рейтинг не ниже 4. Таким образом, WHERE позволяет гибко фильтровать данные по необходимым условиям. Для фильтрации агрегированных результатов используется похожий оператор HAVING (см. вопрос №8), но на этапе выборки отдельных строк применяют именно WHERE.
Для сортировки результатов запроса используется клаузa ORDER BY. Она позволяет упорядочить выборку по одному или нескольким полям, в порядке возрастания или убывания. Например:
SELECT name, salary FROM employees WHERE department = 'Sales' ORDER BY salary DESC; Этот запрос отсортирует сотрудников отдела Sales по полю salary в порядке от большего к меньшему (DESC означает descending, по убыванию). Если нужен обратный порядок (по возрастанию), можно явно указать ASC (ascending) или опустить указание – по умолчанию сортировка восходящая. Также можно перечислить несколько столбцов после ORDER BY, чтобы установить вложенный порядок сортировки (сначала по первому полю, затем по второму – для записей с равным первым полем, и т.д.).
Для ограничения количества выводимых результатов в разных диалектах SQL используются специальные конструкции:
MySQL, PostgreSQL, SQLite: ключевое слово LIMIT. Например, добавив LIMIT 10 в конец запроса, мы получим только первые 10 строк из отсортированного результата:
SELECT name, salary FROM employees ORDER BY salary DESC LIMIT 10; Microsoft SQL Server: оператор SELECT TOP N (указывается перед списком столбцов, например: SELECT TOP 10 name, salary ...).
Oracle: в современных версиях используется синтаксис FETCH FIRST N ROWS ONLY после ORDER BY, либо можно обернуть запрос и воспользоваться условием по псевдоколонке ROWNUM в внешнем запросе.
Несмотря на различия синтаксиса, цель у этих конструкций одна – вернуть ограниченное число строк из результата. Комбинируя сортировку и ограничение, можно, например, получить топ-5 записей с самыми высокими значениями показателя. Важно: при использовании LIMIT/TOP всегда задавайте конкретный порядок через ORDER BY, иначе вы получите произвольные 10 строк (потому что без явной сортировки СУБД не гарантирует определённый порядок результатов).
JOIN – это операция в SQL, позволяющая объединять записи из двух (или более) таблиц в один результирующий набор. Соединение выполняется по общему признаку (значению в определённом столбце), который присутствует в обеих таблицах. Например, если у нас есть таблица Customers (информация о клиентах, поле customer_id) и таблица Orders (информация о заказах, поле customer_id указывает, какой клиент сделал заказ), то с помощью JOIN можно связать заказы с данными о соответствующем клиенте.
Синтаксис соединения выглядит так:
SELECT ... FROM Customers JOIN Orders ON Customers.customer_id = Orders.customer_id; Здесь после ключевого слова JOIN указывается вторая таблица, а после ON – условие, по которому строки связываются (в данном случае требуем равенства идентификатора клиента в обеих таблицах). Результат такого запроса будет содержать колонки из обеих таблиц, объединяя каждую пару записей Customer–Order, у которых совпал customer_id. Иными словами, каждая запись из Customers соединится с записями из Orders, относящимися к тому же клиенту.
По умолчанию используется внутреннее соединение – INNER JOIN, которое возвращает только строки, нашедшие соответствия в обеих таблицах (т.е. только те заказы, для которых есть запись о клиенте, и только тех клиентов, у которых есть хотя бы один заказ). Если в запросе написать просто JOIN без указания типа, СУБД трактует это как INNER JOIN. Существуют и другие виды соединений (левое, правое, полное внешние соединения), которые позволяют вернуть также несоответствующие записи из одной или обеих таблиц – о них подробнее расскажем в следующем вопросе. В целом, JOIN – незаменимый инструмент для комбинирования данных из нескольких таблиц. Практически любая более-менее сложная аналитическая выборка в реляционной базе использует соединения, чтобы собрать нужные атрибуты, хранящиеся в разных таблицах, в едином результате.
Существует несколько типов операции JOIN, отличающихся тем, какие строки попадают в результирующую выборку:
INNER JOIN (внутреннее соединение) – включает только те строки, для которых найдено совпадение в обеих таблицах. Используется, когда нужны только связанные данные из обеих таблиц. Пример: INNER JOIN между Customers и Orders даст только тех клиентов, у которых есть хотя бы один заказ (и только заказы тех клиентов, которые есть в таблице клиентов).
LEFT JOIN (левое внешнее соединение) – возвращает все строки из левой (первой указанной) таблицы и добавляет к ним данные из правой таблицы, если найдено совпадение. Если для некоторых строк левой таблицы соответствий в правой нет, они всё равно будут в результате, но с NULL в полях правой таблицы. Этот тип применяют, когда важно сохранить все записи из первой таблицы. Пример: LEFT JOIN заказов к таблице клиентов вернёт список всех клиентов, дополнив информацией о заказах там, где они есть (для клиентов без заказов поля заказа будут NULL).
RIGHT JOIN (правое внешнее соединение) – симметричен LEFT JOIN, возвращает все строки из правой таблицы и присоединяет к ним совпадающие строки из левой. RIGHT JOIN используется реже (потому что тот же результат можно получить перестановкой таблиц и применив LEFT JOIN). Применяется, когда нужно сохранить все записи второй таблицы.
FULL JOIN (полное внешнее соединение) – возвращает все строки из обеих таблиц, объединяя их там, где есть совпадения, и заполняя NULL там, где соответствующей пары нет ни с одной стороны. Полное соединение полезно, когда нужно слить два набора данных целиком, сохранив несопоставленные записи с каждой стороны. Пример: FULL JOIN таблиц Sales2019 и Sales2020 позволит увидеть и продажи за 2019, которых не было в 2020, и новые продажи 2020 года, отсутствовавшие в 2019 (с соответствующими пропусками).
CROSS JOIN (декартово произведение) – специальный тип соединения, который возвращает комбинации каждой строки первой таблицы с каждой строкой второй. В результате получается декартово произведение множеств записей. CROSS JOIN редко используется в прикладном анализе, разве что для специфических задач (например, сгенерировать все возможные сочетания параметров).
Выбор типа JOIN зависит от поставленной задачи. Если нужны только пересечения – применяем INNER JOIN. Если необходимо проанализировать все элементы одной таблицы в контексте данных другой – выручает LEFT JOIN (или RIGHT JOIN). Для комплексного объединения без потери данных – FULL JOIN. Понимание различий между JOIN-ами важно, чтобы получать корректный результат и не упустить нужную информацию.
Агрегатные функции в SQL – это специальные функции, которые рассчитывают некоторый сводный показатель по набору строк (группе строк). К основным агрегатным функциям относятся:
COUNT() – подсчитывает количество элементов. Например, COUNT(*) возвращает число строк в выборке, а COUNT(user_id) – число непустых значений user_id.
SUM() – вычисляет сумму значений по столбцу (обычно числовому).
AVG() – вычисляет среднее арифметическое значение по столбцу.
MIN() и MAX() – находят минимальное и максимальное значение в наборе.
Эти функции особенно полезны в аналитике, когда нужно получить сводные показатели из данных. С их помощью можно узнать общее количество записей (например, число продаж или клиентов), суммарные показатели (общую выручку, суммарное количество чего-либо), средние значения (средний чек, среднюю оценку), а также экстремальные значения (минимальную и максимальную величины).
Агрегатные функции часто используются вместе с группировкой данных (GROUP BY). Без GROUP BY такие функции вычисляют один результат по всей выборке (или всей таблице). Если же указать группировку, то результат будет рассчитан для каждой группы строк. Пример: запрос
SELECT department, COUNT(*) AS emp_count, AVG(salary) AS avg_salary FROM employees GROUP BY department; разобьёт таблицу сотрудников на группы по значению поля department и выдаст по каждой группе количество сотрудников (COUNT(*)) и среднюю зарплату (AVG(salary)). В результате мы получим одну строку на каждый отдел с этими показателями.
Агрегатные функции позволяют превращать сырые данные в осмысленные метрики, необходимые для анализа (итоги, статистики, показатели эффективности). Например, рассчитав SUM(amount) продаж по месяцам, можно построить график динамики выручки; зная COUNT(*) пользователей по странам – оценить распределение аудитории; имея AVG(duration) сеанса – сделать вывод о вовлечённости пользователей. В этом сила агрегатных функций: они быстро дают сводную информацию, которую иначе пришлось бы вычислять вручную из множества строк.
Оператор GROUP BY позволяет сгруппировать строки, имеющие одинаковые значения в одном или нескольких полях, чтобы затем применить к каждой группе агрегатные функции. При группировке каждая уникальная величина (или комбинация значений по нескольким полям) образует отдельную группу, и запрос возвращает по одной строке на группу – обычно с указанием признака группы и рассчитанных агрегатных показателей для неё.
Важно правило: в запросе с GROUP BY в списке SELECT должны присутствовать либо поля, по которым происходит группировка, либо агрегатные функции. Нельзя выбирать "сырые" поля, не указанные в GROUP BY, потому что после группировки отдельные значения вне групп теряют смысл (СУБД просто не знает, какую конкретно из множества возможных величин вы хотите вывести).
Пример: запрос
SELECT department, COUNT(*) AS emp_count, AVG(salary) AS avg_salary FROM employees GROUP BY department HAVING COUNT(*) > 5; сгруппирует сотрудников по отделам и покажет для каждого отдела количество сотрудников и среднюю зарплату, но только для тех отделов, где сотрудников больше 5 (условие HAVING COUNT(*) > 5). Здесь мы видим работу HAVING: этот оператор вводит условие на уже агрегированные группы. Он похож на WHERE, но применяется после выполнения группировки (к результатам GROUP BY). С помощью HAVING можно отфильтровать группы по значениям агрегатных функций. Например, в нашем запросе мы отсеяли отделы с шестью и менее сотрудниками.
Ключевое отличие: WHERE фильтрует отдельные строки до группировки, а HAVING фильтрует после группировки, отбирая целые группы. Обычно процесс идёт так: сначала WHERE ограничивает данные (например, выбрать продажи только определённого года), затем выполняется GROUP BY, и затем HAVING (если задан) фильтрует уже сводные результаты. Совместное использование GROUP BY и HAVING позволяет гибко строить отчёты – агрегировать данные и сразу же применять к агрегатам условия. Например, можно запросом получить список продуктов, чьи продажи превышают определённый порог, или пользователей, совершивших более N действий за период, и т.д.
Подзапрос (subquery) – это запрос, вложенный внутрь другого SQL-запроса. Он выполняется отдельно, а его результат используется внешним (главным) запросом. Подзапрос заключают в круглые скобки, и он может находиться в разных местах команды:
В условии WHERE (или HAVING) – для фильтрации данных на основе результата другого запроса. Например, можно выбрать товары, цена которых выше средней по всем товарам, сравнив цену с результатом подзапроса:
SELECT product_name FROM products WHERE price > (SELECT AVG(price) FROM products); Здесь внутренний запрос вычисляет среднюю цену, и внешний возвращает только те товары, у которых price больше этого среднего.
В разделе FROM – как виртуальная таблица (так называемое derived table или subselect). Вы можете написать подзапрос, который соберёт промежуточные данные, и затем обращаться к нему, как к обычной таблице. Частый приём – агрегировать данные в подзапросе, а во внешнем запросе сделать JOIN с детализацией. Например, подзапрос может посчитать продажи по категориям, а внешний запрос присоединит к нему таблицу категорий для получения названий и описаний.
В списке выбираемых полей (SELECT) – как скалярный подзапрос, возвращающий одно значение для каждой строки внешнего результата. Например, можно запросом по таблице Customers вывести для каждого клиента его ID и (через подзапрос) общую сумму его заказов из таблицы Orders:
SELECT c.customer_id, (SELECT SUM(amount) FROM orders o WHERE o.customer_id = c.customer_id) AS total_spent FROM customers c; Здесь подзапрос выполняется для каждой строки клиентов и суммирует суммы заказов именно этого клиента.
Подзапросы позволяют разбивать сложные задачи на этапы и получать промежуточные результаты внутри одного общего запроса. Они бывают независимые (как в примерах выше) или коррелированные (зависящие от каждой строки внешнего запроса). В анализе данных подзапросы применяются очень широко. Например, если нужно выбрать пользователей, чья активность выше среднего по всем, можно сравнить их показатель с средним по таблице через подзапрос. Либо найти товары, продажи которых максимальны в своей категории – тоже удобно сделать подзапросом внутри HAVING или через конструкцию ... WHERE sale = (SELECT MAX(sale) FROM ... category ...).
Стоит отметить, что многие задачи, решаемые подзапросами, можно выполнить и с помощью JOIN или CTE (см. следующий вопрос). Например, выражение price > (SELECT AVG(price) ...) из примера можно переписать через JOIN с агрегированной по всем товарам таблицей. Выбор способа зависит от удобства и читаемости. Подзапросы хороши, когда запрос иначе получился бы чрезмерно сложным или нужно получить результат вычисления и использовать его в разных местах. Умение писать подзапросы значительно расширяет возможности аналitika: они позволяют делать вложенные выборки, сравнивать строки с агрегатами, выбирать топ-N элементов в подзапросе (например, взять товары, входящие в топ-3 продаж, через WHERE product_id IN (SELECT ... ORDER BY ... LIMIT 3)).
Главное – следить за эффективностью: подзапросы, особенно коррелированные, могут выполняться неэффективно (например, многократно для каждой строки внешнего запроса). В таких случаях лучше переработать логику на JOIN или воспользоваться CTE.
CTE (общее табличное выражение, от англ. Common Table Expression) – это конструкция в SQL, позволяющая определить временный именованный результат (по сути, дать имя подзапросу) с помощью клаузулы WITH и затем использовать его в основном запросе. CTE во многом похоже на создание временной таблицы прямо в запросе. Синтаксис:
WITH cte_name AS ( <подзапрос> ) SELECT ... FROM cte_name JOIN ... -- можно обращаться к cte_name как к обычной таблице ... Объявив CTE, вы можете использовать его имя в секциях FROM/JOIN основного запроса (а при необходимости и в нескольких местах запроса). Некоторые СУБД также позволяют рекурсивные CTE (когда выражение cte_name ссылается само на себя внутри определения – для решения задач обхода деревьев, графов).
Когда стоит применять CTE:
Разбиение сложного запроса на части (повышение читаемости). Если запрос очень сложный, разбив его на логические блоки через CTE, вы делаете код понятнее. Каждое WITH-выражение выполняет свою подзадачу. Например, сначала можно одним CTE агрегировать данные, вторым – отфильтровать или вычислить дополнительные поля, а затем в основном SELECT соединить результаты. Такой подход делает структуру решения более последовательной (сначала подготовили данные, потом использовали).
Избежание дублирования подзапросов. Если один и тот же подзапрос нужен в нескольких местах, CTE поможет не писать его повторно. Вы определяете его один раз в секции WITH, а потом используете по имени. Это и читаемость улучшает, и облегчает поддержку кода (править надо в одном месте).
Рекурсивные вычисления. Как упомянуто, CTE поддерживают рекурсию (ключевое слово WITH RECURSIVE в PostgreSQL, например). Это позволяет решать задачи вроде иерархических запросов (например, выбросить организационную структуру "начальник–подчинённый" любой глубины) чистым SQL, без необходимости вытаскивать данные в приложение. Рекурсивные CTE – продвинутый случай, для начала достаточно понимать простые CTE.
Пример, когда CTE упрощает задачу: допустим, нужно вывести сотрудников, чья зарплата выше средней по их отделу. Можно сначала отдельным запросом вычислить среднюю зарплату по каждому отделу, а потом сравнить с ней зарплату каждого сотрудника. С CTE это выглядит так:
WITH dept_avg AS ( SELECT department, AVG(salary) AS avg_salary FROM employees GROUP BY department ) SELECT e.name, e.department, e.salary FROM employees e JOIN dept_avg d ON e.department = d.department WHERE e.salary > d.avg_salary; Здесь dept_avg – CTE, в котором рассчитаны средние зарплаты по отделам. В основном запросе мы просто присоединили эти значения к каждой строке сотрудника и отфильтровали по условию. Без CTE пришлось бы либо сделать подзапрос внутри WHERE, либо повторить логику вычисления AVG в условии, что менее элегантно.
Таким образом, CTE делает запрос многоступенчатым, но структурированным. Он особенно полезен при построении сложных аналитических вычислений, шаг за шагом. Стоит отметить, что некоторые СУБД (например, MySQL до версии 8) не поддерживали CTE, но сейчас в основных системах (PostgreSQL, SQL Server, Oracle, MySQL 8+) эта возможность есть. Использовать или нет CTE – во многом вопрос стиля и удобства чтения запроса. В критичных по производительности местах иногда обычный подзапрос может работать быстрее (зависит от оптимизатора), но в большинстве случаев CTE не уступает. Для обучения и практики SQL CTE очень рекомендуются – они прививают навык декомпозиции сложных задач.
Оконные функции – это функции в SQL, которые позволяют выполнять вычисления по группе строк (окну) относительно каждой строки, не сворачивая эти строки в одну. В отличие от агрегатных функций с GROUP BY, оконные функции не уменьшают число выводимых строк: каждая исходная запись остаётся в результате, но к ней добавляется результат расчёта по окну, определённому для этой строки.
Синтаксис оконной функции включает выражение OVER (PARTITION BY ... ORDER BY ...). PARTITION BY задаёт, как разделить набор строк на части (по аналогии с GROUP BY, но без схлопывания), а ORDER BY внутри OVER определяет порядок следования строк внутри каждой такой группы (это важно для функций, зависящих от порядка, например, ранжирования или накопительных сумм). Обе части необязательны: можно указать только PARTITION (тогда функции применятся в рамках каждой группы, но без учёта порядка) или только ORDER (тогда окно – весь набор, но с определённой сортировкой).
Примеры оконных функций и их применение:
Ранжирование и нумерация строк. Функции ROW_NUMBER(), RANK(), DENSE_RANK() и др. присваивают каждой строке номер или ранг в пределах своего окна. Например, если мы хотим пронумеровать продажи по убыванию суммы, мы можем:
SELECT sale_id, amount, RANK() OVER(ORDER BY amount DESC) AS sales_rank FROM sales; Здесь RANK() пронумерует продажи: самая большая сумма получит ранг 1, одинаковые суммы – одинаковый ранг, и т.д. Ранжирование полезно для выделения топ-N элементов или определения позиции элемента в рейтинге (без агрегации остальных).
Агрегаты как оконные. Любые агрегатные функции (SUM, AVG, COUNT, MIN, MAX) можно применять как оконные, добавляя OVER. Например,
SELECT department, name, salary, AVG(salary) OVER(PARTITION BY department) AS dept_avg_salary FROM employees; в каждой строке сотрудника добавит колонку со средним зарплат по его отделу (PARTITION BY department создаёт окно = все сотрудники этого отдела). В отличие от GROUP BY, мы получаем и подробные данные по каждому сотруднику, и показатель по группе одновременно. Это удобно, когда нужно сравнить каждую запись с общим показателем группы (например, кто получает выше среднего, каков отрыв продаж от среднерыночных и т.п.).
Функции смещения (лаги и лиды). Функции LAG(value, N) и LEAD(value, N) позволяют получить значение из предыдущей или следующей строки в пределах окна. Классический пример – расчет разницы с предыдущим периодом:
SELECT date, revenue, revenue - LAG(revenue, 1) OVER(ORDER BY date) AS diff_from_prev_day FROM daily_revenue; Здесь LAG(revenue, 1) подставит выручку предыдущего дня (определяемого ORDER BY date), и мы сразу вычисляем разницу. Аналогично можно получить следующую запись (LEAD), что полезно для вычисления например разницы с следующим годом (если надо посмотреть рост).
Накопительные итоги и скользящие расчёты. Используя окно с ORDER BY и указанием "rame" (рамки), можно рассчитывать кумулятивные суммы, скользящие средние и т.п. Например, накопительная сумма:
SELECT date, SUM(revenue) OVER(ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_revenue FROM daily_revenue; Здесь каждая строка будет содержать сумму с начала до текущей даты. Аналогично, можно задать окно скользящего среднего за N предыдущих периодов.
Оконные функции чрезвычайно мощны для анализа данных. Они позволяют решать задачи, которые стандартным SQL (с GROUP BY) приходилось обходить с помощью подзапросов или более сложных JOIN-ов. Благодаря оконным функциям, вы можете в одном запросе, например, получить каждую транзакцию и долю этой транзакции от общего объёма продаж, или отметить первую/последнюю покупку клиента, или рассчитать процент попадания значения в топ-квартиль и т.д. Большинство современных СУБД поддерживают оконные функции согласно стандарту SQL:2003 (Oracle, SQL Server, PostgreSQL, MySQL 8+ и др.). Для аналитика умение пользоваться оконными функциями открывает новый уровень возможностей работы с данными.
Основные конструкции SQL стандартизированы, но каждая популярная система управления базами данных (СУБД) имеет свои диалект SQL – небольшой набор отличий, расширений и особенностей. В контексте анализа данных важно знать следующие различия:
Синтаксис запросов и ключевые слова. Некоторые операторы имеют разный синтаксис. Мы уже упоминали пример с ограничением числа строк: LIMIT (MySQL, Postgres) vs TOP N (MS SQL) vs FETCH FIRST N ROWS (Oracle). Другой пример – функция получения текущей даты: в MySQL это CURDATE(), в PostgreSQL – CURRENT_DATE (стандарт), в SQL Server – GETDATE(), а в Oracle – SYSDATE. Вокруг кавычек: стандарт SQL требует заключать идентификаторы (имена столбцов, таблиц) в двойные кавычки "id", тогда как, например, MySQL по умолчанию позволяет обратные апострофы `id`. В Microsoft T-SQL кавычки " вообще не используются для идентификаторов (там нужны квадратные скобки [id] или опять же апострофы при включённом ANSI-режиме).
Типы данных. Набор доступных типов данных и их поведение могут различаться. Например, Oracle поддерживает собственный тип NUMBER(p,s) для чисел произвольной точности, а в MySQL эквивалент – DECIMAL(p,s). Для текстов: в SQL Server строковые типы NVARCHAR/NCHAR хранят Unicode, а в MySQL для Unicode нужно выбирать utf8/utf8mb4 как кодировку поля VARCHAR. PostgreSQL имеет много необычных типов: JSON, массивы, IP-адреса, геометрические типы и т.д. – что расширяет возможности анализа (в MySQL JSON тоже есть начиная с 5.7, но реализован иначе). Также поведение AUTO_INCREMENT (MySQL) vs SERIAL (Postgres) vs IDENTITY (MS SQL) – то, как делаются автонумерующиеся ID – реализовано по-своему в каждой СУБД.
Встроенные функции и процедуры. Каждая СУБД предлагает свой набор функций для обработки строк, дат, математических операций. Например, функция для извлечения части строки: в MySQL/SQL Server – SUBSTRING(), в Oracle/Postgres – SUBSTR(). Для объединения строк: стандартный оператор || работает в Oracle/Postgres, но в MySQL/SQL Server нужно использовать функцию CONCAT(). Функции для работы с NULL тоже названы по-разному: IFNULL(x, val) в MySQL, ISNULL(x, val) в T-SQL, NVL(x, val) в Oracle – все они делают то же, что стандартная COALESCE(x, val) (возвращают значение val, если x оказался NULL).
Процедурные возможности и расширения. Многие СУБД имеют возможности, выходящие за рамки стандартного SQL, особенно в части написания скриптов, процедур и триггеров. Oracle использует язык PL/SQL – полноценный процедурный язык с переменными, циклами, и т.д. Microsoft SQL Server имеет T-SQL (Transact-SQL), который расширяет SQL конструкциями программирования. PostgreSQL поддерживает PL/pgSQL и даже другие языки (можно писать хранимые функции на Python, JavaScript, etc.). Это важно, если вы автоматизируете что-то внутри базы (например, сложные расчёты или ETL-процессы) – синтаксис и возможности будут разными.
Различия в поведении и нюансах. Есть мелочи, которые могут сбивать с толку: например, MySQL по умолчанию допускает запрос SELECT * FROM sales GROUP BY region без указания агрегатов по всем не-группируемым полям (он просто возьмёт первые попавшиеся значения), тогда как PostgreSQL/Oracle строго потребуют агрегировать или включить поле в GROUP BY. Постепенно MySQL тоже ужесточил этот момент в режиме ANSI. Oracle до версии 12 не поддерживал WITH (CTE) и LIMIT – приходилось писать громоздкие подзапросы с ROWNUM. В SQL Server функции TOP и OFFSET ... FETCH (аналог LIMIT) работают только с ORDER BY. Эти нюансы изучаются по мере необходимости – главное знать, что при переносе запросов из одной базы в другую иногда приходится чуть переписывать синтаксис.
В целом, навык SQL в целом переносим между СУБД. Если вы освоили написание запросов на одном диалекте, разобраться в другом – дело нескольких дней практики. Учитесь основам на любой СУБД, но будьте готовы уточнять синтаксис под конкретную базу. Например, перенос запроса из PostgreSQL в Oracle может потребовать заменить ILIKE на UPPER() + LIKE (Oracle не знает ILIKE), функцию генерации случайного числа RANDOM() на DBMS_RANDOM.VALUE, и т.д. При работе над реальными проектами обычно оговаривается, под какую базу нужно писать код, и вы пользуетесь справочником именно для неё. Платформа «Учись Онлайн Ру» в своём каталоге отмечает, на какую СУБД ориентирован курс (MS SQL, PostgreSQL, универсальный ANSI SQL и т.п.), что тоже помогает выбрать обучение под ваши нужды.
NULL в SQL обозначает отсутствие значения (неизвестное значение). Важно понимать, что NULL – это не ноль, не пустая строка и не пробел, а именно специальная "метка" отсутствия. Работа с NULL имеет ряд особенностей:
Логические выражения с NULL. Любое сравнение с NULL возвращает неопределённое значение. Например, условие age = NULL не будет истинным ни для какого age – даже если age тоже NULL. Поэтому для проверки на NULL используют отдельный оператор: IS NULL (и соответственно IS NOT NULL). Пример: WHERE email IS NULL отберёт строки, где поле email пустое. Противоположное: WHERE email IS NOT NULL – только строки с непустым email.
Трёхзначная логика. В SQL помимо TRUE и FALSE есть значение UNKNOWN (неизвестно), которое получается в логических выражениях, если участвует NULL. Например, условие 5 > NULL – не TRUE и не FALSE, а UNKNOWN. При фильтрации WHERE такие строки отбрасываются (WHERE пропускает только TRUE). Поэтому, если у вас в данных есть NULL, учитывайте их явно. Например, запрос WHERE status = 'active' не выберет записи, у которых статус NULL – даже если вам казалось, что NULL можно приравнять к "не активен", СУБД так не считает без явного указания.
Агрегатные функции и NULL. Как правило, агрегатные функции игнорируют NULL. COUNT(field) посчитает только строки, где field не NULL. SUM(price) просуммирует только заданные значения (NULL пропустит). Однако есть нюанс: COUNT(*) считает строки вообще независимо от NULL (потому что * – не поле, а просто счётчик строк). Ещё пример: AVG(field) вычисляется как сумма делить на количество ненулевых значений, то есть NULL не учитываются в среднем. Кроме того, если все значения, которые должна агрегировать функция, оказались NULL, то и результат агрегата будет NULL (например, MAX по пустому набору возвращает NULL). В общем, NULL обычно "ничего не даёт" агрегатам.
Замена NULL на значения. Иногда в отчётах или при вычислениях нужно трактовать NULL как какую-то дефолтную величину (например, нет данных – считать 0). Для этого существуют функции: стандартная COALESCE(value, default) – возвращает value, если он не NULL, иначе default. В Oracle аналог – NVL(value, default), в SQL Server – ISNULL(value, default). Пример: COALESCE(phone, 'не указан') вернёт либо телефон, либо строку "не указан", если телефон = NULL. При числовых расчётах можно делать COALESCE(amount, 0) – подставит 0 вместо отсутствующего значения. Эти функции очень полезны, чтобы избавить результаты от "пустоты" и, например, сложить фактическое значение с 0 вместо получения NULL.
NULL при соединениях и вычислениях. Если делаете LEFT JOIN, у "хвостов" без пары справа будут NULL в полях правой таблицы (см. вопрос №6). В арифметических и строковых операциях NULL обычно "зараждает" результат: 'Hello ' || NULL || '!' = NULL (конкатенация с неизвестным даёт неизвестно), 5 + NULL = NULL. Поэтому, если в каких-то вычислениях возможен NULL, часто оборачивают выражения COALESCE(..., substitute), чтобы не потерять всю формулу. Например, средняя оценка sum(points)/count(*) будет NULL, если count=0 (деление на 0), но можно написать COALESCE(sum(points)/NULLIF(count(*),0), 0) – тут мы обезопасились от нуля в делителе и заменили NULL на 0.
Вывод: NULL обозначает отсутствие данных, и SQL требует явно обрабатывать этот случай. Всегда проверяйте поля, которые могут быть NULL, с помощью IS NULL/IS NOT NULL. Учитывайте NULL при составлении условий (например, для текстового поля можно написать ... OR field IS NULL, если хотите включить "пустые" значения в выборку). При агрегировании помните, что NULL-значения могут влиять на результат (например, среднее число детей среди клиентов посчитается только по тем, у кого заполнено поле число детей). И не забывайте, что в результатах отчёта лучше заменить NULL на понятные обозначения (черточки, слова "нет данных" и т.д.) – для этого есть функции, как мы показали выше.
Python и SQL – мощная связка для аналитика данных. Обычно SQL применяется для извлечения и предварительной обработки данных (выгрузить нужные наборы из базы), а Python – для глубокой обработки, анализа и визуализации этих данных. Интеграция происходит следующим образом:
Подключение Python к базе данных. Существует множество библиотек, которые позволяют Python-сценарию подключиться к СУБД. Для разных баз есть свои драйверы: например, psycopg2 для PostgreSQL, pymysql или mysql-connector для MySQL/MariaDB, cx_Oracle для Oracle, pyodbc или pymssql для MS SQL Server, и т.д. Также есть универсальный слой SQLAlchemy, который поддерживает подключение ко всем основным СУБД. С помощью этих библиотек можно отправлять SQL-запросы прямо из Python-кода и получать результаты.
Выгрузка данных в DataFrame через pandas. Библиотека pandas – основной инструмент анализа данных в Python – имеет встроенную функцию для SQL-запросов: pandas.read_sql_query(sql, connection). Она выполняет указанный SQL (через открытое соединение) и возвращает результат в виде DataFrame (таблица в памяти Python). Это очень удобно: например, можно так:
import pandas as pd import sqlite3 conn = sqlite3.connect('example.db') df = pd.read_sql_query( "SELECT region, SUM(sales) AS total_sales FROM deals GROUP BY region;", conn ) Здесь мы подключились к файлу-базе SQLite, выполнили SQL-запрос сумм продаж по регионам, и получили DataFrame df с двумя колонками: region и total_sales. Далее с этим DataFrame можно работать средствами pandas как с обычной таблицей.
Анализ и обработка в Python. Получив данные из SQL, Python предоставляет богатые возможности для их анализа. Например, можно воспользоваться библиотекой NumPy для статистических расчётов, matplotlib или seaborn для построения графиков, scikit-learn для машинного обучения на этих данных и т.д. Часто pipeline выглядит так: SQL-запросом выгрузили нужный срез из базы → загрузили в pandas DataFrame → выполнили дополнительные расчёты в pandas (например, добавили новые столбцы с коэффициентами, объединили с другими данными) → построили визуализации или сохранили результат.
Автоматизация отчетов и скриптов. Python хорош тем, что позволяет автоматизировать процесс: например, по расписанию запускать скрипт, который обращается к базе SQL, вытаскивает обновлённые данные, генерирует отчёт (даже в виде Excel или PDF) и рассылает заинтересованным лицам. При этом вся тяжёлая выборка и агрегация может выполняться на стороне базы (SQL для этого оптимизирован), а Python используется для оркестрации и пост-обработки результатов.
Обратная интеграция (загрузка в базу). Если после анализа вы получили новые данные, которые нужно сохранить в базе, pandas предоставляет метод to_sql для отправки DataFrame обратно в таблицу SQL. Либо можно в Python сформировать команды INSERT/UPDATE и выполнить их через тот же подключенный драйвер. Таким образом, Python может быть и инструментом ETL: извлекать-обрабатывать-загружать данные, сочетая SQL и собственные вычисления.
Пример: у вас стоит задача – сравнить продажи и план по продажам и выслать отчет отклонений. Вы можете SQL-запросом выбрать фактические продажи по продуктам из базы, в том же Python подгрузить плановые показатели из Excel, затем в pandas объединить эти данные (по продукту), посчитать отклонение и с помощью библиотеки openpyxl или xlsxwriter сформировать красивую таблицу Excel, которую скрипт отправит письмом. Во всех этих шагах SQL используется там, где это эффективно (вытянуть агрегированные данные), а Python – для всего остального.
Подводя итог: Python и SQL не конкуренты, а союзники. SQL берёт на себя задачи быстрого получения данных из надёжно хранящихся больших таблиц (например, выбрать миллион транзакций с нужными фильтрами гораздо проще одним SQL). Python же даёт свободу манипуляций с уже полученными данными, применение сложных алгоритмов и автоматизацию. Для аналитика очень ценно владеть обоими инструментами: уметь максимально выгрузить работу на SQL, а потом результат творчески обработать в Python.
Excel – один из самых популярных инструментов для представления и анализа данных, и он поддерживает интеграцию с SQL-базами данных несколькими способами:
Импорт данных из базы в Excel. В Excel есть возможности подключения внешних данных. На вкладке Данные можно найти команды Получить данные (Get Data) из различных источников – в том числе из реляционных БД (SQL Server, PostgreSQL, Oracle, Access и т.д. через ODBC). При подключении Excel обычно позволяет либо указать таблицу/представление для импорта, либо написать собственный SQL-запрос. После этого данные из запроса попадают в диапазон на листе или в Модель данных Excel. Эти подключения можно обновлять – т.е. в любой момент Excel может повторно выполнить запрос к базе и подтянуть актуальные данные. Пример: вы подключились к базе продаж, выбрали из таблицы orders поля за последний месяц – Excel сохранил этот запрос, и по кнопке "Обновить все" вы всегда сможете получить свежие цифры без повторного экспорта вручную.
Power Query (Get & Transform). В новых версиях Excel (Office 365, 2016+) доступен Power Query – мощный инструмент ETL внутри Excel. С его помощью можно подключаться к базам данных и вытаскивать данные, выполняя при этом довольно сложные преобразования. Power Query позволяет настраивать шаги преобразования: фильтрацию, добавление столбцов, объединение таблиц (в том числе, кстати, выполнять JOIN разных источников, эмулируя SQL-подобные объединения). При подключении к SQL Power Query может по возможности транслировать ваши действия в SQL-запрос (т.н. Query Folding) – например, если вы в Power Query выбрали столбцы и поставили фильтр по дате, Excel на самом деле сформирует и выполнит SQL с SELECT этих столбцов и WHERE по дате7. Результат работы Power Query загружается либо в лист Excel (как обычная таблица), либо в модель данных для последующего использования в сводных таблицах и Power Pivot. Таким образом, Power Query – это удобный визуальный интерфейс, под капотом часто использующий SQL, что особенно ценно не программистам.
Обработка данных в самих таблицах. Часто после импорта данных из базы, Excel используется для дополнительного анализа с помощью знакомых инструментов: сводных таблиц (Pivot Table), графиков, формул. Например, SQL-запросом можно выгрузить детальные продажи, а в Excel на их основе построить сводную по продуктам и месяцам – фактически выполнив агрегирование средствами Excel. Или, скажем, с помощью функции ВПР (VLOOKUP) в Excel можно подтянуть данные из одной таблицы к другой по ключу – что аналогично JOIN двух таблиц. Конечно, Excel работает в памяти, поэтому с огромными объёмами он не справится так, как серверная БД. Но для умеренных данных (десятки тысяч строк) такой подход вполне практичен. Многие аналитики комбинируют: сложные выборки делают SQL-скриптами, а затем результат ("плоскую таблицу") дорабатывают в Excel, пользуясь удобством интерактивных фильтров, условного форматирования, ручных пометок и т.д.
Прочие электронные таблицы. Помимо Excel, стоит упомянуть, что Google Sheets и другие табличные приложения тоже могут взаимодействовать с SQL-данными, хотя и менее прямо. Google Sheets не имеет встроенного коннектора к внешней СУБД, но можно писать скрипты на Google Apps Script или использовать сторонние плагины для импорта SQL-данных. Зато в Google Sheets есть функция QUERY, позволяющая писать SQL-подобные запросы к диапазонам внутри самой таблицы (например, =QUERY(A1:C100, "select A, sum(C) where B > 5 group by A") работает почти как SQL над диапазоном A1:C100). Это не совсем про SQL и БД, но полезно знать, что концепции SQL проникают и в электронные таблицы. LibreOffice Calc позволяет через ODBC/JDBC брать данные – то есть похоже на Excel.
Таким образом, SQL и Excel часто используются вместе: SQL "достает" данные, а Excel служит средством их представления и дальнейшего ад-хок анализа. Навык экспорта SQL-данных в Excel (и обновления этих выгрузок) очень полезен. Например, можно настроить файл Excel, связанный с несколькими SQL-запросами, и получить своего рода дешёвый BI: при обновлении он подтянет из базы новые данные и пересчитает формулы/графики. Если же требуется что-то более масштабное и автоматизированное, то смотрят в сторону специализированных BI-инструментов (о них далее), но знать, как Excel работает с SQL, должен каждый аналитик.
Большинство BI-платформ при работе с реляционными данными используют SQL "под капотом". Когда вы строите дашборд или отчёт в Tableau, Power BI, QlikView, Looker и т.п., эти инструменты либо импортируют данные из базы с помощью SQL-запросов, либо в режиме реального времени генерируют SQL для получения нужных агрегатов.
Основные моменты интеграции SQL и BI-инструментов:
Подключение к источнику данных. В BI-системах есть готовые коннекторы к популярным СУБД. Например, в Tableau вы можете подключиться к PostgreSQL, указав хост, базу, пользователя/пароль, и сразу получить список таблиц. В Power BI при добавлении источника данных выбираете тип "SQL Server" или "Oracle" и задаёте параметры подключения. Часто BI-инструмент предлагает либо импортировать всю таблицу, либо написать пользовательский SQL-запрос для более точной выборки данных. Многие аналитики предпочитают подготовить в базе представления или сложные запросы, а в BI загружать уже агрегированный результат, чтобы не перегружать инструмент лишними вычислениями.
Импорт vs. прямые запросы (Direct Query). В режиме импорта BI-инструмент один раз выполняет SQL-запрос (или несколько) и сохраняет данные во внутреннем хранилище. Например, Power BI импортирует таблицы в свою колонночную базу VertiPaq, Tableau может импортировать в экстракт Hyper. После этого при фильтрации/агрегации в отчёте обращения к внешней базе не происходят – всё считается "локально". Такой режим удобен быстротой работы отчётов, но требует обновлять импорт (например, раз в день). В режиме Direct Query/Live BI-инструмент при каждом взаимодействии с отчётом генерирует SQL на лету и обращается к базе данных. Например, фильтруя дашборд Power BI в DirectQuery, вы фактически запускаете SQL с теми же фильтрами на сервере. Это позволяет работать с огромными данными без их полной загрузки в BI, но накладывает нагрузку на базу и требует оптимизации SQL. Знание SQL помогает понимать, какие запросы строит BI-инструмент и как можно упростить модель, чтобы они выполнялись быстрее.
Построение вычислений. Внутри BI-систем часто есть свои средства для расчётов – например, язык DAX в Power BI или calculated fields в Tableau. Эти средства могут частично переводиться в SQL. Например, простое выражение "выручка = цена * количество" может быть выполнено на стороне базы (BI подставит его в SELECT). Но более сложные вещи (уникальные пользователи за период, сравнение с предыдущим годом и т.п.) могут выполняться уже силами BI на импортированных данных. Иногда эффективнее сложную метрику вычислить заранее в SQL (например, сделать представление в БД), чем в каждом отчёте задавать её через формулу BI. Поэтому аналитики нередко совмещают: пишут SQL-запрос, который сразу выдаёт нужный срез/агрегат, и подключают его как источник для BI – чтобы минимизировать вычисления в отчёте и использовать мощь СУБД.
Пример Tableau: при подключении к базе вы можете перетащить на экран несколько таблиц и задать связи (JOIN) между ними – Tableau сам будет генерировать SQL с этими соединениями, когда нужно получить данные. Можно вместо этого нажать кнопку "New Custom SQL" и вставить готовый SQL-запрос, возможно, более оптимальный или сложный, чем вы можете задать в интерфейсе. Тогда Tableau будет напрямую использовать результат вашего запроса как источник (например, ваш запрос уже агрегировал данные до нужного уровня). Пример Power BI: подключаясь через Power Query к базе, вы можете либо последовательно выбирать колонки/фильтры в интерфейсе (PQ попробует сложить их в SQL), либо нажать "Дополнительно -> SQL-выражение", где вручную написать SELECT с нужными джойнами и условиями. В итоге Power BI загрузит результат запроса в модель.
Безопасность и доступы. При использовании BI важно учесть, что доступны будут те же данные, что доступны учетной записи, под которой вы подключаетесь к базе. Часто создают специального read-only пользователя для BI с правами только на нужные таблицы или представления, чтобы случайно BI-отчётом не "утянули" лишние конфиденциальные сведения. Зато BI-инструменты обычно умеют применять row-level security или другие механизмы уже на своём уровне – но это вне SQL.
Оптимизация запросов. Если отчёт в BI работает медленно, зная SQL можно посмотреть, какие именно запросы он отправляет (многие инструменты позволяют посмотреть логи или "профайл" запросов). Поняв, что, скажем, запрос тянет слишком много строк или не бьёт по индексу, можно оптимизировать: например, создать в БД индекс, материализованное представление либо изменить сам запрос (через тот же custom SQL или настройки модели).
По сути, BI-инструменты – это ещё один уровень абстракции над SQL для удобства визуализации и интерактивности. Но глубоко внутри они всё равно опираются на SQL для взаимодействия с данными. Поэтому хороший аналитик BI не воспринимает свой инструмент как “волшебную коробочку”, а понимает, как его действия транслируются в запросы. Это помогает и отчёты ускорить, и убедиться в корректности вычислений (например, зная, как считаются агрегаты, вы избежите дублирования при неправильных JOIN внутри модели).
Допустим, у нас есть база данных продаж, и мы хотим проанализировать выручку за первое полугодие 2025 года по категориям товаров, а также другие показатели, например количество уникальных покупателей и средний чек. Структура базы: таблица Orders (заказы) с полями order_id, order_date, customer_id; таблица OrderItems (позиции заказа) с полями order_id, product_id, quantity, price; таблица Products (товары) с полем product_id и category. Напишем запрос, который соберёт сводку по категориям:
SELECT p.category AS category, SUM(i.quantity * i.price) AS total_sales, COUNT(DISTINCT o.customer_id) AS unique_customers, MAX(o.order_date) AS last_order_date, AVG(i.quantity * i.price) AS avg_order_value FROM Orders o JOIN OrderItems i ON o.order_id = i.order_id JOIN Products p ON i.product_id = p.product_id WHERE o.order_date BETWEEN '2025-01-01' AND '2025-06-30' GROUP BY p.category ORDER BY total_sales DESC; Разберём, что делает этот запрос:
Соединяем таблицы. JOIN позволяет объединить заказы (Orders) с их позициями (OrderItems) и с информацией о товаре (Products). В результате для каждой позиции заказа мы "узнаем", к какой категории относится товар, и имеем данные о заказе в целом (например, дату и клиента).
Вычисляем поля в SELECT:
SUM(i.quantity * i.price) – суммарная выручка по категории (считаем стоимость каждой позиции заказа и суммируем).
COUNT(DISTINCT o.customer_id) – количество уникальных клиентов, совершивших покупки этой категории.
MAX(o.order_date) – дата последнего заказа в данной категории (наибольшая дата).
AVG(i.quantity * i.price) – средняя сумма заказа (точнее, средняя стоимость позиции, но если предположить, что один заказ = одна позиция в нашем примере, то это средний чек; если нет – можно было бы делить сумму на COUNT(DISTINCT o.order_id) для среднего по заказам).
Фильтр по дате: ограничиваем заказы январь–июнь 2025 года.
Группировка по категории: все агрегаты вычисляются для каждой категории p.category отдельно.
Сортировка: добавили ORDER BY total_sales DESC, чтобы сначала шли категории с наибольшей выручкой.
Предположим, запрос вернул такой результат:
category |
total_sales |
unique_customers |
last_order_date |
avg_order_value |
Electronics |
12540000 |
4800 |
2025-06-30 |
2612 |
Furniture |
8910500 |
3720 |
2025-06-29 |
2095 |
Clothing |
4770000 |
5800 |
2025-06-30 |
822 |
Groceries |
3102000 |
4100 |
2025-06-28 |
757 |
(Данные условные, для примера.)Из этого отчёта аналитик может сделать выводы: категория Electronics лидирует по выручке (12,54 млн₽) и имеет довольно высокий средний чек ~2612₽, при этом привлекла 4800 покупателей за полгода. Категория Furniture на втором месте по объёму, но уникальных покупателей меньше – возможно, покупки более дорогие/редкие (средний чек ~2095₽). Clothing и Groceries имеют гораздо меньший средний чек (сотни рублей), хотя по числу покупателей они сопоставимы или даже превышают другие – это типично, т.к. продукты питания и одежда дешевле электроники. Также можно заметить, что в некоторых категориях последние продажи были 30 июня (конец периода), а в других – 28 или 29 июня, что может говорить о том, что, например, 30 числа не было продаж мебели (Furniture).
Этот запрос демонстрирует типовую задачу аналитика: соединить несколько таблиц и посчитать агрегированные показатели. На практике запрос мог бы быть ещё сложнее (учёт возвратов, фильтрация некоторых позиций, расчёт долей и пр.), но принцип остаётся: используя JOIN и GROUP BY, мы получили сводный отчёт по категориям. Его можно выгрузить в Excel или BI-систему для визуализации (например, построить столбчатый график выручки по категориям).
Возьмём задачу из веб-аналитики: допустим, у нас есть таблица событий пользователей на сайте – UserActions с колонками user_id, action (например, 'view_product', 'add_to_cart', 'purchase'), action_date. Мы хотим проанализировать воронку конверсии за определённый период: сколько было просмотров товаров, сколько из них закончились добавлением в корзину, и сколько покупок совершили пользователи.
Одним из способов решить задачу воронки – подсчитать количество каждого типа событий. Сделаем это за июль 2025 года с помощью условной агрегации (через CASE):
SELECT SUM(CASE WHEN action = 'view_product' THEN 1 ELSE 0 END) AS views, SUM(CASE WHEN action = 'add_to_cart' THEN 1 ELSE 0 END) AS adds_to_cart, SUM(CASE WHEN action = 'purchase' THEN 1 ELSE 0 END) AS purchases FROM UserActions WHERE action_date >= '2025-07-01' AND action_date < '2025-08-01'; Что делает этот запрос:
Фильтр WHERE ограничивает события июлем 2025 (с 1 июля включительно по 1 августа не включительно).
Каждый SUM(CASE WHEN ... THEN 1 ELSE 0 END) подсчитывает количество строк, удовлетворяющих условию:
views: суммируем 1 для каждой строки, где action = 'view_product' (иначе 0). Таким образом получим общее число просмотров товаров.
adds_to_cart: аналогично считаем события добавления в корзину.
purchases: считаем покупки.
Каждая такая конструкция независима, но агрегируется по всему набору (поскольку GROUP BY не указан, считается один итог по всей выборке).
Предположим, результат вернулся:
views | adds_to_cart | purchases --------+--------------+---------- 250000 | 50000 | 8000 Это значит, что за месяц было 250 тыс. просмотренных страниц товаров, 50 тыс. добавлений товаров в корзину и 8 тыс. покупок. По этим трем цифрам можно вычислить коэффициенты конверсии между этапами: из просмотров в добавление (50k/250k = 20%), и из добавления в покупку (8k/50k = 16%). В совокупности конверсия просмотра в покупку = 8k/250k = 3.2%. Такая информация помогает выявить узкие места в воронке: например, если доля adds_to_cart относительно просмотров низкая, возможно, есть проблемы с привлекательностью товара или юзабилити добавления; если же из корзины до покупки доходит только 16%, стоит проанализировать процесс оформления заказа.
Преимущество данного подхода с CASE – все метрики получены в одном запросе, в одной строке результата. Мы можем легко добавить новые шаги, если они есть, или разнести по группам. Например, можно добавить GROUP BY по дню (DATE(action_date)) или неделе, чтобы получить поканальную воронку по дням. Или добавить ещё один CASE WHEN для подсчёта, скажем, сколько пользователей зарегистрировалось (action = 'signup').
Более сложная задача поведения – например, когортный анализ удержания пользователей – потребовала бы более сложных запросов (с подзапросами или оконными функциями), но принцип тот же: SQL отлично справляется с подсчётом различного рода событий, если правильно использовать агрегирование и условные выражения.
Чтобы уверенно применять SQL, очень важно практиковаться на разнообразных задачах. Вот несколько подходов и ресурсов, которые помогут закрепить навыки:
Работа с учебными базами данных. Существует ряд классических примеров баз: Northwind (магазин товаров, заказы, клиенты), AdventureWorks (более сложная база от Microsoft), Chinook (база музыкального магазина) и другие. Вы можете установить такую базу у себя (либо воспользоваться онлайн-эмуляторами) и попробовать отвечать на вопросы по данным. Например: «Какие 5 продуктов принесли наибольшую выручку?», «В каком месяце года наблюдается спад продаж?», «Клиенты из какого региона приносят больше всего прибыли?» и т.д. Решая такие задачи, вы научитесь применять разные конструкции SQL (JOIN, агрегаты, подзапросы).
Онлайн-тренажёры с задачами. В интернете много интерактивных ресурсов:
SQLZOO – один из самых известных бесплатных тренажёров4. Прямо в браузере вы решаете задачи по возрастанию сложности и сразу проверяете результат.
SQL-EX.RU – русскоязычный сайт с большим набором задач по SQL5. Там задачи разбиты по темам (SELECT, JOIN, подзапросы и т.д.), есть система проверки ответов.
HackerRank (раздел SQL) и LeetCode – эти площадки известны задачами для интервью, в том числе у них есть задачи по SQL. Задачи там довольно разного уровня – от простых выборок до сложных запросов с несколькими JOIN и оконными функциями. Решая их, вы не только закрепите синтаксис, но и научитесь оптимальным приёмам (часто самый простой наивный запрос не проходит по времени, и нужно придумать более эффективный).
Stepik (интерактивный курс-тренажёр) – упомянутый ранее курс на Stepik содержит более 300 практических задач на SQL, которые вы решаете прямо на платформе1. Он хорош тем, что начинается с базы и постепенно вводит новые конструкции, сразу предлагая попрактиковаться.
Kaggle (раздел Learn) – Kaggle предлагает краткий курс по SQL и небольшие интерактивные задачи, плюс на самом Kaggle можно практиковаться писать запросы к публичным датасетам через сервис BigQuery.
Придумайте проект самостоятельно. Отличный способ закрепить SQL – взять данные, которые вам интересны, загрузить в базу (или воспользоваться бесплатной базой) и попытаться извлечь инсайты. Например, допустим вас интересует кино – возьмите открытый датасет с фильмами (рейтингами, кассовыми сборами), импортируйте в SQLite и попробуйте составить "отчет": топ-10 фильмов по рейтингу за каждый год, средний рейтинг по жанрам, режиссёр с наибольшим числом фильмов в топ-250 и т.п. Или, например, есть открытые данные городских служб – выберите тему (транспорт, экология), сформулируйте 5 вопросов и ответьте на них запросами. Такой pet-project заставит вас гуглить и учиться тому, чему не научат на стандартизованных задачах, и заодно попрактикует навыки работы с базой вне учебной “песочницы”.
Упражнения из книг и статей. В конце многих книг по SQL есть разделы с упражнениями. Попробуйте их решить! Даже если нет готового ответа, можно обсудить на форумах, свериться с другими. Полезны также задачи на форумах: на Stack Overflow есть метка [sql] – там пользователи спрашивают совета по запросам. Вы можете смотреть на их проблемы и пытаться решить их самостоятельно, сравнить со стрелой решений от гуру. Также существуют подборки вроде "50 SQL-задач для аналитиков"3 – своего рода чеклист задач, которые должен уметь решить специалист (от простых выборок до нетривиальных).
Пройти тестовые задания или мини-проекты. Если вы планируете карьеру аналитика, попробуйте взять реальное тестовое задание (их нередко публикуют в интернете) и решить его. Например, в тестовом может быть: "есть таблица транзакций, составьте запрос, который строит RFM-сегментацию клиентов". Вам придётся применить весь спектр знаний – и агрегирование, и условную логику, и, возможно, оконные функции. Даже если сначала решение кажется сложным, поищите похожие разобранные примеры, постепенно сложите свой запрос. Это отличная проверка навыков.
Совет: во время практики старайтесь не просто получить правильный ответ, но и понять альтернативные способы. SQL зачастую предоставляет несколько вариантов решить задачу (через JOIN или подзапрос, через оконную функцию или группировку). Попробуйте разные и сравните. Это обогатит ваш "багаж" приёмов. И конечно, если какая-то задача вызывает трудности – возвращайтесь к теории, перечитывайте главу или статью на эту тему, а затем снова к практике.
Регулярно упражняясь на разнообразных задачах, вы вскоре заметите, что стандартные запросы вы уже пишете практически автоматически, а для более сложных знаете, какие конструкции попробовать. Навык SQL – как мышца, его надо постоянно поддерживать в тонусе. К счастью, ресурсов для этого более чем достаточно 😊.
На платформе «Учись Онлайн Ру» собран богатый каталог курсов по SQL, ориентированных в том числе на анализ данных. Здесь представлены программы от ведущих онлайн-школ, причём есть как короткие курсы по конкретно SQL, так и большие комплексные программы по аналитике с изучением SQL2. Информация о курсах регулярно обновляется2, и можно увидеть актуальные цены, отзывы учеников, рейтинги.
Примеры курсов на платформе:
«SQL с 0 для анализа данных» от ProductStar – интенсив для новичков, 2 месяца обучения, формат видеолекций и практики с наставником2. Здесь упор на основы SQL и применение запросов к типовым задачам аналитика, в конце – дипломный проект. Отличается высоким рейтингом (4.5/5) и менторской поддержкой, подходит для тех, кто стартует с нуля и хочет за короткий срок освоить SQL.
«SQL и получение данных» от Нетологии – курс на ~2 месяца, включает изучение SQL и основ работы с базами данных (в том числе PostgreSQL, основ Linux и даже MongoDB для кругозора)2. Формат – вебинары и видеолекции, с домашними заданиями и обратной связью. Рассчитан также на начинающих, делает акцент на практику и позволяет после курса уверенно писать запросы и понимать инфраструктуру данных.
Комплексные программы по аналитике данных с SQL. Например, профессия «Аналитик данных» (есть у Яндекс Практикума, у Нетологии, у SkillFactory и др.). Эти программы длятся дольше (6-12 месяцев), включают не только SQL, но и Python, статистику, BI-системы и прочие навыки аналитика2. SQL там даётся с нуля до достаточно продвинутого уровня, но он является частью большого курса. Такой вариант хорош, если вы нацелены именно на новую профессию и готовы учиться несколько месяцев с погружением в разные инструменты.
Всего на «Учись Онлайн Ру» можно найти десятки курсов по SQL. Чтобы определиться, какой из них вам подойдёт, учитывайте:
Ваш уровень и цель. Если вы полный новичок и хотите освоить базовый SQL для работы аналитиком, выбирайте курсы для начинающих, где разбираются фундаментальные вещи (SELECT, JOIN, агрегаты) и есть много практики. Такие курсы часто имеют в названии "с нуля" или явно указывают, что для новичков. Пример – упомянутый курс от ProductStar или SkillFactory «Курс по SQL для анализа данных» (7 недель, стартовый уровень)2. Если же вы уже знаете основы и интересуют более продвинутые темы (оптимизация запросов, нюансы конкретной СУБД, SQL для больших данных), можно смотреть на курсы среднего уровня или узкие (например, курс по T-SQL программированию, или по оптимизации в Oracle). Также решите, зачем вам курс: для общего развития, для применений на работе, или готовитесь к интервью – цели могут подсказать, на что делать упор (практика, теория, сертификат).
Программа и содержание. Внимательно изучите учебный план курса. Для аналитика данных курс по SQL должен охватывать: базовый синтаксис (SELECT-FROM-WHERE), все виды JOIN, агрегатные функции и GROUP BY, подзапросы, оконные функции (очень желательно!), работу с датами, возможно, основы DDL (создание таблиц) и простой администрирование (как подключиться к БД, базовые типы данных). Посмотрите, уделяется ли внимание именно аналитическим задачам – кейсам, близким к реальным (часто в описании пишут «в ходе курса вы будете считать метрики, строить отчёты...», это хорошо). Если курс позиционируется под определённую СУБД (например, «SQL в MS SQL Server»), убедитесь, что вам актуально именно это (например, на работе используется MS SQL). Если цель – просто научиться писать запросы, подойдёт любой качественный курс по стандартному SQL.
Формат обучения. Подумайте, что вам удобнее: самостоятельный темп (записи уроков, тренажёр) или график занятий с преподавателем. На платформе есть курсы полностью в записи (доступ к материалам и задачам, вы проходите когда удобно) – они часто дешевле. Есть курсы с вебинарами по расписанию, группой и ментором – это дисциплинирует и даёт общение, поддержку, но требует вписаться во время. Также посмотрите на объем практики: идеальный курс по SQL должен давать много задач для написания запросов. Хорошо, если есть проект в конце (например, проанализировать базу продаж и сделать выводы) – это ценный опыт.
Отзывы и рейтинг. Платформа «Учись Онлайн Ру» показывает рейтинг курсов и отзывы выпускников. Обратите внимание, как люди оценили содержание, не слишком ли было легко/сложно, помог ли куратор в решении вопросов, понравилась ли платформа упражнений. Отзывы могут подсказать, например, что «курс больше теоретический, практики мало» – тогда вы поймёте, что лучше выбрать другой, если вам нужна практика. Или наоборот «много сложных домашек, еле успевал» – приготовитесь к высокой нагрузке.
Сертификат и ценность для карьеры. Если для вас важно получить документ об окончании, посмотрите, что выдаёт школа: сертификат школы, государственный диплом о профпереподготовке (такое бывает у крупных школ), либо никакого документа. Обычно для устройства на работу достаточно сертификата школы (и то важнее навыки и пройденные проекты). Но кому-то психологически важно иметь "корочку". Также посмотрите, помогают ли с трудоустройством – некоторые комплексные программы включают карьерные консультации, стажировки. Курсы покороче обычно ограничиваются выдачей сертификата и советом “добавить в портфолио выполненный проект”.
Стоимость и формат оплаты. Курсы по SQL длятся от нескольких недель до нескольких месяцев, цены варьируются сильно (есть бесплатные вводные до дорогих профессий под 100+ тысяч рублей). На «Учись Онлайн Ру» можно отфильтровать по цене, посмотреть наличие скидок (платформа показывает, если сейчас акция, многие курсы со скидками 40–50%). Также можно учесть, что некоторые курсы можно купить в рассрочку. Если бюджет ограничен или вы не уверены – начните с недорогих или бесплатных вариантов (в каталоге есть отметки о бесплатных программах). Платные курсы оправдывают себя, когда вы цените сопровождение и структурированный подход.
Резюмируя выбор курса: определите, что вы хотите добиться (освоить с нуля, углубить знания, получить работу аналитика), просмотрите несколько программ на платформе «Учись Онлайн Ру», почитайте описание и отзывы. Составьте shortlist из 2–3 вариантов и сравните: где программа кажется понятнее и ближе вашим целям, где формат удобнее, и отзывы лучше. Далее – смело записывайтесь. Платформа дает много информации, чтобы выбор был осознанным – пользуйтесь этим. И помните, что успех обучения всё равно зависит во многом от вас: курсы дают направление, но практиковаться и дальше развивать навык SQL нужно будет постоянно.
(Курсы на «Учись Онлайн Ру» представлены от разных школ – Яндекс Практикум, Нетология, SkillFactory, Skypro, ProductStar и др. Платформа объединяет их предложения, чтобы вам было проще выбрать подходящий по содержанию и бюджету курс2.)
Существует множество книг по SQL – как на русском, так и на английском – которые помогут и новичкам, и продвинутым пользователям лучше понять язык запросов и научиться эффективным приёмам. Перечислим некоторые рекомендации:
Алан Бойл (Alan Beaulieu) «Изучаем SQL» (O'Reilly) – отличная книга для начинающих. На русском она выходила в серии Head First (также известна как "Head First SQL"). Пишет простым языком и покрывает весь путь от основ SELECT до сложных запросов и аналитических функций6. В книге много визуальных пояснений и практических примеров, после каждой главы – упражнения.
Аллен Тейлор «SQL для чайников» – классический вводный учебник6. Рассматривает базовые операции, устройство баз данных, и даже затрагивает администрирование. Подойдёт тем, кто вообще новичок в тематиках баз данных и хочет с азов.
Кэти Тэнимура «SQL для анализа данных» (SQL for Data Analytics). Книга специально ориентирована на применение SQL в data science и аналитике. Она сравнивает различные СУБД, рассказывает о подготовке данных для анализа, приведены кейсы анализа данных с помощью SQL. Полезна тем, кто уже знает базовый синтаксис и хочет понять, как применить это к реальным задачам аналитики.
Антон Жиянов «Оконные функции SQL. Анализ данных на практике» – русскоязычная книга, полностью посвященная оконным функциям и их использованию для решения аналитических задач. Оконные функции – тема продвинутая, и эта книга хорошо её раскрывает, с множеством примеров "скользящих" расчётов, ранжирования, вычисления процентов и т.д. Рекомендуется после того, как вы освоили базовый SQL и готовы прокачать навык до следующего уровня.
Энтони Молинаро «SQL Cookbook» (есть перевод на русский как "SQL. Сборник рецептов"6). Это сборник задач и готовых решений на разных диалектах SQL. Удобно тем, что вы можете найти типичную проблему (например, "найти второе максимальное значение", "вычислить промежуток между записями") и посмотреть, как это делается запросом. Книга ориентирована на тех, кто уже знаком с SQL и хочет пополнить копилку приёмов.
Бен Форт (Ben Forta) "SQL за 10 минут" – небольшой учебник, рассчитанный на очень быстрое введение. Хорош как справочник: каждая "урок-глава" освещает какую-то тему за ~10 минут чтения. Можно использовать, чтобы быстро освежить что-то.
Ресурсы онлайн: помимо книг, есть много статей и туториалов. На Хабре есть статьи вроде «6 книг по SQL: что почитать новичкам и специалистам»6 – там обзоры литературы. В официальной документации СУБД (Microsoft, Oracle, PostgreSQL) тоже много обучающих разделов. А для ежедневной работы очень полезен сайт SQLTutorial.org или русский SQL-tutorial.ru – как справочник с примерами по разным темам.
Литература по базам данных в целом: Если вас интересует не только синтаксис SQL, но и понимание, как работают СУБД, можно рекомендовать книгу «Совершенный код SQL» (Э.Т. Стоунекер) – она о стилях написания запросов и оптимизации, или книгу «Основы системы управления базами данных» (К. Дейт) – для глубокого теоретического понимания реляционной модели. Но для аналитика данных это избыточно; лучше сосредоточиться на практических книгах, упомянутых выше.
И наконец, официальная документация вашей СУБД – тоже очень ценный ресурс. Например, документация PostgreSQL прекрасно описывает и особенности SQL стандарта, и свои расширения, с примерами. А Microsoft Docs для T-SQL содержит массу руководств "как сделать X".
Выбирайте формат, который вам удобнее: книги дают последовательное погружение, онлайн-ресурсы – интерактивность и обновлённость. В идеале, сочетайте: прошли главу книги – попробуйте решить задачи онлайн по этой теме, или прочитали статью – закрепите на практике. Так материал усвоится лучше.
Новички при работе с SQL часто сталкиваются с определёнными типами ошибок – как синтаксических, так и логических. Рассмотрим некоторые распространённые ошибки и недочёты:
Забывают про GROUP BY при использовании агрегатов. Одна из самых частых ошибок: попытка выбрать и обычные столбцы, и агрегатные функции без группировки. Например: SELECT region, SUM(sales) FROM deals; – большинство СУБД (кроме MySQL в старых режимах) выдадут ошибку, потому что region не в GROUP BY. Правильно: либо добавить GROUP BY region, либо убрать region из SELECT.
Используют SELECT * там, где не нужно. SELECT * (выбрать все столбцы) удобно при экспресс-запросах, но в реальных сценариях лучше явно указывать нужные поля. Во-первых, это экономит трафик и время, во-вторых, защищает от неожиданного изменения структуры таблицы. Новички часто пишут SELECT * и используют только пару полей из десяти – лучше перечислить эти поля.
Не учитывают влияние условий на результат (логические ошибки). Например, написать WHERE year = 2021 OR year = 2022 AND status = 'Active' и получить не то, что ожидалось из-за приоритета AND. Правильно было бы добавить скобки: (year = 2021 OR year = 2022) AND status = 'Active'. Логика условий требует внимания: AND выполняется раньше OR, и без скобок запрос может отбирать лишние данные.
Пренебрегают проверкой результатов JOIN. Часто новички делают JOIN, не удостоверившись, что соединение один-ко-многим не дублирует строки. Пример: соединить таблицу клиентов с таблицей заказов и подсчитать сумму заказов – если забыть группировку по клиенту, то при наличии нескольких заказов получатся повторённые данные клиента. Или делают несколько JOIN-ов и получают декартово произведение по ошибке (когда забыли условие ON или перепутали поля). Всегда надо проверять: если после JOIN число строк сильно выросло – ожидаемо ли это? Возможно, забыто условие или соединение “лишнее”.
Неверно используют NULL (см. вопрос №13). Классический пример – пытаться сравнить с NULL через = NULL. Либо забыть обработать NULL в подзапросе: запрос WHERE price > (SELECT AVG(price) FROM tbl WHERE category = X) ничего не вернёт, если в категории X нет записей (AVG вернёт NULL, а сравнение price > NULL всегда FALSE). Новички порой не знают про IS NULL и COALESCE, что приводит к неправильным результатам.
Дублируют одинаковые подзапросы. Например, пишут: WHERE price > (SELECT AVG(price) FROM sales) AND quantity > (SELECT AVG(quantity) FROM sales). Тут подзапрос для AVG выполняется дважды – лучше вычислить его один раз (через CTE или CROSS JOIN). Это не столько "ошибка", сколько неэффективность, но она характерна для неопытных – не задумываться об оптимизации и лишней работе, которую делает база.
Опускают явное указание JOIN типа или условий. В старом стиле SQL можно делать FROM A, B WHERE A.id = B.id, новички могут таким пользоваться, но забыть условие – получат CROSS JOIN (декартово умножение). Лучше всегда использовать явные JOIN ... ON ... и никогда не оставлять соединение без условия (кроме случаев осознанного CROSS JOIN).
Не учитывают порядок выполнения запросов. Например, пытаются в WHERE использовать алиас поля из SELECT (который вычисляется после). Пишут: SELECT amount*1.2 AS amount_new FROM sales WHERE amount_new > 1000 – не сработает, нужно либо повторить выражение amount*1.2 > 1000 в WHERE, либо использовать HAVING (если алиас – агрегат). Понимание, что сначала FROM/WHERE, потом SELECT, потом ORDER/HAVING – важно, и сначала часто на этом спотыкаются.
Избыточно усложняют запрос. Иногда новички пишут вложенные подзапросы там, где можно одним JOIN, или делают JOIN нескольких таблиц, а можно было только нужные поля достать подзапросом. Результат – запрос работает, но сложный и медленный. Это не явная ошибка, но практика и опыт подскажут потом, как писать проще и эффективнее.
Забывают про DISTINCT когда нужно уникальные значения. Например, подсчитывают количество клиентов через COUNT(customer_id) вместо COUNT(DISTINCT customer_id) и получают завышенное число (вместо количества клиентов – количество строк продаж).
Используют неэкранированные данные (SQL-инъекции). Это скорее для разработчиков: подставлять прямо параметры в строку запроса (например, в Python строку формировать "WHERE name = " + name). Это может привести к SQL-инъекциям. В аналитических задачах реже, но если пишете скрипты – помните про параметризацию запросов.
Большинство этих ошибок лечатся внимательностью и опытом. Хорошая практика – всегда проверять на маленьком наборе (через LIMIT или условие) свой запрос: действительно ли он выдаёт то, что вы ожидали? Также полезно читать план выполнения (EXPLAIN), чтобы убедиться, что запрос не делает чего-то явно лишнего (например, огромного join вместо использования индекса). Со временем вы начнёте сразу замечать потенциальные проблемы в запросе ещё до запуска.
Оптимизация SQL – обширная тема, но для аналитических запросов можно выделить несколько ключевых принципов, которые помогут вашим запросам выполняться быстрее:
Работайте с как можно меньшим объёмом данных. Даже до оптимизации запросов на уровне индексов и плана, подумайте: действительно ли вам нужно тянуть все миллион строк за 5 лет? Возможно, проще агрегировать данные на уровне базы. Например, если вы строите отчёт по месяцам, сделайте агрегацию GROUP BY month прямо в SQL, вместо того чтобы выгружать сырые данные помесячно и агрегировать на клиенте (в Excel или Python). Чем меньше строк возвращает запрос – тем он обычно быстрее и менее нагружает сеть. Используйте WHERE для отсеивания ненужного (даты, категории, статусы – всё, что исключает лишние данные, должно быть в фильтре).
Убедитесь, что используются индексы. В базах данных индексы – главный механизм ускорения чтения. Например, если вы часто фильтруете по дате (WHERE order_date BETWEEN ...) – хорошо иметь индекс по order_date. Многие СУБД автоматически создают индекс по первичному ключу, но не на другие столбцы. Проверьте, нет ли случаев, где запрос делает полный скан таблицы (это можно увидеть через EXPLAIN в PostgreSQL/MySQL или графический план в MS SQL). Если такая операция повторяется часто – возможно стоит создать индекс. Однако помните: слишком много индексов плохо на вставку/обновление, так что тут нужен баланс, но в аналитических базах (хранилищах) индексов обычно ставят щедро на те поля, по которым делают срезы.
Избегайте неоптимальных конструкций. К ним относятся коррелированные подзапросы (которые выполняются для каждой строки внешнего запроса) – иногда их лучше переписать через JOIN. Избегайте использование функции к столбцу в условии поиска – например, WHERE YEAR(order_date) = 2022 не сможет использовать индекс по order_date, лучше order_date BETWEEN '2022-01-01' AND '2022-12-31'. Старайтесь не делать SELECT DISTINCT без надобности – иногда проблема в неправильном JOIN, дубли можно убрать исправив запрос, а DISTINCT – дорогостоящая операция сортировки/свертки.
Разбивайте сложные запросы. Парадоксально, но иногда проще и быстрее выполнить два-три маленьких запроса, чем один гигантский JOIN всего со всем. Например, если у вас очень сложная бизнес-логика – можно сначала результирующую таблицу подготовить (CTE или временную таблицу), а потом к ней присоединять остальные данные. СУБД бывает легче оптимизировать несколько простых запросов, чем один сверхсложный (оптимизатор может запутаться). Также, разбив на шаги, вы сможете проанализировать, на каком шаге узкое место.
Используйте подходящие типы данных и хранение. Например, если у вас хранится время как текст, то фильтр по нему будет медленный – лучше хранить как DATE/DATETIME. Числовые идентификаторы вместо строковых тоже ускоряют соединения. В колонночных базах (ClickHouse, Vertica) свои нюансы оптимизации – там выгодно агрегировать и отбирать только нужные колонки.
Стройте материальные представления для сложных агрегатов. Если у вас часто запрашивают одни и те же тяжёлые агрегаты (например, "выручка за месяц по регионам"), может иметь смысл периодически (например, раз в сутки) рассчитывать эту сводную таблицу и сохранять, чтобы отчёт брал данные уже из неё. Это аналог денормализации: жертвуем чуть свежестью или избыточным хранением ради скорости запросов. Многие СУБД умеют materialized view, которые автоматом обновляются – удобно пользоваться.
Профилируйте и анализируйте планы. Инструмент EXPLAIN (в MySQL/Postgres) или 'Execution Plan' в SQL Server Management Studio – ваш друг. Он покажет, какие индексы задействованы, сколько строк оценивается пройти, где узкие места. Например, если видите операция Nested Loop со 100k итерациями – возможно, стоит переписать на JOIN + GROUP BY, чтобы превратить в один проход. Или если видите Seq Scan (полный просмотр таблицы) на огромной таблице – стоит оптимизировать условие или добавить индекс.
Пример оптимизации: у вас запрос:
SELECT customer_id FROM orders WHERE TO_CHAR(order_date, 'YYYY-MM') = '2023-01'; Он работает 5 секунд, делая Seq Scan, потому что для каждой строки применяет функцию TO_CHAR и не может использовать индекс по дате. Оптимизация:
SELECT customer_id FROM orders WHERE order_date >= DATE '2023-01-01' AND order_date < DATE '2023-02-01'; – такой запрос мгновенно отработает, если есть индекс по order_date. Это простой пример, но в реальности такие мелочи складываются.
Оптимизация – это итеративный процесс: изменили запрос/индекс -> замерили -> стало лучше или нет. Всегда проверяйте, что оптимизация не изменила логику запроса. И помните принцип: сначала напишите правильно работающий запрос, потом уже думайте о его скорости. Для учебных и относительно небольших данных чаще важнее корректность. Но в боевой базе умение оптимизировать отличает хорошего аналитика: отчёты будут бегать, а не еле ползти.
SQL применяется в двух основных классах систем: OLTP (Online Transaction Processing) – оперативные транзакционные базы, и OLAP (Online Analytical Processing) – аналитические хранилища/витрины. Они могут использовать одни и те же СУБД и язык SQL, но характер работы и запросов отличается:
Модели данных. В OLTP-системах (например, база интернет-магазина) данные находятся в третьей нормальной форме: много таблиц, связанные детально, чтобы избежать дублирования. В OLAP же часто применяют денормализацию: схемы типа «звезда» или «снежинка» – факт таблицы с ключами на справочники (типы, даты, клиенты). В аналитической БД меньше таблиц, они шире, часто заранее агрегированы. Это делается для ускорения чтения (жертвуя избыточностью хранения).
Характер запросов. OLTP-запросы обычно простые и точечные: "вставить заказ", "получить пользователя по id", "обновить статус оплаты". Эти запросы затрагивают мало строк (чаще всего 1-10), но выполняются очень часто и должны быть максимально быстрыми. В OLAP-запросах наоборот – сложные агрегаты на больших объёмах: "дай средний чек по городам за год", "сгруппируй миллион продаж по 5 категориям". Они могут сканировать сотни тысяч и миллионы строк, что для OLTP недопустимо (там бы это заблокировало систему). Поэтому OLAP-системы оптимизированы под чтение больших массивов (например, индексируются иначе, хранятся по столбцам).
Параллелизм и ресурсоёмкость. В транзакционных БД упор на параллельное обслуживание множества мелких запросов – важна высокая степень параллелизма, блокировки на уровне записей и т.д. В аналитических допускается, что один запрос может грузить процессор и диск несколько секунд (или минут) – при этом обычно аналитические базы выделены отдельно от боевой системы, чтобы тяжелый запрос аналитика не мешал работе приложения. Многие аналитические СУБД (типа Vertica, ClickHouse) так и устроены: они круто выполняют тяжёлые селекты, но не предназначены для частых апдейтов одной строки.
SQL-конструкции. Обычный SELECT, JOIN, WHERE – есть везде. Но, например, оконные функции исторически появились именно для OLAP-задач (анализ последовательностей, ранги), и поддержка их в классических OLTP-СУБД была второстепенной (MySQL только недавно добавил). В OLTP редко нужны гигантские GROUP BY – там скорее индекс применяется для выборки конкретной записи. Зато критичны точные обновления и согласованность – там используют транзакции, блокировки, проверки целостности (FOREIGN KEY). В аналитических базах часто денормализуют и могут вообще не задавать внешних ключей (чтобы быстрее загружать данные пачками). Там нет частых апдейтов – данные обычно загрузили пакетно за день и читают. Поэтому, например, архитектуры хранилищ часто разделяют: есть мастер-БД (OLTP) где всё оперативно, и периодически данные реплицируются/трансформируются в хранилище (OLAP) для отчётов.
Пример: таблица заказов. В OLTP она будет очень детальной, и запрос типа «найти заказ по номеру» вернёт одну строку – быстро по индексу. Аналитик же спросит: «сколько заказов было каждый день в разрезе способов оплаты» – этот запрос затронет всю таблицу заказов за год. Если выполнить его на боевой базе – нагрузка огромная, плюс, возможно, придется джойнить много таблиц (чтобы расшифровать способы оплаты, категории товаров и т.д.). Для этого и выделяют отдельную базу или "витрину": подготовленную, где достаточно одной таблицы с фактами, где все нужные поля уже в ней (способ оплаты как текст, а не id, категория товара уже подставлена и т.п.). Тогда запрос выполняется быстрее и не мешает основной базе.
Таким образом, SQL один, но подходы разные. Аналитик, работающий с OLAP, обычно может позволить себе писать более тяжёлые запросы, не думая о блокировках, но должен думать об оптимизации объёма данных (чтобы уложиться во вменяемое время выполнения). Разработчик OLTP, наоборот, старается, чтобы каждый запрос был максимально лёгким и быстрым, и часто избегает сложных JOIN и подзапросов в пользу простых операций. Для аналитика важно знать возможности СУБД-хранилища: например, многие аналитические БД имеют свои расширения SQL (например, ClickHouse – функцию uniq для приближённого COUNT(DISTINCT) для сверхбольших данных, или специальные кластерные функции для распределённых запросов).
Если коротко, OLTP-SQL – про множество маленьких транзакций (вставить/обновить/получить точно по ключу), OLAP-SQL – про немногочисленные тяжёлые запросы (сложные выборки, агрегаты, соединения большого объёма данных). И то, и другое – SQL, просто применяемый в разных условиях. Аналитику стоит понимать эту разницу, особенно если приходится брать данные из рабочих баз: не запускать там случайно запрос, который положит систему. Лучше выгружать данные по частям или через специальные средства (реплики, слепки) и уже анализировать на стороне.
Существует ряд рекомендаций, которые помогают делать SQL-запросы более читабельными, поддерживаемыми и правильными. Соблюдение этих лучших практик особенно важно, когда запросы становятся сложными или когда вы работаете в команде. Вот основные:
Пишите код, понятный другим (и себе через время). Форматируйте запрос: используйте переводы строк и отступы, чтобы ясно отделять секции. Обычно ключевые слова (SELECT, FROM, WHERE, JOIN, GROUP BY, ORDER BY) пишут с новой строки капсом, а условия и выражения – с отступом. Пример стиля:
SELECT c.customer_name, SUM(o.amount) AS total_amount FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE o.status = 'Completed' AND o.order_date >= '2023-01-01' GROUP BY c.customer_name ORDER BY total_amount DESC; По отступам видно структуру: SELECT-список, затем FROM/JOIN блок, условия, группировка, сортировка.
Используйте понятные алиасы для таблиц и полей. В длинном запросе с несколькими JOIN короткие алиасы (a, b, c) экономят место, но могут сбивать с толку. Лучше давать осмысленные: emp для таблицы сотрудников, dept для отдела, sale для продаж. Или хотя бы первые буквы: cu для customers, or для orders. Алиасы полей (через AS) полезны, чтобы назвать вычисляемую колонку понятным именем, особенно если потом используете её вне SQL (в отчёте). Например, AVG(salary) AS avg_salary – удобно.
Явно указывайте нужные столбцы вместо SELECT *. Мы уже упоминали это как ошибку новичков. В аналитических запросах часто нужно не все поля таблицы, а конкретные – указывайте их. Это улучшит читабельность (сразу видно, какие данные используются) и потенциально ускорит выполнение (меньше данных передается).
Следите за производительностью, но не преждевременно. То есть, пишите сначала логически корректный запрос, в простом понимании. Не бойтесь использовать подзапрос или CTE, если так понятнее, даже если можно слить всё в один монструозный JOIN. Потом, протестировав на объёме, можно оптимизировать при необходимости. Но изначально приоритезируйте ясность. Преждевременная оптимизация – зло, но и полностью игнорировать её нельзя: просто держите в уме масштабы данных (если таблица на 100 млн строк, три вложенных подзапроса – плохая идея).
Проверяйте запрос по частям (пошагово). При разработке сложного SQL-запроса вы можете сначала написать подзапрос отдельно и выполнить, убедиться, что он возвращает то, что нужно. Затем использовать его в основном запросе. Или построить запрос без фильтра, посмотреть на данные, а потом добавить WHERE. Разбивайте: так легче отлавливать ошибки и понимать логику.
Документируйте сложную логику. В SQL можно писать комментарии: -- однострочный комментарий или /* многострочный */. Если запрос нестандартный, или магия происходит – оставьте пояснение. Например:
-- Получаем последние записи на дату, используя оконную функцию SELECT *, ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY event_time DESC) AS rn FROM events; Хотя бы одной строкой опишите подход, чтобы другому аналитику не пришлось ломать голову, зачем тут ROW_NUMBER.
Избегайте дублирования логики. Если один и тот же расчёт используется в нескольких местах, рассмотрите CTE или представление. Это защити от ситуации, когда вы поправили формулу в одном запросе, а в другом забыли – и показатели разошлись. Лучше вычислить, скажем, "чистую прибыль" в одном месте и потом везде на него ссылаться, чем в нескольких запросах писать revenue - cost и вдруг в одном вычесть ещё скидки, а в другом нет.
Используйте инструменты форматирования и проверки. Существуют IDE (например, DataGrip, DBeaver) и плагины, которые могут автоматически форматировать SQL по заданному стилю. Воспользуйтесь ими, чтобы не мучиться с выравниванием вручную. Также есть "статические анализаторы" (linters) для SQL, которые подскажут, где, возможно, ошибка (например, забыли GROUP BY или несуществующая колонка). Они не так распространены, как для языков программирования, но для крупных проектов могут быть полезны.
Следите за транзакциями и изменяющими запросами. Хотя аналитики обычно делают SELECT, иногда приходится что-то обновить или создать временную таблицу. Тут правило: либо делайте это на своей песочнице/витрине, либо будьте крайне осторожны на боевой базе. Никогда не выполняйте UPDATE/DELETE без WHERE! И сначала сделайте SELECT с таким же условием, убедитесь, что именно эти строки выберутся.
Консистентность агрегатов. Например, договоритесь, как считать "уникальных пользователей": где-то могут считать по cookie, где-то по account_id – важно в рамках вашей команды или проекта иметь единое определение и писать запросы согласно ему. То же с датами: если вы всегда считаете показатели за день по UTC – явно в запросах это обеспечьте (... WHERE DATE(timestamp at time zone 'UTC') = ...). Эти моменты лучше зафиксировать либо в документации, либо как комментарии в шаблонах запросов, чтобы все делали одинаково.
И последнее: постоянно учитесь и улучшайте стиль. Читайте чужие запросы, задавайте вопросы: "А почему ты здесь сделал LEFT JOIN вместо подзапроса?" – опытные коллеги могут поделиться инсайтами. SQL – язык лаконичный, и со временем вы будете писать всё более элегантные решения. Но "элегантное" не должно означать "заумное": помните, что читать ваш запрос, возможно, будет вчерашний новичок. Поэтому – пишите прозрачно. Хороший SQL-запрос – как хороший отчёт: ясно, логично, без лишнего. Следуя лучшим практикам, вы не только избежите ошибок, но и облегчите жизнь себе и другим аналитикам, которым предстоит работать с вашим кодом.
Комментарии
Комментариев пока нет. :(
Написать комментарий
Задайте интересующий вопрос или напишите комментарий.
Зачастую ученики и представители школ на них отвечают.
Только зарегистрированные пользователи могут оставлять комментарии. Зарегистрируйтесь или войдите в личный кабинет