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

Ответ

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

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

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

    • Все значения атрибутов атомарны (неделимы).
    • В таблице нет повторяющихся групп или массивов.
    • Пример нарушения: Столбец PhoneNumbers со значением "123-456, 789-012". Исправление: вынести номера в отдельную таблицу CustomerPhones.
  2. Вторая нормальная форма (2НФ):

    • Таблица находится в 1НФ.
    • Каждый неключевой атрибут полностью зависит от всего составного первичного ключа (а не от его части).
    • Пример: Таблица OrderDetails(OrderID, ProductID, ProductName, Quantity). ProductName зависит только от ProductID, а не от всей пары (OrderID, ProductID). Нарушение. Исправление: вынести ProductID, ProductName в таблицу Products.
  3. Третья нормальная форма (3НФ):

    • Таблица находится в 2НФ.
    • Нет транзитивных зависимостей. Неключевые атрибуты не должны зависеть от других неключевых атрибутов.
    • Пример: Таблица Employees(EmployeeID, DepartmentID, DepartmentLocation). DepartmentLocation зависит от DepartmentID, который не является ключом. Нарушение. Исправление: вынести DepartmentID, DepartmentLocation в таблицу Departments.

Практический пример преобразования:

-- Исходная денормализованная таблица (нарушает 1НФ, 2НФ, 3НФ)
CREATE TABLE CustomerOrders (
    OrderID INT,
    CustomerName VARCHAR(100),
    CustomerPhone VARCHAR(20), -- Зависит от CustomerName, а не от OrderID (3НФ)
    ProductID INT,
    ProductName VARCHAR(100), -- Зависит только от ProductID (2НФ)
    Quantity INT
    -- Первичный ключ? Составной (OrderID, ProductID)
);

-- Нормализованная схема (до 3НФ)
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY,
    CustomerName VARCHAR(100) NOT NULL,
    CustomerPhone VARCHAR(20)
);

CREATE TABLE Products (
    ProductID INT PRIMARY KEY,
    ProductName VARCHAR(100) NOT NULL
);

CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT NOT NULL,
    OrderDate DATE,
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);

CREATE TABLE OrderDetails ( -- Разрешает составной PK для связи M:M
    OrderID INT NOT NULL,
    ProductID INT NOT NULL,
    Quantity INT NOT NULL,
    PRIMARY KEY (OrderID, ProductID),
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID),
    FOREIGN KEY (ProductID) REFERENCES Products(ProductID)
);

Зачем это нужно? Нормализация минимизирует дублирование, обеспечивает целостность данных и упрощает поддержку. Однако для сложных аналитических запросов (OLAP) часто применяется контролируемая денормализация для повышения скорости чтения за счет избыточности.

Ответ 18+ 🔞

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

Нормализация — это, по сути, такой способ расхламить твои таблицы в базе, чтобы одни и те же данные не валялись в десяти местах, а при обновлении чего-то одного у тебя всё не ебнулось к ебеням. Достигается это тем, что ты раскидывашь всё по разным полочкам — это и есть нормальные формы (НФ). Их, блядь, дохуя, но основные три.

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

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

    • Это как базовый уровень — не быть свиньёй. Каждая ячейка в таблице должна содержать одно простое значение, а не список, как в записной книжке алкаша. Никаких массивов или повторяющихся групп.
    • Нарушение, от которого волосы дыбом: Столбец PhoneNumbers со значением "123-456, 789-012, 555-666". Это пиздец. Исправление: завести отдельную табличку для телефонов, чтобы каждый номер был на своей строчке.
  2. Вторая нормальная форма (2НФ):

    • Представь, у тебя есть таблица заказов с составным ключом (ID заказа + ID товара). Так вот, если в этой же таблице у тебя лежит ещё и название товара, которое привязано только к ID товара, а не к паре «заказ-товар», то это полный распиздяй. Название товара должно жить отдельно, в своей уютной табличке Products.
    • Пример: OrderDetails(OrderID, ProductID, ProductName, Quantity). ProductName тут — как маньяк на детской площадке, он явно лишний. Выноси его к хуям в таблицу товаров.
  3. Третья нормальная форма (3НФ):

    • Тут уже тонкая работа. Если у тебя в таблице сотрудников есть не только отдел, но и адрес этого отдела, который зависит от отдела, а не от самого сотрудника — это транзитивная зависимость, и она нас не ебёт. Адрес отдела должен сидеть в таблице отделов, а не тащиться за каждым сотрудником, как хвост.
    • Пример: Employees(EmployeeID, DepartmentID, DepartmentLocation). DepartmentLocation — хитрая жопа, которая зависит от DepartmentID. Нарушение! Выноси DepartmentID и DepartmentLocation в отдельную таблицу Departments, и спи спокойно.

Смотри, как это выглядит на практике:

-- Исходная таблица, от которой у нормального человека терпения ноль ебать.
-- Нарушает всё, что можно. Пиздопроебибна схема.
CREATE TABLE CustomerOrders (
    OrderID INT,
    CustomerName VARCHAR(100),
    CustomerPhone VARCHAR(20), -- Этот телефон зависит от имени, а не от заказа! (Нарушение 3НФ)
    ProductID INT,
    ProductName VARCHAR(100), -- Название торчит тут просто так, зависит только от ProductID (Нарушение 2НФ)
    Quantity INT
    -- Первичный ключ? Да хуй его знает, вроде составной (OrderID, ProductID)
);

-- А вот схема после уборки. Почти как после хорошего клининга.
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY,
    CustomerName VARCHAR(100) NOT NULL,
    CustomerPhone VARCHAR(20)
);

CREATE TABLE Products (
    ProductID INT PRIMARY KEY,
    ProductName VARCHAR(100) NOT NULL
);

CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT NOT NULL,
    OrderDate DATE,
    FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);

CREATE TABLE OrderDetails ( -- Здесь уже порядок, составной ключ на месте.
    OrderID INT NOT NULL,
    ProductID INT NOT NULL,
    Quantity INT NOT NULL,
    PRIMARY KEY (OrderID, ProductID),
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID),
    FOREIGN KEY (ProductID) REFERENCES Products(ProductID)
);

Зачем этот геморрой? А затем, чувак, чтобы данные не повторялись, чтобы когда ты менял номер телефона клиента, он обновлялся в одном месте, а не в овердохуища разных заказах. Целостность, блядь. Но! Если тебе нужно делать супер-сложные отчёты и скорость чтения критична, иногда специально нарушают эти правила — делают денормализацию. Это как сознательно набросать носков в угол, чтобы быстрее собраться на работу. Но это уже осознанный выбор, а не беспросветный пиздец в твоей базе.