SQL: От Новичка до Профессионала

Этот курс охватывает все аспекты работы с реляционными базами данных: от написания простых запросов до проектирования сложных схем и оптимизации производительности. Вы освоите синтаксис SQL, научитесь анализировать данные и управлять ими на профессиональном уровне.

1. Введение в базы данных и основы SQL: структура таблиц и базовые операции CRUD

Введение в базы данных и основы SQL: структура таблиц и базовые операции CRUD

Добро пожаловать в курс «SQL: От Новичка до Профессионала»! Мы начинаем наше путешествие в мир данных с самых основ. В современном мире данные — это новая нефть. Каждое приложение, которым вы пользуетесь — от социальной сети до банковского клиента — работает с огромными объемами информации. Но как эта информация хранится, и, что более важно, как мы можем с ней взаимодействовать?

В этой статье мы разберем, что такое базы данных, почему они лучше обычных таблиц в Excel, изучим структуру реляционных таблиц и освоим четыре главных глагола языка SQL, на которых держится весь интернет.

Что такое база данных и зачем она нужна?

Представьте, что вы владелец небольшого книжного магазина. Чтобы вести учет книг, вы заводите толстую тетрадь. В ней вы записываете название книги, автора, цену и количество на складе. Со временем тетрадь заканчивается, вы заводите вторую, третью. Найти конкретную книгу становится сложно. Вы решаете перенести всё в Excel. Это удобно, пока у вас один магазин. Но что, если магазинов становится десять? А продавцов — пятьдесят? И все они пытаются одновременно открыть один и тот же файл, внося изменения.

Здесь на сцену выходят Базы Данных (БД).

База данных — это организованная коллекция структурированной информации или данных, обычно хранящаяся в электронном виде в компьютерной системе. Чтобы управлять этой базой, нам нужно специальное программное обеспечение — СУБД (Система Управления Базами Данных).

!Взаимодействие пользователя, приложения и СУБД

Почему не Excel?

Хотя электронные таблицы отлично подходят для простых задач, базы данных предлагают:

  • Масштабируемость: БД могут хранить миллионы и миллиарды записей без потери производительности.
  • Безопасность: Вы можете настроить права доступа так, чтобы стажер мог только читать данные, а менеджер — изменять их.
  • Целостность данных: БД следит за тем, чтобы вы не ввели текст в поле для цены или не удалили книгу, которую только что купили.
  • Многопользовательский доступ: Сотни пользователей могут работать с данными одновременно.
  • Реляционные базы данных: Мир таблиц

    Существует много типов баз данных, но самыми популярными и распространенными являются реляционные базы данных (от англ. relation — отношение, связь). В таких системах данные хранятся в таблицах, которые могут быть связаны друг с другом.

    Анатомия таблицы

    Реляционная таблица очень похожа на лист в Excel, но имеет строгие правила. Давайте рассмотрим структуру на примере таблицы Books (Книги).

    !Структура реляционной таблицы: строки, столбцы и первичный ключ

    Основные элементы таблицы:

    * Таблица (Table): Объект, где хранятся данные по определенной теме (например, «Книги» или «Клиенты»). * Столбец (Column) или Поле (Field): Определяет атрибут данных. Например, у книги есть атрибуты: «Название», «Автор», «Цена». Каждый столбец имеет строго определенный тип данных (число, текст, дата). * Строка (Row) или Запись (Record): Это конкретный экземпляр данных. Одна строка — это одна конкретная книга со всеми её характеристиками. * Первичный ключ (Primary Key): Это уникальный идентификатор каждой строки. Обычно это числовой столбец id. Он гарантирует, что мы сможем отличить две книги с одинаковым названием, но разными изданиями.

    SQL: Язык общения с данными

    Чтобы «поговорить» с базой данных, нам нужен общий язык. Этим языком является SQL (Structured Query Language — язык структурированных запросов).

    Важно понимать: SQL — это декларативный язык. Это значит, что вы говорите системе, что вы хотите получить, а не как это сделать. Вы не пишете алгоритм поиска книги; вы просто говорите: «Дай мне книгу с названием

    2. Работа с несколькими таблицами: типы соединений JOIN, агрегатные функции и группировка данных

    Работа с несколькими таблицами: типы соединений JOIN, агрегатные функции и группировка данных

    В предыдущей статье мы научились создавать таблицы и манипулировать данными внутри одной сущности. Мы создали книжный магазин, наполнили его книгами и научились их искать. Но давайте будем честны: реальный мир сложнее одной таблицы.

    Представьте, что наш книжный магазин вырос. Теперь у нас есть не только книги, но и авторы (с их биографиями), клиенты (с адресами) и заказы. Если мы попытаемся записать имя клиента, его адрес, дату заказа и список купленных книг в одну таблицу, мы получим хаос. Мы будем дублировать адрес клиента для каждой купленной им книги. Это нарушает главный принцип баз данных — нормализацию (избегание избыточности).

    Поэтому данные разбивают на разные таблицы. Но как теперь получить отчет: «Кто купил 'Войну и мир'?» или «Сколько книг написал Стивен Кинг?». Для этого нам нужно научиться соединять таблицы и агрегировать данные. Добро пожаловать в мир JOIN и GROUP BY.

    Соединение таблиц: Магия JOIN

    Чтобы связать две таблицы, нам нужны общие точки соприкосновения. Обычно это Первичный ключ (Primary Key) одной таблицы и Внешний ключ (Foreign Key) другой.

    Допустим, у нас есть две таблицы:

  • Authors (Авторы): id, name
  • Books (Книги): id, title, author_id
  • Поле author_id в таблице книг ссылается на поле id в таблице авторов. Это и есть наша связующая нить.

    1. INNER JOIN (Внутреннее соединение)

    Это самый распространенный тип соединения. INNER JOIN возвращает только те строки, для которых нашлось совпадение в обеих таблицах.

    !Схема работы INNER JOIN: выбираются только общие данные.

    Синтаксис:

    Результат: Вы увидите список авторов и их книг. * Если у автора нет книг, он не попадет в выборку. * Если есть книга без автора (что странно, но технически возможно), она тоже не попадет в выборку.

    2. LEFT JOIN (Левое соединение)

    Иногда нам нужно вывести всех авторов, даже если они пока ничего не написали (или мы просто не внесли их книги в базу). Здесь на помощь приходит LEFT JOIN.

    Он берет все строки из левой таблицы (той, что указана первой, сразу после FROM) и подтягивает к ним совпадающие строки из правой таблицы. Если совпадения нет, база данных подставит NULL (пустоту).

    !Схема работы LEFT JOIN: выбираются все данные из левой таблицы и совпадения из правой.

    Пример:

    Результат:

  • Стивен Кинг — «Сияние»
  • Стивен Кинг — «Оно»
  • Иван Новичков — NULL
  • Мы видим Ивана Новичкова, хотя книг у него в базе нет. Вместо названия книги стоит NULL.

    3. RIGHT JOIN и FULL JOIN

    RIGHT JOIN: Зеркальное отражение LEFT JOIN. Берет все строки из правой* таблицы и ищет совпадения в левой. На практике используется редко, так как проще поменять таблицы местами и использовать LEFT JOIN. * FULL JOIN: Возвращает строки, если есть совпадение хотя бы в одной из таблиц. Это объединение результатов левого и правого соединений. Если совпадения нет, с одной из сторон будут NULL.

    Агрегатные функции: Считаем и анализируем

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

    Самые популярные функции:

    * COUNT() — считает количество строк. * SUM() — считает сумму значений в столбце. * AVG() — вычисляет среднее арифметическое. * MIN() — находит минимальное значение. * MAX() — находит максимальное значение.

    Примеры:

    Сколько всего книг в магазине?

    Какова средняя цена книги?

    Самая дорогая книга?

    > Важно: Агрегатные функции (кроме COUNT(*)) игнорируют значения NULL. Если у книги не указана цена, она не будет участвовать в расчете среднего.

    Группировка данных: GROUP BY

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

    Представьте, что вы хотите узнать не просто «сколько всего книг», а «сколько книг написал каждый автор».

    !Визуализация того, как GROUP BY схлопывает множество строк в итоговые группы.

    Синтаксис:

    Как это работает:

  • СУБД берет таблицу Books.
  • Сортирует и объединяет строки с одинаковым author_id в виртуальные «пачки».
  • Применяет функцию COUNT(*) к каждой «пачке» отдельно.
  • Мы можем усложнить запрос и соединить его с JOIN, чтобы видеть имена авторов, а не их ID:

    Фильтрация сгруппированных данных: HAVING

    Это один из самых частых вопросов на собеседованиях: «В чем разница между WHERE и HAVING?».

    * WHERE фильтрует строки ДО группировки. * HAVING фильтрует группы ПОСЛЕ группировки.

    Пример: Мы хотим найти авторов, которые написали больше 5 книг.

    Неправильно (вызовет ошибку):

    Правильно:

    Порядок выполнения запроса

    Чтобы стать профессионалом, важно понимать, в каком порядке SQL «читает» ваш запрос. Это отличается от того, как вы его пишете:

  • FROM / JOIN (Откуда берем данные?)
  • WHERE (Какие строки отбираем?)
  • GROUP BY (Как группируем?)
  • HAVING (Какие группы оставляем?)
  • SELECT (Какие колонки показываем и вычисляем?)
  • ORDER BY (Как сортируем итог?)
  • LIMIT (Сколько строк возвращаем?)
  • Заключение

    Сегодня мы сделали огромный шаг вперед. Мы перешли от простых запросов к одной таблице к построению сложных отчетов, связывающих данные из разных источников. Мы изучили:

  • JOIN — для объединения таблиц по ключам.
  • Агрегатные функции — для математического анализа данных.
  • GROUP BY — для сегментации данных.
  • HAVING — для фильтрации результатов агрегации.
  • В следующей статье мы углубимся в тему вложенных запросов (подзапросов), которые позволят решать еще более нетривиальные задачи.

    3. Проектирование баз данных: нормализация, типы данных, первичные и внешние ключи

    Проектирование баз данных: нормализация, типы данных, первичные и внешние ключи

    В предыдущих статьях мы научились извлекать данные с помощью SELECT, фильтровать их, соединять таблицы через JOIN и даже группировать результаты. Но мы работали с уже готовыми таблицами, которые кто-то заботливо создал до нас. В реальной жизни, прежде чем писать запросы, нужно спроектировать структуру хранения данных.

    Представьте, что вы строите дом. SELECT и JOIN — это отделка, покраска стен и расстановка мебели. Проектирование базы данных (БД) — это заливка фундамента и возведение несущих стен. Если ошибиться на этом этапе, дом может рухнуть под собственным весом, как только вы добавите второй этаж.

    В этой статье мы разберем, как превратить хаотичный набор данных в стройную, эффективную и надежную структуру. Мы поговорим о типах данных, ключах и «Священном Граале» проектировщиков — нормализации.

    Типы данных: Фундамент каждой колонки

    Когда вы создаете таблицу в Excel, вы можете писать в ячейку что угодно: сегодня число, завтра текст, послезавтра дату. Базы данных такого не прощают. Для каждого столбца мы должны строго определить тип данных. Это позволяет СУБД (Системе Управления Базами Данных) оптимизировать хранение и предотвращать ошибки.

    Рассмотрим основные категории типов данных, которые используются в 99% случаев.

    1. Числовые типы (Numeric)

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

    * INTEGER (INT): Целые числа. Например, количество книг на складе, ID пользователя, год издания. * DECIMAL / NUMERIC: Числа с фиксированной точностью. Идеально подходят для денег. Например, цена книги 599.99. В отличие от типов с плавающей точкой (FLOAT), здесь нет ошибок округления. * FLOAT / DOUBLE: Числа с плавающей точкой. Используются для научных вычислений, где важен масштаб, а не идеальная точность до копейки (например, координаты GPS или вес в граммах).

    2. Строковые типы (String)

    * CHAR(n): Строка фиксированной длины. Если вы задали CHAR(10), а записали слово «SQL», база данных добьет остаток пробелами. Хорошо подходит для кодов стран (RU, US) или автомобильных номеров. * VARCHAR(n): Строка переменной длины. Самый популярный тип. VARCHAR(255) означает, что можно записать до 255 символов, но если вы запишете 3 символа, место будет занято только под 3 символа (плюс байт длины). Используется для имен, адресов, названий книг. * TEXT: Для очень длинных текстов, например, описания книги или содержания статьи блога.

    3. Дата и время (Date & Time)

    * DATE: Только дата (2023-10-05). День рождения, дата публикации. * TIME: Только время (14:30:00). Время начала урока. * DATETIME / TIMESTAMP: И дата, и время. Момент совершения покупки.

    4. Логический тип (Boolean)

    * BOOLEAN: Хранит TRUE (истина) или FALSE (ложь). Например, флаг is_active (активен ли пользователь) или in_stock (есть ли товар в наличии).

    > Совет: Всегда выбирайте минимально необходимый тип данных. Не используйте TEXT для номера телефона и FLOAT для цен. Это экономит место и ускоряет поиск.

    Ключи: Скелет базы данных

    Чтобы таблицы могли взаимодействовать друг с другом (как мы делали это с помощью JOIN), нам нужны специальные метки — ключи.

    Первичный ключ (Primary Key)

    Первичный ключ (PK) — это столбец (или набор столбцов), который уникально идентифицирует каждую строку в таблице.

    Представьте паспортный стол. У многих людей могут совпадать Имя и Фамилия. Даже дата рождения может совпасть. Но номер паспорта уникален. В базе данных роль номера паспорта играет Primary Key.

    Правила первичного ключа:

  • Уникальность: Значение не может повторяться.
  • NOT NULL: Ключ не может быть пустым.
  • Неизменность: В идеале, значение первичного ключа никогда не должно меняться.
  • Чаще всего в качестве PK используют искусственное поле id (целое число), которое автоматически увеличивается с каждой новой записью (AUTO_INCREMENT).

    Внешний ключ (Foreign Key)

    Внешний ключ (FK) — это столбец, который ссылается на Первичный ключ другой таблицы. Именно он создает связь между таблицами.

    Если в таблице Orders (Заказы) есть поле customer_id, которое указывает на id в таблице Customers (Клиенты), то customer_id — это внешний ключ.

    !Визуализация связи Первичного и Внешнего ключей.

    Зачем это нужно? Внешние ключи обеспечивают целостность данных. База данных не позволит вам: * Добавить заказ для клиента, которого не существует в таблице Customers. * Удалить клиента, у которого есть активные заказы (сначала нужно удалить заказы).

    Нормализация: Наводим порядок

    Нормализация — это процесс организации данных в базе, цель которого — устранить избыточность (дублирование) и обеспечить логическую связность.

    Существует несколько «Нормальных форм» (НФ). Мы рассмотрим три основные, которых достаточно для 95% задач.

    Первая нормальная форма (1НФ): Атомарность

    Правило: В каждой ячейке таблицы должно храниться только одно значение. Таблица не должна содержать списков.

    Пример нарушения: Представьте таблицу Students, где в поле Phone записано: «89001112233, 89005556677».

    Почему это плохо? Как найти студента по второму номеру телефона? Как изменить один из номеров? Придется считывать строку, разбивать её запятыми, искать нужное... Это медленно и неудобно.

    Решение: Либо создать отдельную строку для каждого телефона, либо (что лучше) вынести телефоны в отдельную таблицу Phones.

    Вторая нормальная форма (2НФ): Зависимость от ключа

    Правило: Таблица должна находиться в 1НФ, и все неключевые столбцы должны зависеть от полного первичного ключа.

    Это правило актуально, когда у вас составной первичный ключ (состоит из двух и более полей).

    Пример нарушения: Таблица OrderItems (Состав заказа). Ключ составной: OrderID + ProductID. Поля: OrderID, ProductID, Quantity (количество), ProductName (название товара).

    Здесь Quantity зависит от обоих ключей (сколько конкретного товара в конкретном заказе). А вот ProductName зависит только от ProductID. Название товара не меняется от заказа к заказу.

    Почему это плохо? Мы дублируем название товара в каждой строке заказа. Если название изменится, нам придется обновлять миллионы строк заказов.

    Решение: Вынести ProductName в отдельную таблицу Products.

    Третья нормальная форма (3НФ): Нет транзитивных зависимостей

    Правило: Таблица должна быть во 2НФ, и неключевые столбцы не должны зависеть от других неключевых столбцов.

    Проще говоря: «Все поля должны зависеть от ключа, всего ключа и ничего кроме ключа».

    Пример нарушения: Таблица Customers. Поля: id, Name, City, ZipCode (почтовый индекс).

    Здесь City зависит от ZipCode. Если мы знаем индекс, мы знаем город. Получается цепочка: id -> ZipCode -> City.

    Почему это плохо? Если в городе поменяется индексация, нам придется искать всех клиентов и менять им города. Также есть риск, что для одного индекса мы по ошибке напишем разные города.

    Решение: Создать таблицу ZipCodes (ZipCode, City) и оставить в клиентах только индекс.

    > Примечание: На практике 3НФ иногда нарушают сознательно ради производительности, чтобы не делать лишний JOIN для получения названия города. Это называется денормализацией, но делать это нужно осознанно.

    !Процесс разделения одной большой таблицы на несколько нормализованных.

    Практический пример: Книжный магазин

    Давайте применим знания и спроектируем базу для нашего магазина, избегая ошибок.

    Плохой дизайн (Excel-style): Одна таблица Sales: OrderDate, CustomerName, CustomerEmail, BookTitle, AuthorName, Price, Quantity.

    Проблемы:

  • Имя автора дублируется для каждой его книги.
  • Данные клиента дублируются для каждого заказа.
  • Если удалить все заказы книги, мы забудем, что такая книга вообще существовала.
  • Хороший дизайн (Нормализованный):

  • Authors (Авторы)
  • * id (PK, INT) * name (VARCHAR)

  • Books (Книги)
  • * id (PK, INT) * title (VARCHAR) * price (DECIMAL) * author_id (FK, INT) -> ссылается на Authors.id

  • Customers (Клиенты)
  • * id (PK, INT) * email (VARCHAR, Unique) * name (VARCHAR)

  • Orders (Заказы)
  • * id (PK, INT) * order_date (DATETIME) * customer_id (FK, INT) -> ссылается на Customers.id

  • OrderDetails (Детали заказа — связующая таблица)
  • * order_id (FK, INT) * book_id (FK, INT) * quantity (INT) Составной PK:* (order_id, book_id)

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

    Заключение

    Проектирование базы данных — это баланс между строгими правилами нормализации и реальной производительностью.

    Главные выводы:

  • Выбирайте правильные типы данных (числа для чисел, даты для дат).
  • Используйте Первичные ключи для идентификации строк.
  • Связывайте таблицы через Внешние ключи.
  • Применяйте Нормализацию, чтобы избежать хаоса и дублей.
  • Теперь, когда у нас есть надежный фундамент, в следующей статье мы поговорим о том, как изменять структуру уже существующих таблиц и управлять данными с помощью команд INSERT, UPDATE и DELETE.

    4. Продвинутый SQL: подзапросы, оконные функции и обобщенные табличные выражения (CTE)

    Продвинутый SQL: подзапросы, оконные функции и обобщенные табличные выражения (CTE)

    Мы прошли долгий путь от создания простых таблиц до проектирования надежных баз данных. Вы уже умеете извлекать данные, фильтровать их, соединять таблицы и группировать результаты. Казалось бы, что еще нужно?

    Но реальные аналитические задачи часто выходят за рамки возможностей обычного GROUP BY. Представьте, что ваш начальник просит не просто найти самую дорогую книгу, а составить рейтинг книг по каждому автору отдельно, или сравнить продажи текущего месяца с предыдущим в одной строке.

    Если пытаться решить это с помощью обычных JOIN и группировок, запросы превратятся в нечитаемых монстров. Здесь на сцену выходят инструменты профессионалов: подзапросы, CTE и оконные функции. Это «швейцарский нож» SQL-разработчика, который превращает сложные задачи в элегантные решения.

    Подзапросы (Subqueries): Запрос внутри запроса

    Подзапрос (или вложенный запрос) — это SQL-запрос, который находится внутри другого запроса. Это как матрешка: вы открываете одну инструкцию, а внутри нее спрятана другая.

    Подзапросы чаще всего используются в блоке WHERE для фильтрации, но могут встречаться и в SELECT, и в FROM.

    Простой пример

    Допустим, мы хотим найти все книги, которые стоят дороже средней цены по магазину.

    Алгоритм действий:

  • Найти среднюю цену (SELECT AVG(price) FROM Books).
  • Использовать это число, чтобы отфильтровать книги.
  • С помощью подзапроса мы делаем это в один шаг:

    SQL сначала выполнит внутренний запрос (в скобках), получит число (например, 500), и затем подставит его во внешний запрос: WHERE price > 500.

    !Визуализация принципа работы подзапроса: внутренний запрос выполняется первым и передает результат внешнему.

    Типы подзапросов

  • Скалярные: Возвращают одно единственное значение (как в примере выше). Используются с операторами сравнения (=, >, <).
  • Столбцовые: Возвращают список значений (один столбец). Используются с оператором IN.
  • Пример:* Найти авторов, которые написали книги в жанре «Фантастика».
  • Табличные: Возвращают полноценную таблицу. Обычно используются в блоке FROM.
  • Обобщенные табличные выражения (CTE)

    Подзапросы — это мощно, но когда их становится много, код превращается в «спагетти». Читать запрос с тремя уровнями вложенности — то еще удовольствие.

    Для решения проблемы читаемости были придуманы CTE (Common Table Expressions). По сути, это именованные временные таблицы, которые существуют только во время выполнения одного запроса.

    Синтаксис начинается с ключевого слова WITH.

    Рефакторинг с помощью CTE

    Давайте перепишем пример со средней ценой, используя CTE:

    Преимущества CTE:

  • Читаемость: Вы даете понятные имена блокам логики.
  • Повторное использование: Вы можете обратиться к таблице AveragePrice несколько раз в основном запросе, не переписывая код вычисления.
  • Декомпозиция: Сложную задачу можно разбить на несколько простых шагов (несколько CTE через запятую).
  • Оконные функции: Магия аналитики

    Если подзапросы и CTE — это просто удобные инструменты, то оконные функции — это настоящая революция в SQL.

    Вспомните GROUP BY. Он берет несколько строк и «схлопывает» их в одну (например, считает сумму продаж по месяцу). При этом мы теряем детализацию: мы видим сумму, но не видим отдельные заказы.

    Оконные функции позволяют выполнять вычисления (суммы, средние, ранги) по набору строк, не схлопывая их. Строки остаются на месте, но к ним добавляется новый вычисляемый столбец.

    Синтаксис всегда включает ключевое слово OVER().

    !Различие между группировкой, которая уменьшает количество строк, и оконными функциями, которые сохраняют исходные строки.

    1. Нумерация строк: ROW_NUMBER()

    Задача: Пронумеровать книги каждого автора по убыванию цены. Самая дорогая книга автора должна получить номер 1.

    Разберем магию OVER: * PARTITION BY author_id: Мы делим таблицу на виртуальные «окна» (группы) по каждому автору. Нумерация будет сбрасываться для каждого нового автора. * ORDER BY price DESC: Внутри каждого «окна» мы сортируем книги от дорогой к дешевой.

    Результат: | title | author_id | price | rank_in_author | |---|---|---|---| | Книга А (Кинг) | 1 | 1000 | 1 | | Книга Б (Кинг) | 1 | 500 | 2 | | Книга В (Роулинг) | 2 | 800 | 1 |

    2. Ранжирование: RANK() и DENSE_RANK()

    Что если у двух книг одинаковая цена? ROW_NUMBER просто присвоит им разные номера (например, 1 и 2) произвольно или по дополнительной сортировке. Но иногда нам нужно честное соревнование.

    * RANK(): Если цены равны, ранг будет одинаковым (например, 1, 1), но следующий номер будет пропущен (1, 1, 3). * DENSE_RANK(): Ранги одинаковые (1, 1), но пропусков нет (1, 1, 2). «Плотное» ранжирование.

    3. Заглянуть в прошлое и будущее: LAG() и LEAD()

    Это незаменимые функции для сравнения данных во времени.

    LAG(column): Берет значение из предыдущей* строки. LEAD(column): Берет значение из следующей* строки.

    Пример: Мы хотим увидеть, как менялась цена заказов. Насколько текущий заказ дороже предыдущего?

    В одной строке мы получаем доступ к данным из другой строки без сложных соединений таблицы самой с собой!

    4. Агрегация в окне

    Мы можем использовать привычные SUM, AVG, COUNT как оконные функции. Это часто используется для подсчета нарастающего итога (Running Total).

    Здесь SUM будет складывать суммы всех заказов от начала времен до текущей строки включительно.

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

    Чтобы стать профессионалом, нужно понимать уместность инструментов:

  • Подзапросы: Идеальны для разовых фильтраций (WHERE id IN (...)). Если подзапрос становится слишком большим — переделывайте в CTE.
  • CTE: Используйте для сложных отчетов, где нужно подготовить данные в несколько этапов. Это делает код чистым и поддерживаемым.
  • Оконные функции: Используйте всегда, когда нужно сравнить текущую строку с другими строками группы (рейтинги, динамика изменений, нарастающие итоги), не теряя самих строк.
  • Заключение

    Сегодня вы перешли черту, отделяющую новичка от уверенного пользователя SQL. Подзапросы, CTE и оконные функции — это тот арсенал, который спрашивают на собеседованиях в топовые компании.

    Эти инструменты позволяют переложить сложную логику обработки данных с языка программирования (Python, Java) на плечи базы данных, которая справляется с этим гораздо быстрее и эффективнее.

    В следующей, заключительной части курса, мы поговорим о том, как заставить все эти запросы летать: мы разберем индексы, оптимизацию производительности и транзакции.

    5. Оптимизация производительности и администрирование: индексы, транзакции, хранимые процедуры и триггеры

    Оптимизация производительности и администрирование: индексы, транзакции, хранимые процедуры и триггеры

    Поздравляю! Если вы читаете эту статью, значит, вы уже прошли путь от создания первой таблицы до написания сложных аналитических запросов с оконными функциями. Вы умеете проектировать базы данных и извлекать из них информацию. Но в мире профессиональной разработки мало просто «получить данные». Важно получить их быстро, безопасно и гарантированно.

    Представьте, что ваша база данных — это библиотека. Пока в ней 100 книг, библиотекарь (СУБД) найдет нужную за секунду. Но когда книг становится миллион, поиск превращается в проблему. А что, если в библиотеке начнется пожар во время выдачи книги? Или если кто-то попытается украсть читательский билет?

    В этой финальной статье курса мы превратимся из пользователей базы данных в её администраторов и архитекторов. Мы разберем четыре кита, на которых держится производительность и надежность корпоративных систем: Индексы, Транзакции, Хранимые процедуры и Триггеры.

    Индексы: Скорость света для ваших запросов

    Самая частая проблема новичков — медленные запросы. Вы пишете SELECT * FROM Users WHERE email = 'alex@example.com', и база данных «зависает» на 10 секунд. Почему?

    По умолчанию, когда вы просите СУБД найти строку, она выполняет операцию Full Table Scan (Полное сканирование таблицы). Она берет первую строку, проверяет email, берет вторую, третью... и так до самого конца миллионной таблицы. Это неэффективно.

    Индекс — это специальная структура данных, которая работает как алфавитный указатель в конце книги. Вместо того чтобы листать все страницы в поисках термина, вы смотрите в указатель, находите номер страницы и сразу открываете её.

    !Визуальное сравнение полного перебора и поиска по индексу.

    Как это работает (немного математики)

    Без индекса сложность поиска описывается как линейная зависимость:

    Где — это оценка сложности алгоритма («О» большое), а — количество строк в таблице. Если строк 1 000 000, нам может потребоваться 1 000 000 операций сравнения.

    С индексом (обычно используется структура B-Tree — сбалансированное дерево) сложность поиска снижается до логарифмической:

    Где — это логарифм от количества строк. Для 1 000 000 строк это всего около 20 операций сравнения! Разница колоссальная.

    Создание индекса

    Синтаксис прост:

    Теперь поиск по email будет мгновенным.

    Обратная сторона медали

    Если индексы так хороши, почему бы не создать их для каждого столбца?

  • Замедление записи: Каждый раз, когда вы делаете INSERT, UPDATE или DELETE, СУБД должна обновить не только саму таблицу, но и все индексы к ней. Чем больше индексов, тем медленнее запись.
  • Место на диске: Индексы занимают физическое место.
  • > Золотое правило: Создавайте индексы только для тех столбцов, по которым вы часто ищете (WHERE), соединяете таблицы (JOIN) или сортируете (ORDER BY).

    Транзакции: Всё или ничего

    Представьте банковский перевод. Вы переводите 1000 рублей другу. Процесс состоит из двух шагов:

  • Списать 1000 рублей с вашего счета.
  • Зачислить 1000 рублей на счет друга.
  • Что произойдет, если на сервере выключат электричество ровно между первым и вторым шагом? Деньги у вас списались, а другу не пришли. Они исчезли. В финансовом мире это катастрофа.

    Чтобы этого избежать, используются Транзакции.

    Транзакция — это последовательность операций, которая воспринимается базой данных как единое целое. Либо выполняются все операции успешно, либо не выполняется ни одна.

    Управление транзакцией

    * BEGIN TRANSACTION — начало транзакции. * COMMIT — подтверждение (сохранить изменения). * ROLLBACK — откат (отменить всё, что было сделано с момента начала).

    Пример безопасного перевода:

    ACID: Философия надежности

    Любая надежная транзакционная система должна соответствовать требованиям ACID:

  • A — Atomicity (Атомарность): Транзакция неделима. Либо всё, либо ничего.
  • C — Consistency (Согласованность): Транзакция переводит базу из одного корректного состояния в другое. Деньги не могут возникнуть из воздуха.
  • I — Isolation (Изолированность): Параллельные транзакции не должны мешать друг другу. Если два человека одновременно покупают последний билет в кино, купить должен только один.
  • D — Durability (Долговечность): Если система сказала «COMMIT» (успех), данные сохранены навсегда, даже если через секунду сгорит серверная.
  • !Иллюстрация принципов ACID, защищающих целостность данных.

    Хранимые процедуры: Код внутри базы

    Обычно мы пишем SQL-запросы в коде нашего приложения (на Python, Java, PHP). Но иногда логику выгоднее хранить прямо в базе данных. Для этого существуют Хранимые процедуры (Stored Procedures).

    Это, по сути, функции, написанные на SQL, которые сохраняются в СУБД и могут быть вызваны по имени.

    Зачем они нужны?

  • Производительность: Процедура компилируется один раз и кэшируется сервером. Это быстрее, чем каждый раз отправлять длинный текстовый запрос по сети.
  • Безопасность: Вы можете запретить пользователю прямой доступ к таблицам (INSERT, SELECT), но разрешить выполнение процедуры AddUser. Это защищает от ошибок и хакерских атак (SQL Injection).
  • Повторное использование: Логику «расчета годовой премии» можно написать один раз в базе, и её будут использовать и веб-сайт, и мобильное приложение, и система отчетности.
  • Пример (псевдокод)

    Теперь, чтобы дать скидку, программисту достаточно вызвать: CALL GiveDiscount(55, 10);.

    Триггеры: Эффект бабочки

    Триггер — это специальный тип хранимой процедуры, которая запускается автоматически при наступлении определенного события (INSERT, UPDATE, DELETE).

    Это «сигнализация» вашей базы данных. Вы не вызываете триггер вручную, он срабатывает сам.

    Сценарии использования

  • Аудит и логирование: Кто-то изменил зарплату сотрудника? Триггер автоматически запишет в отдельную таблицу AuditLog: «Пользователь Admin изменил зарплату Bob с 5000 на 8000 в 14:00».
  • Валидация данных: Перед вставкой записи триггер может проверить, что возраст пользователя не отрицательный, и отменить операцию, если данные некорректны.
  • Автоматические расчеты: При добавлении товара в заказ триггер может автоматически пересчитать общую сумму заказа.
  • Пример триггера аудита

    > Предостережение: Триггеры — это скрытая логика. Если их слишком много, отладка превращается в ад. Вы делаете простой INSERT, а он вызывает триггер, который обновляет другую таблицу, которая вызывает свой триггер... Будьте осторожны.

    Заключение курса

    Мы прошли огромный путь. Вы начали с понимания того, что такое таблица, и дошли до управления транзакциями и оптимизации производительности.

    Теперь вы знаете: * Как структурировать данные (Нормализация). * Как манипулировать ими (CRUD). * Как анализировать их (JOIN, GROUP BY, Window Functions). * Как защищать и ускорять их (Индексы, Транзакции).

    SQL — это язык, который не стареет. Технологии меняются, фреймворки приходят и уходят, но данные остаются фундаментом любого бизнеса. И теперь вы умеете говорить с этим фундаментом на одном языке.

    Удачи в ваших проектах, и пусть ваши запросы всегда будут быстрыми, а данные — целостными!