Что такое нормализация базы данных?

Ответ

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

Основные нормальные формы:

  1. 1NF (Первая нормальная форма): Устранить повторяющиеся группы. Все значения в столбцах атомарны (неделимы), каждая запись уникальна.
  2. 2NF (Вторая нормальная форма): Должна выполняться 1NF. Устранить частичные зависимости — все неключевые атрибуты должны полностью зависеть от всего первичного ключа (а не от его части).
  3. 3NF (Третья нормальная форма): Должна выполняться 2NF. Устранить транзитивные зависимости — неключевые атрибуты не должны зависеть от других неключевых атрибутов.

Пример на Node.js с PostgreSQL:

-- Денормализованная таблица (проблемы: дублирование, аномалии обновления)
CREATE TABLE denormalized_orders (
  order_id INT,
  customer_name VARCHAR,
  customer_email VARCHAR,
  product_name VARCHAR,
  product_price DECIMAL
);

-- Нормализованная схема (3NF)
CREATE TABLE customers (
  customer_id SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL,
  email VARCHAR UNIQUE NOT NULL
);

CREATE TABLE products (
  product_id SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL,
  price DECIMAL NOT NULL
);

CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT REFERENCES customers(customer_id) ON DELETE CASCADE,
  order_date TIMESTAMP DEFAULT NOW()
);

CREATE TABLE order_items (
  order_id INT REFERENCES orders(order_id) ON DELETE CASCADE,
  product_id INT REFERENCES products(product_id),
  quantity INT NOT NULL,
  PRIMARY KEY (order_id, product_id)
);

Практическое применение в Node.js: При работе с ORM, например, Sequelize, нормализация отражается в моделях и связях:

// Модели Sequelize, соответствующие нормализованной схеме
const Customer = sequelize.define('Customer', { name: DataTypes.STRING, email: DataTypes.STRING });
const Product = sequelize.define('Product', { name: DataTypes.STRING, price: DataTypes.DECIMAL });
const Order = sequelize.define('Order', { date: DataTypes.DATE });
const OrderItem = sequelize.define('OrderItem', { quantity: DataTypes.INTEGER });

// Определение связей
Customer.hasMany(Order);
Order.belongsTo(Customer);
Order.belongsToMany(Product, { through: OrderItem });
Product.belongsToMany(Order, { through: OrderItem });

Преимущества: Целостность данных, эффективное использование памяти, упрощение операций обновления. Недостаток: Усложнение запросов из-за необходимости частых JOIN, что может влиять на производительность для сложных выборок.

Ответ 18+ 🔞

А, нормализация, ёпта! Ну это когда ты пытаешься навести порядок в своей базе данных, чтобы она не превратилась в такой же пиздец, как твоя квартира после трёхдневной пьянки. Представь, что у тебя одна таблица, куда ты скидываешь всё подряд: и кто заказ сделал, и что купил, и почту клиента, и цену товара. Через месяц ты сам от себя охуеешь, когда поймёшь, что почту клиента надо поменять в овердохуища записей, а товар переименовали — и ты опять в пизде.

Так вот, чтобы этого не было, умные дядьки придумали нормальные формы. Это как инструкция по сборке шкафа из Икеи, только если её не соблюдать, у тебя не просто дверца отвалится, а вся твоя CRM-ка накроется медным тазом.

Вот эти самые формы, коротко, чтобы не бздеть:

  1. Первая нормальная форма (1NF): Это базовый уровень, чувак. Прекрати пихать в одну ячейку кучу данных через запятую, как список покупок. Каждое значение — отдельно, каждая запись — уникальна. Никаких массивов в столбцах, ядрёна вошь!
  2. Вторая нормальная форма (2NF): Тут уже интереснее. Допустим, у тебя ключ составной (заказ + товар). И у товара есть цена. Так вот, цена должна зависеть от всего ключа (от пары заказ-товар)? Нет, блядь, она зависит только от товара! Это частичная зависимость, от неё надо избавляться. Выноси товар с его ценой в отдельную таблицу, не будь распиздяем.
  3. Третья нормальная форма (3NF): А это уже высший пилотаж. Смотри: у тебя в заказе есть ID клиента, а у клиента — его менеджер. Получается, менеджер зависит от заказа через клиента? Это транзитивная зависимость, хуй с горы. Выноси клиентов с их менеджерами в свою таблицу. Правило простое: неключевые поля должны зависеть только от первичного ключа, а не друг от друга. Иначе волнение ебать — обновил менеджера у клиента в одном месте, а в старых заказах он старый и остался. Пизда рулю.

Пример, как было и как стало:

Было — бардак полный, одна таблица на весь мир:

CREATE TABLE denormalized_orders (
  order_id INT,
  customer_name VARCHAR, -- А если имя изменится? Обновлять все заказы? Да похуй, правда?
  customer_email VARCHAR, -- То же самое. Доверия ебать ноль к таким данным.
  product_name VARCHAR, -- Переименовали товар? Удачи.
  product_price DECIMAL
);

Стало — порядок, красота, 3NF:

-- Клиенты отдельно
CREATE TABLE customers (
  customer_id SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL,
  email VARCHAR UNIQUE NOT NULL -- Один раз поменял и везде ок.
);

-- Товары отдельно
CREATE TABLE products (
  product_id SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL,
  price DECIMAL NOT NULL
);

-- Заказы (только факт заказа и кто)
CREATE TABLE orders (
  order_id SERIAL PRIMARY KEY,
  customer_id INT REFERENCES customers(customer_id) ON DELETE CASCADE,
  order_date TIMESTAMP DEFAULT NOW()
);

-- А что именно в заказе — в отдельной связующей таблице
CREATE TABLE order_items (
  order_id INT REFERENCES orders(order_id) ON DELETE CASCADE,
  product_id INT REFERENCES products(product_id),
  quantity INT NOT NULL,
  PRIMARY KEY (order_id, product_id)
);

Как это в ноде крутить, если ты на ORM типа Sequelize:

// Описываешь модели, которые отражают твой новый, чистый мир.
const Customer = sequelize.define('Customer', { name: DataTypes.STRING, email: DataTypes.STRING });
const Product = sequelize.define('Product', { name: DataTypes.STRING, price: DataTypes.DECIMAL });
const Order = sequelize.define('Order', { date: DataTypes.DATE });
const OrderItem = sequelize.define('OrderItem', { quantity: DataTypes.INTEGER });

// И говоришь им, как они друг с другом связаны. Это магия, бля.
Customer.hasMany(Order);
Order.belongsTo(Customer);
Order.belongsToMany(Product, { through: OrderItem }); // Вот эта связь "многие-ко-многим" через OrderItem — это и есть суть нормализации.
Product.belongsToMany(Order, { through: OrderItem });

Итог, ёпта:

  • Плюсы: Данные целые, не дублируются, обновлять что-то одно в одном месте — просто песня. Памяти жрёт меньше.
  • Минус: Чтобы собрать полную информацию о заказе, тебе теперь надо склеить (JOIN) кучу таблиц. Если делать это бездумно и часто, можно так нагрузить базу, что она тебе скажет «иди ты нахуй» и будет права. Но это уже вопрос оптимизации запросов и индексов, другая история.