🖥Нормализация БД. Часть 3. Третья нормальная форма (3NF) В прошлых постах мы разобрали две нормальные формы. 1NF помогла избавиться от списков товаров в одной ячейке. 2NF - отделить данные заказа от его позиций. Казалось бы, теперь с нашей БД всё нормально. Но давайте посмотрим на таблицу orders: order_id | customer_id | customer_name | phone
101 | 15 | Иван | 8900111 102 | 20 | Анна | 8900222 103 | 15 | Иван | 8900111 104 | 15 | Иван | 8900111 У таблицы простой первичный ключ order_id, поэтому частичной зависимости от составного ключа здесь нет. Условия 2NF выполнены. Но кое-что явно не так. Почему имя Ивана и его телефон повторяются в каждом заказе? 1. Какие проблемы могут возникнуть? Допустим, Иван поменял номер телефона. Нам придётся найти все его заказы и обновить каждую запись. UPDATE orders SET phone = '8900999' WHERE customer_id = 15; А если где-то забудем обновить телефон? Получится, что у одного покупателя в разных заказах хранятся разные номера, хотя по нашей модели поле должно содержать его актуальный телефон. Это аномалия обновления. Есть ещё две проблемы. Аномалия вставки. Покупатель зарегистрировался, но пока ничего не заказал. Куда сохранить его имя и телефон? Если вся информация о покупателях находится в orders, нам потребуется создать фиктивный заказ или вообще не сохранять покупателя. Аномалия удаления. Представим, что мы удалили единственный заказ Анны. Если других записей с её данными нет, вместе с заказом потеряем информацию о самой Анне. Получается, жизненный цикл покупателя почему-то зависит от жизненного цикла заказа. 2. Почему так происходит? Чтобы разобраться, познакомимся с функциональными зависимостями. Функциональная зависимость означает, что значение одного набора полей однозначно определяет значение другого. В нашем примере: order_id → customer_id По идентификатору заказа можно определить его покупателя. А по идентификатору покупателя можно определить его актуальное имя и телефон: customer_id → customer_name customer_id → phone Получается цепочка: order_id | v customer_id | v customer_name, phone Это называется транзитивной зависимостью. Имя и телефон определяются не непосредственно самим заказом, а через другое неключевое поле - customer_id. Именно такую проблему в нашем случае исправляет третья нормальная форма. 3. Что такое 3NF? В упрощённом виде правило звучит так: Таблица должна соответствовать 2NF, а неключевые атрибуты не должны транзитивно зависеть от её кандидатных ключей через другие неключевые атрибуты. Проще говоря, данные о покупателе должны храниться у покупателя, а данные о заказе - у заказа. Сейчас мы смешали две сущности: Заказ: идентификатор, дата, статус, покупатель. Покупатель: идентификатор, имя, телефон. Поэтому разделим их. 4. Приводим таблицу к 3NF Создаём отдельную таблицу покупателей: CREATE TABLE customers ( customer_id BIGINT PRIMARY KEY, name VARCHAR(100) NOT NULL, phone VARCHAR(20) ); Теперь таблицу заказов: CREATE TABLE orders ( order_id BIGINT PRIMARY KEY, customer_id BIGINT NOT NULL, order_date DATE NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ); Получится следующая структура: customers
customer_id | name | phone
15 | Иван | 8900111 20 | Анна | 8900222 orders
order_id | customer_id | order_date
101 | 15 | 15.09 102 | 20 | 16.09 103 | 15 | 17.09 104 | 15 | 18.09 Теперь Иван хранится в customers всего один раз. Если он поменяет номер телефона, достаточно обновить одну запись. А его заказы останутся на месте. Мы не потеряем покупателя при удалении заказа и сможем регистрировать новых покупателей, даже если они пока ничего не купили. Но здесь возникает интересный вопрос. А что, если нам нужно сохранить именно те данные покупателя, которые были указаны в момент оформления заказа? Разберём во второй части. #postgresql #базы_данных #системный_анализ