Урок по треку SQL и базы данных 6 мин

Проектирование и нормализация за 6 минут

Ключи, связи, нормальные формы, денормализация и ограничения.

Хорошая схема делает неправильные данные невозможными, а частые запросы простыми.

Ключи

  • Первичный ключ (PK) однозначно идентифицирует строку: уникален и не NULL. Обычно суррогатный: id bigint GENERATED ALWAYS AS IDENTITY или UUID.
  • Естественный ключ берётся из предметной области (ИНН, email). Он может меняться, поэтому его чаще делают UNIQUE, а PK оставляют суррогатным.
  • UUID против bigint: UUID можно генерировать на клиенте и в разных сервисах, его не угадать перебором. Но он в 2 раза длиннее, а случайный UUID v4 вставляется в случайное место B-tree и фрагментирует индекс. UUID v7 упорядочен по времени и этой проблемы почти лишён.
  • Внешний ключ (FK) гарантирует, что ссылка указывает на существующую строку. ON DELETE CASCADE удалит детей вместе с родителем, RESTRICT запретит удаление, SET NULL обнулит ссылку.

Связи

  • Один к одному: FK с ограничением UNIQUE (профиль пользователя).
  • Один ко многим: FK на стороне «многих» (orders.customer_id).
  • Многие ко многим: отдельная связующая таблица, например student_courses(student_id, course_id) с составным PK.

Нормализация

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

  • 1НФ: значения атомарны, нет повторяющихся групп. Колонка phones = '111, 222' нарушает 1НФ, телефоны выносят в отдельную таблицу.
  • 2НФ: 1НФ и каждая неключевая колонка зависит от всего составного ключа. В order_items(order_id, product_id, qty, product_title) название товара зависит только от product_id: нарушение.
  • 3НФ: 2НФ и нет транзитивных зависимостей: неключевая колонка не зависит от другой неключевой. employees(id, department_id, department_name): название отдела зависит от department_id, а не от сотрудника.
  • BCNF: строже 3НФ, каждый детерминант является потенциальным ключом. На собесе обычно достаточно понимать 1-3НФ.

Денормализация

Иногда данные дублируют сознательно: ради скорости чтения (счётчик comments_count у поста), ради истории (цена товара на момент заказа хранится в order_items.price, это не дубль, а другой факт). Денормализация требует механизма синхронизации: триггер, код приложения или фоновая пересборка.

Ограничения как защита

  • NOT NULL там, где значение обязательно.
  • CHECK (qty > 0), CHECK (ends_at > starts_at).
  • UNIQUE (user_id, slug).
  • Частичный уникальный индекс: CREATE UNIQUE INDEX ON users (email) WHERE deleted_at IS NULL разрешает повторно зарегистрировать удалённый email.

Проверка только в коде приложения не спасает от гонок: два параллельных запроса оба проверят «email свободен» и оба вставят. Ограничение в базе не даст.

Типы

  • Деньги: numeric(12,2) или целые копейки в bigint. Не float: 0.1 + 0.2 не равно 0.3.
  • Время: timestamptz, хранит момент в UTC и показывает в зоне сессии.
  • jsonb для действительно гибких атрибутов, а не вместо всей схемы.
  • text в PostgreSQL не медленнее varchar(n).

Частые ошибки

  • Список значений через запятую в одной колонке.
  • Деньги во float.
  • timestamp без зоны для событий.
  • Уникальность только в коде приложения.
  • FK без индекса на дочерней стороне.

Что спросят на собесе

  • Что такое нормальные формы, пример нарушения 2НФ и 3НФ.
  • Когда денормализация оправдана.
  • Как спроектировать связь многие ко многим.
  • UUID или автоинкремент: плюсы и минусы.
  • Как хранить деньги и время.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.