Зарегистрируйтесь, чтобы продолжить обучение

Первая нормальная форма Основы реляционных баз данных

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

Нормальная форма

Возьмем для примера интернет-магазин, в котором продается электроника. Когда пользователь делает заказ, в базу данных заносится запись об этом. В нее входит вся необходимая информация: данные пользователя, какой товар он купил, сколько он стоил и адрес доставки. Затем эти данные используются всеми подразделениями интернет-магазина — от бухгалтеров до службы доставки.

Таблица order_items

first_name last_name address item price
Сергей Иванов Москва, ул. Промышленная утюг 15.00
Иван Петров Самара, ул. Энгельса кофеварка 5000.00
Виктор Сидоров Омск, ул. Дворцовая утюг, телевизор 1000.00, 6500.00
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00

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

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

Всего существует шесть нормальных форм, которые включают определенные требования. С каждым следующим уровнем требования все жестче, так как включают в себя предыдущие уровни.

В рамках курса мы разберем три нормальные формы. В этом уроке познакомимся с первой.

Первая нормальная форма

Первая нормальная форма сводится к трем правилам:

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

Разберем каждое правило подробнее.

Каждая ячейка – одно значение

Вернемся к примеру выше. У одной записи поля item и price содержат два значения через запятую. У такого способа организации данных много недостатков. Например, пропадает возможность делать обычную выборку по условиям:

-- Как задать условие, чтобы получить все записи о проданных утюгах?
SELECT * FROM order_items WHERE item = ...;

Другая проблема связана с типами данных. Поле price в таблице order_items имеет числовой тип (numeric). Если мы захотим хранить там более одного значения, то тип превратится в строковый, а все данные станут обычными строками.

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

Чтобы избавиться от перечислений в ячейках, можно создать новые записи:

first_name last_name address item price
Сергей Иванов Москва, ул. Промышленная утюг 15.00
Иван Петров Самара, ул. Энгельса кофеварка 5000.00
Виктор Сидоров Омск, ул. Дворцовая утюг 1000.00
Виктор Сидоров Омск, ул. Дворцовая телевизор 6500.00
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00

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

Данные одного типа

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

Разные данные в рамках одного поля тоже не дают выполнить агрегирующие запросы, например, поиск сумм, максимального, минимального. Еще усложняется обработка данных на уровне кода. В коде придется каким-то образом понимать, что из себя представляют данные.

Вот еще несколько примеров с похожей ситуацией:

  • Хранение даты свадьбы в поле «день рождения»
  • Хранение номера телефона вместо адреса в поле «адрес»

Исправленная версия таблицы:

first_name last_name address item price
Сергей Иванов Москва, ул. Промышленная утюг 1000.00
Иван Петров Самара, ул. Энгельса кофеварка 5000.00
Виктор Сидоров Омск, ул. Дворцовая утюг 1000.00
Виктор Сидоров Омск, ул. Дворцовая телевизор 6500.00
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00

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

Уникальные записи

Последние две записи в таблице выглядят идентично, хотя это два разных заказа. Их сделал один человек, но в разное время:

first_name last_name address item price
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00
Сергей Иванов Москва, ул. Матросова ноутбук 20000.00

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

Реализовать уникальность можно несколькими способами, например, добавить новое поле с датой заказа, которое сделает запись уникальной. Этот способ не очень надежный и не очень удобный в работе. Придется постоянно анализировать весь набор полей.

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

Разберем два вида первичного ключа:

  • Естественный — когда используются значения из окружающего мира, например, email, ФИО или паспортные данные. При этом нужно убедиться, что ключ не будет повторяться. Такие первичные ключи используют редко из-за их ненадежности. Часто они не уникальны и могут изменяться или повторяться. Например, номер паспорта меняется при смене документа
  • Суррогатный — когда используются автоматически генерируемые уникальные значения. Такой ключ поддерживается любой базой данных «из коробки». Иногда это просто числа, а иногда — сложные число-буквенные строки или хеши

Добавим в нашу таблицу первичный ключ:

id first_name last_name address item price
8 Сергей Иванов Москва, ул. Промышленная утюг 1000.00
2 Иван Петров Самара, ул. Энгельса кофеварка 5000.00
7 Виктор Сидоров Омск, ул. Дворцовая утюг 1000.00
4 Виктор Сидоров Омск, ул. Дворцовая телевизор 6500.00
9 Сергей Иванов Москва, ул. Матросова ноутбук 20000.00
6 Сергей Иванов Москва, ул. Матросова ноутбук 20000.00

Первичный ключ принято создавать первым полем с названием id. Для первичного ключа обязательно указывать PRIMARY KEY в описании таблицы:

-- Первичный ключ только один на таблицу
CREATE TABLE products (
    id bigint PRIMARY KEY,
    first_name varchar(255),
    last_name varchar(255),
    address varchar(255),
    item varchar(255),
    price numeric -- специальный тип данных, который подходит под работу с деньгами. Обеспечивает высокую точность при расчетах.
);

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

Выводы

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

Также все данные в одной колонке могут быть только одного типа. Данные разного типа усложняют работу с таблицей, так как в коде придется каким-то образом понимать, что из себя представляют данные. А чтобы отличать записи друг от друга, нужно использовать суррогатный первичный ключ, который сделает каждую запись уникальной.


Дополнительные материалы

  1. Первичный ключ
  2. Внешний ключ
  3. Суррогатный ключ

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»