Нормализация
Накратко: Нормализацията разделя данните в таблици така, че всеки факт да е записан на едно място, за да няма аномалии при промяна, добавяне и изтриване.
Проблем
Excel-ът дойде в базата
Помниш таблицата с поръчките от първия урок на курса? Някой я е пренесъл в базата както е: един ред за всеки продукт в поръчка, а до него датата, клиентът, имейлът му и градът.
flat_orders
order_idinteger *ordered_attext *customertext *emailtext *citytextproducttext *pricereal *quantityinteger *
11 реда, а клиентите са трима. Името и имейлът на Мария са записани 6 пъти. Погледни и града ѝ в поръчка 6.
Идеята
Аномалии
Когато един факт е записан на много места, всяка промяна е риск. Тези проблеми се казват аномалии. Те са три вида.
При промяна. Мария сменя имейла си. Колегата го поправя само в последната ѝ поръчка:
Сега базата казва две неща наведнъж. Кой имейл е верният?
Виж го
Добавяне и изтриване
При добавяне. Магазинът започва да продава овесени ядки. Още никой не ги е поръчал. Къде да запишеш цената им?
Ред без поръчка не може да влезе. Продуктът съществува само докато някой го купи.
При изтриване. Елена отказва поръчка 4 и я трият. Това е единствената поръчка с тигана:
Заедно с поръчката изчезна и това, че магазинът продава тиган за 24,90 €.
Идеята
Нормални форми
Подреждането на таблиците така, че всеки факт да е на едно място, се казва нормализация. Правилата са на стъпки, нормални форми. Всяка следваща включва предишната.
Първа (1НФ): една стойност в клетка. Без списъци като „веган, био“ и без колони tag1, tag2, tag3.
Само белият хляб, макар че веган са и пълнозърнестият хляб, ябълките и кафето. Пълнозърнестият е веган, но клетката му е „веган, био“. С LIKE '%веган%' ще го намериш, но не можеш да преброиш продуктите по етикет, да поправиш „био“ навсякъде или да не пуснеш правописна грешка. Етикетите отиват в таблица за връзка, както в първия урок на модула.
Втора (2НФ): всяка колона зависи от целия ключ. Има значение, когато ключът е от няколко колони:
order_lines
- PK
order_idinteger - PK
product_idinteger product_nametext *quantityinteger *
Името на продукта зависи само от product_id, не от поръчката. Ще се повтаря във всяка поръчка с този продукт. Мястото му е в products.
Трета (3НФ): колоните зависят само от ключа, а не от друга колона:
products
- PK
idinteger nametext *category_idinteger *category_nametext *pricereal *
category_name се определя от category_id, не от продукта. Ако категорията се преименува, трябва да поправиш всички продукти в нея. Мястото на името е в categories.
По-точно, за любопитните
Кратката формула на трите форми е: всяка колона зависи „от ключа, от целия ключ и от нищо друго освен ключа“.
Има и по-високи форми (на Бойс и Код, четвърта, пета), но на практика таблица в трета форма обикновено вече няма тези аномалии.
flat_orders нарушава втората форма. Ключът на реда е поръчката и продуктът, а датата и клиентът зависят само от поръчката. Нарушава и третата: градът и имейлът зависят от клиента, не от поръчката.
Пробвай
Коя форма?
Идеята
Разделяне
Разделяш flat_orders на таблиците на магазина: customers, products, orders и order_items. Започваш с тези, които не сочат към никого.
INSERT може да вземе редовете от SELECT вместо от VALUES. С DISTINCT всеки продукт влиза веднъж:
products
- PK
idinteger nametext *pricereal *
От 11 реда остават 6 продукта, всеки с id. Цената остава и в редовете на поръчките, защото това е цената, на която е купено. Защо това не е повторение, ще видим в следващия урок.
Виж го
Excel-ът е мръсен
Същото с клиентите:
customers
- PK
idinteger nametext *emailtext *citytext
Грешка. В поръчка 6 градът на Мария е „Софя“. DISTINCT сравнява целия ред, затова вижда двама клиенти с един имейл, а UNIQUE не ги пуска. Това е добре: правилото хвана грешката, вместо да я пренесе в новата база.
Първо поправяш данните, после разделяш:
Почти всеки пренос от Excel започва с такова почистване.
Пробвай
Поръчките
orders
- PK
idinteger - FK
customer_idinteger *→ customers ordered_attext *
Продуктите и клиентите вече са разделени. Напълни orders (id, customer_id, ordered_at) от flat_orders. Номерът на поръчката е order_id, а клиентът се намира по имейла.
Решение
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id),
ordered_at TEXT NOT NULL
);
INSERT INTO orders (id, customer_id, ordered_at)
SELECT DISTINCT f.order_id, c.id, f.ordered_at
FROM flat_orders f
JOIN customers c ON c.email = f.email;INSERT INTO orders (id, customer_id, ordered_at) SELECT DISTINCT f.order_id, c.id, f.ordered_at FROM flat_orders f JOIN customers c ON c.email = f.email. Свързваш по имейла, защото той е уникален, а името може да се повтори. DISTINCT е нужен, защото поръчка с два продукта е на два реда. 7 поръчки.
Упражнение
Две задачи
1. Остава таблицата за връзка между поръчките и продуктите.
order_items
- PK
order_idinteger→ orders - PK
product_idinteger→ products quantityinteger *unit_pricereal *
Напълни order_items от flat_orders: номера на поръчката, id-то на продукта, броя и цената.
Решение
CREATE TABLE order_items (
order_id INTEGER NOT NULL REFERENCES orders(id),
product_id INTEGER NOT NULL REFERENCES products(id),
quantity INTEGER NOT NULL,
unit_price REAL NOT NULL,
PRIMARY KEY (order_id, product_id)
);
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT f.order_id, p.id, f.quantity, f.price
FROM flat_orders f
JOIN products p ON p.name = f.product;Всеки ред на flat_orders става ред на order_items, затова тук няма DISTINCT. Продуктът се намира по името, което вече е UNIQUE в products. 11 реда, колкото в началото.
2. Сега отчетът от първия урок на курса отнема една заявка. Колко е похарчил всеки клиент?
Покажи името на всеки клиент и сбора на покупките му (броя по цената):
Решение
SELECT c.name, SUM(oi.quantity * oi.unit_price) AS spent
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY c.id, c.name;Три таблици и GROUP BY. Мария 28,20 €, Георги 25,20 €, Елена 31,20 €. Ако Мария смени имейла си утре, това е един ред в customers.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Ограничения: правила за данните · Външни ключове: връзки между таблиците · Видове връзки: 1:1, 1:N, M:N · ER диаграма: от описание до схема
- Продължава в: Естествен ключ и денормализация · Документни бази · Пекарната, етап 1: базата и таблиците
- Задачи по темата: Проектиране на база