C, PHP, VB, .NET

Дневникът на Филип Петров


* Шеста нормална форма

Публикувано на 14 март 2015 в раздел Бази от Данни.

Шеста нормална форма (6НФ) е предложена през 1990 г. от Кристофър Дейт с цел да бъдат разрешени проблеми с дублиране на информация при т.нар. "времеви масиви с информация" (temporal data). Става въпрос за таблици, в които записваме информация за настоящи и предишни варианти на дадени записи. Нека например разгледаме следната таблица:

CREATE TABLE customers(
 `id` INT UNSIGNED NOT NULL,
 `from` DATE NOT NULL,
 `to` DATE NOT NULL,
 `phone` VARCHAR(16) NOT NULL,
 `address` VARCHAR(255) NOT NULL,
 PRIMARY KEY(`id`, `from` , `to`)
 );

В тази таблица ние пазим информация за това какъв е телефона на даден клиент и на кой адрес живее. Но нямаме само това - ние също така пазим и различните му адреси или телефони през годините - тоест това е архивна информация. From указва от кога е започнало събитието, а to - до кога. Ако to е 0000-00-00, приемаме, че и към момента информацията е валидна, т.е. това е актуалния запис за този клиент. Тази таблица отговаря на 5НФ, но е очевидно, че ще съдържа голямо количество от дублираща се информация. Ето един пример:

INSERT INTO customers(id, from, to, phone, address) VALUES
 (1, "2012-01-01", "2012-03-01", "0888-555-555", "Stamboliiski 20"),
 (1, "2012-03-01", "2014-10-01", "0889-111-111", "Stamboliiski 20"),
 (1, "2014-10-01", "0000-00-00", "0889-111-111", "Pirotska 16");

Виждаме, че на 2012-03-01 клиента си е сменил телефона, а на 2014-10-01 си е сменил адреса. Очевидни са обаче и повторенията на информация - два пъти пазим един и същи адрес ако клиента си смени само телефона, както и два пъти пазим един и същи телефон ако клиента си смени само адреса.

Шеста нормална форма настъпва тогава, когато в релацията съществуват само тривиални зависимости на съединенията (join dependencies).

Декомпозицията на горната таблица до 6НФ ще се получи по следния начин:

CREATE TABLE customers_phones(
 `id` INT UNSIGNED NOT NULL,
 `from` DATE NOT NULL,
 `to` DATE NOT NULL,
 `phone` VARCHAR(16) NOT NULL,
 PRIMARY KEY(`id`, `from` , `to`)
 );

CREATE TABLE customers_addresses(
 `id` INT UNSIGNED NOT NULL,
 `from` DATE NOT NULL,
 `to` DATE NOT NULL,
 `address` VARCHAR(255) NOT NULL,
 PRIMARY KEY(`id`, `from` , `to`)
 );

INSERT INTO customers_phones(id, from, to, phone)
 VALUES(1, "2012-01-01", "2012-03-01", "0888-555-555"),
 (1, "2012-03-01", "0000-00-00", "0889-111-111");

INSERT INTO customers_addresses(id, from, to, address)
 VALUES(1, "2012-01-01", "2014-10-01", "Stamboliiski 20"),
 (1, "2014-10-01", "0000-00-00", "Pirotska 16");

Забележете следното - при спазена 6НФ за дадена таблица ние имаме първичен ключ и не повече от един друг атрибут.

Друго приложение на 6НФ може да се намери при премахването на незадължителни полета (такива със стойности NULL). Например нека пазим имената и адресите на служителите в дадена фирма, а само за част от служителите пазим и основния език за програмиране, на който могат да програмират:

CREATE TABLE employees(
 `id` INT UNSIGNED PRIMARY KEY,
 `name` VARCHAR(255) NOT NULL,
 `address` VARCHAR(255) NOT NULL,
 `programming_lang` ENUM("C++", "JAVA", "C#") NULL DEFAULT NULL
 );

Ясно е, че ако фирмата има стотици служители, но малцина от тях са програмисти, то в колоната programming_lang ще имаме огромно количество NULL стойности. Затова в такива случаи прибягваме до декомпозиция чрез добавяне на подклас от обекти:

CREATE TABLE employees(
 `id` INT UNSIGNED PRIMARY KEY,
 `name` VARCHAR(255) NOT NULL,
 `address` VARCHAR(255) NOT NULL
 );

CREATE TABLE employees_programmers(
 `id` INT UNSIGNED PRIMARY KEY REFERENCES employees(id),
 `programming_lang` ENUM("C++", "JAVA", "C#") NOT NULL
 );

В тази декомпозиция таблицата employees е в 5НФ (има две колони, които не участват в първичния ключ), а таблица employees_programmers е в 6НФ. Ако искаме да приведем и employees в 6НФ, трябва да направим следното:

CREATE TABLE employees_names(
 `id` INT UNSIGNED PRIMARY KEY,
 `name` VARCHAR(255) NOT NULL
 );

CREATE TABLE employees_addresses(
 `id` INT UNSIGNED PRIMARY KEY,
 `address` VARCHAR(255) NOT NULL
 );

Заслужава ли си да декомпозираме таблица employees до 6НФ? Ако в нашата база от данни ще пазим само един единствен адрес на служител (както е в примера), категорично НЯМА смисъл от такава декомпозиция - ние не само, че няма да спестим, а даже ще разхищаваме повече информация!

Изобщо декомпозирането до 6НФ извън описаните по-горе случаи е рядкост и е добре да се избягва на практика. 6НФ по-често води до повишаване на броя на таблиците без практическа полза за намаляване на обема от информация (дори както видяхме по-горе може и да се повиши). А както знаем колкото повече таблици има, толкова повече ще трябва да правим заявки с JOIN, т.е. ние ще хабим изчислителни ресурси на процесора на системата. Това определено е нежелано.

И накрая като обобщение на всички статии, които написах и са свързани с нормализиране на бази от данни, трябва да кажа нещо, което може да се каже, че беше прието като подразбиращо се от само себе си и пренебрегнато в статиите ми свързани с нормализация на база от данни, но всъщност е от огромно значение за извършването на добра нормализация - винаги трябва да се отчитат взаимовръзките на информацията, когато се нормализира една база от данни! Не можете да извършите коректна нормализация, без да отчетете това какво точно искаме да съхраняваме не просто като информация, а като логически свързана информация. Ако вземем втория пример в тази статия, таблица employees ще изглежда по-различно ако правилата позволяват да пазим повече от един език за програмиране за програмистите или пък позволим да пазим повече от един адрес на служител.

 



Добави коментар

Адресът на електронната поща няма да се публикува


*