Fork me on GitHub

Данная заметка, делается в первую очередь для себя после прочтения монументального труда “Введение в системы баз данных” автора К. Дж. Дейт.

Труд этот очень обширный и затрагивает множество теоретических и практических аспектов связанных с базами данных, теорией их устройства (большей частью затронуты реляционные базы данных).

Чтобы как-то оставить информацию в памяти я решил сделать заметку с некоторыми цитатами касательно понятия нормализации, денормализации и понятий нормальных форм.

Процедура нормализации это процедура разбиения логически несвязанной информации на отдельные переменные отношения (таблицы).

Цель нормализации - избавиться от избыточности, и избежать аномалий обновления к которым приводит избыточность.

Проще говоря, преследуется цель разделить данные так, чтоб они были максимально независимыми друг от друга, тогда не возникает проблем с обновлением/удалением/добавлением новых данных.

В качестве небольшого отступления для примера создадим базу в PostgreSQL с которой будем проводить эксперимент дальнейших преобразований:

postgres=# CREATE DATABASE haircolors;
CREATE DATABASE
postgres=# 
postgres=# \c haircolors 
You are now connected to database "haircolors" as user "postgres".
haircolors=# \dt
No relations found.

Представим, что мы оптовый продавец всякой всячины для салонов красоты и есть некая база данных покупателей которые делают у нас заказы для каких то салонов красоты, которая может выглядеть допустим примерно так:

CREATE TABLE CUSTOMERS(
   ID INT PRIMARY KEY     NOT NULL,
   FIRSTNAME      VARCHAR(128) NOT NULL,
   LASTNAME       VARCHAR(128) NOT NULL,
   PHONENUMBER    VARCHAR(20) NOT NULL,
   SALON_NAME     VARCHAR(256) NOT NULL,
   ADDRESS        VARCHAR(256) NOT NULL,
   MANUFACTURER   VARCHAR(256) NOT NULL,
   PRODUCT_NAME   VARCHAR(256) NOT NULL,
   DATEORDER      TIMESTAMP NOT NULL, 
   PRICE         MONEY NOT NULL,
   QTY INT NOT NULL 
);

Покупатель с FIRSTNAME, LASTNAME, PHONENUMBER который живет по ADDRESS и производит закупки продукции PRODUCT_NAME неких производителей MANUFACTURER по цене PRICE в количестве QTY для салонов SALON_NAME, мы отмечаем дату и время когда произошел заказ DATEORDER.

Данный пример больше учебный, взял очень произвольно, попробуем найти проблемы в процессе нормализации.

haircolors=# \d customers
                Table "public.customers"
    Column    |            Type             | Modifiers 
--------------+-----------------------------+-----------
 id           | integer                     | not null
 firstname    | character varying(128)      | not null
 lastname     | character varying(128)      | not null
 phonenumber  | character varying(20)       | not null
 salon_name   | character varying(256)      | not null
 address      | character varying(256)      | not null
 manufacturer | character varying(256)      | not null
 product_name | character varying(256)      | not null
 dateorder    | timestamp without time zone | not null
 price        | money                       | not null
 qty          | integer                     | not null
Indexes:
    "customers_pkey" PRIMARY KEY, btree (id)

Заполним нашу условную базу данных некоторыми значениями:

INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY) 
VALUES (1, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Matrix', 'Краска для волос', '2017-07-03 12:15:01 +0000', 899.01, 3);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY) 
VALUES (2, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Loreal', 'Краска для волос', '2017-07-03 12:16:01 +0000', 599.12, 2);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY) 
VALUES (3, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Blond', 'Краска для волос', '2017-07-02 11:01:01 +0000', 299.12, 12);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY) 
VALUES (4, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Союз', 'Полотенца одноразовые', '2017-07-02 11:01:01 +0000', 199.12, 120);

Из произвольно набранных данных видим, что таблица не удовлетворяет 1НФ:

Первая нормальная форма

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

haircolors=# select * from customers ;
 id | firstname | lastname | phonenumber  |        salon_name        |                          address                          | manufacturer |     product_name      |      dateorder      |   price    | qty 
----+-----------+----------+--------------+--------------------------+-----------------------------------------------------------+--------------+-----------------------+---------------------+------------+-----
  1 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3
  2 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:15:01 | 599.12 руб |   2
  3 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12
  4 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120
(4 rows)

Здесь явно что то не так как минимум со столбцом salon_name в котором имеются данные по смыслу означающие разные салоны (разные сущности) перечисленные через запятую. Например если я захочу добавить заказчику “Денис Петров” еще 1 название салона, мне придется обновить все записи, причем не забыть ни одного вхождения. В примере малое количество записей (строк) и поэтому кажется ничего страшного, но представим что записей сотни тысяч и проблема станет ощутимее. Кроме того очень неудобно работать с данными которые представлены вот так через запятую. Для получения какой либо полезной информации придется как то парсить значение этого столбца.

Можно также увидеть что покупатель Юлия Бабкина поставляет товары в тот же салон ViVa, что и Денис Петров, следовательно сделаем заключение, что “несколько разных покупателей могут поставлять продукцию в один и тот же салон.

Приведем данные к первой нормальной форме, для чего создадим таблицу SALON c полями (столбцами) SALON_ID# и SALON_NAME, (на самом-то деле сейчас нет особой надобности в поле SALON_ID#, так как само имя салона может являться первичным ключом, но представим, вдруг у нас могут быть 2 салона с одинаковым именем, и нам все же придется их как то различать) - вынесем возможные значения salon_name и привяжем с помощью внешнего ключа к нашей таблице customers.

CREATE TABLE SALON (
   SALON_ID SERIAL PRIMARY KEY,
   SALON_NAME VARCHAR(128) NOT NULL
);
haircolors=# \d salon
                                      Table "public.salon"
   Column   |          Type          |                        Modifiers                         
------------+------------------------+----------------------------------------------------------
 salon_id   | integer                | not null default nextval('salon_salon_id_seq'::regclass)
 salon_name | character varying(128) | not null
Indexes:
    "salon_pkey" PRIMARY KEY, btree (salon_id)

Добавим значения - названия салонов в таблицу SALON:

INSERT INTO salon (salon_name) VALUES ('Е-Студия');
INSERT INTO salon (salon_name) VALUES ('Ле-туаль');
INSERT INTO salon (salon_name) VALUES ('UpDo');
INSERT INTO salon (salon_name) VALUES ('ViVa');
INSERT INTO salon (salon_name) VALUES ('Diana');

получается следующее, обратим внимание что salon_id заполнен автоматически, мы не определяли его значение:

haircolors=# select * from salon;
 salon_id | salon_name 
----------+------------
        1 | Е-Студия
        2 | Ле-туаль
        3 | UpDo
        4 | ViVa
        5 | Diana
(5 rows)

Переименуем столбец таблицы CUSTOMERS salon_name в salon_name_old

ALTER TABLE CUSTOMERS RENAME COLUMN SALON_NAME TO SALON_NAME_OLD;

Добавим столбец salon_name как внешний ключ который ссылается на первичный ключ таблицы SALON salon_id. Cначала создадим столбец, потом “навесим” на него ограничение:

ALTER TABLE customers ADD COLUMN SALON_NAME INT;

ALTER TABLE customers 
   ADD CONSTRAINT fk_salon_name
   FOREIGN KEY (salon_name) 
   REFERENCES salon(salon_id);

Ради примера удалим допустим ограничение NOT NULL столбца salon_name_old:

ALTER TABLE customers ALTER COLUMN salon_name_old DROP NOT NULL;

Можем наблюдать нами созданный столбец и ограничение:

haircolors=# \d customers
                 Table "public.customers"
     Column     |            Type             | Modifiers 
----------------+-----------------------------+-----------
 id             | integer                     | not null
 firstname      | character varying(128)      | not null
 lastname       | character varying(128)      | not null
 phonenumber    | character varying(20)       | not null
 salon_name_old | character varying(256)      | 
 address        | character varying(256)      | not null
 manufacturer   | character varying(256)      | not null
 product_name   | character varying(256)      | not null
 dateorder      | timestamp without time zone | not null
 price          | money                       | not null
 qty            | integer                     | not null
 salon_name     | integer                     | 
Indexes:
    "customers_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "fk_salon_name" FOREIGN KEY (salon_name) REFERENCES salon(salon_id)

Теперь пришла очередь заполнить колонку salon_name таблицы CUSTOMERS:

INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (5, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Matrix', 'Краска для волос', '2017-07-03 12:15:01 +0000', 899.01, 3, 1);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (6, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Matrix', 'Краска для волос', '2017-07-03 12:15:01 +0000', 899.01, 3, 2);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (7, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Matrix', 'Краска для волос', '2017-07-03 12:15:01 +0000', 899.01, 3, 4);

INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (8, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Loreal', 'Краска для волос', '2017-07-03 12:16:01 +0000', 599.12, 2, 1);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (9, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Loreal', 'Краска для волос', '2017-07-03 12:16:01 +0000', 599.12, 2, 2);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (10, 'Денис', 'Петров', '+79784567897', 'Е-Студия, ViVa, Ле-туаль', 'ул. Александра Косарева, д.77, кв. 54, Севастополь 299006', 'Loreal', 'Краска для волос', '2017-07-03 12:16:01 +0000', 599.12, 2, 4);


INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (11, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Blond', 'Краска для волос', '2017-07-02 11:01:01 +0000', 299.12, 12, 3);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (12, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Blond', 'Краска для волос', '2017-07-02 11:01:01 +0000', 299.12, 12, 4);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (13, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Blond', 'Краска для волос', '2017-07-02 11:01:01 +0000', 299.12, 12, 5);


INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (14, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Союз', 'Полотенца одноразовые', '2017-07-02 11:01:01 +0000', 199.12, 120, 3);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (15, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Союз', 'Полотенца одноразовые', '2017-07-02 11:01:01 +0000', 199.12, 120, 4);
INSERT INTO CUSTOMERS (ID,FIRSTNAME,LASTNAME,PHONENUMBER, SALON_NAME_OLD,ADDRESS,MANUFACTURER,PRODUCT_NAME,DATEORDER, PRICE, QTY, SALON_NAME) 
VALUES (16, 'Юлия', 'Бабкина', '+79784168585', 'UpDo, ViVa, Diana', 'ул. Кесаева, д.1, кв. 55, Севастополь 299003', 'Союз', 'Полотенца одноразовые', '2017-07-02 11:01:01 +0000', 199.12, 120, 5);

База значительно выросла, но не страшно, зато мы убрали перечисление в столбце:

haircolors=# select * from customers;
 id | firstname | lastname | phonenumber  |      salon_name_old      |                          address                          | manufacturer |     product_name      |      dateorder      |   price    | qty | salon_name 
----+-----------+----------+--------------+--------------------------+-----------------------------------------------------------+--------------+-----------------------+---------------------+------------+-----+------------
  1 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |           
  2 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:15:01 | 599.12 руб |   2 |           
  3 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |           
  4 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |           
  5 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          1
  6 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          2
  7 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          4
  8 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          1
  9 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          2
 10 | Денис     | Петров   | +79784567897 | Е-Студия, ViVa, Ле-туаль | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          4
 11 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          3
 12 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          4
 13 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          5
 14 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          3
 15 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          4
 16 | Юлия      | Бабкина  | +79784168585 | UpDo, ViVa, Diana        | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          5
(16 rows)

Теперь можно удалить столбец salon_name_old и строки с id 1-4, информация о названии салона у нас представлена в столбце salon_name

ALTER TABLE customers DROP COLUMN salon_name_old ;

DELETE FROM customers WHERE id IN (1,2,3,4);
haircolors=# select * from customers;
 id | firstname | lastname | phonenumber  |                          address                          | manufacturer |     product_name      |      dateorder      |   price    | qty | salon_name 
----+-----------+----------+--------------+-----------------------------------------------------------+--------------+-----------------------+---------------------+------------+-----+------------
  5 | Денис     | Петров   | +79784567897 | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          1
  6 | Денис     | Петров   | +79784567897 | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          2
  7 | Денис     | Петров   | +79784567897 | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          4
  8 | Денис     | Петров   | +79784567897 | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          1
  9 | Денис     | Петров   | +79784567897 | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          2
 10 | Денис     | Петров   | +79784567897 | ул. Александра Косарева, д.77, кв. 54, Севастополь 299006 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          4
 11 | Юлия      | Бабкина  | +79784168585 | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          3
 12 | Юлия      | Бабкина  | +79784168585 | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          4
 13 | Юлия      | Бабкина  | +79784168585 | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          5
 14 | Юлия      | Бабкина  | +79784168585 | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          3
 15 | Юлия      | Бабкина  | +79784168585 | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          4
 16 | Юлия      | Бабкина  | +79784168585 | ул. Кесаева, д.1, кв. 55, Севастополь 299003              | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          5
(12 rows)

Вот что получилось, убрали перечисление для названия салонов, но не будем закрывать глаза на перечисление в столбце address , создадим отдельную табличку ADDRESS и заполним данными:

CREATE TABLE ADDRESS (
   ID INT PRIMARY KEY,
   CITY VARCHAR(128) NOT NULL,
   BUILDING INT NOT NULL,
   FLAT_NO INT NOT NULL,
   STREET VARCHAR(128) NOT NULL,
   ZIP_CODE INT NOT NULL
);

INSERT INTO address (ID, CITY, STREET, BUILDING, FLAT_NO, ZIP_CODE) VALUES (
1, 'Севастополь', 'ул. Александра Косарева', 77, 54, 299006);
INSERT INTO address (ID, CITY, STREET, BUILDING, FLAT_NO, ZIP_CODE) VALUES (
2, 'Севастополь', 'ул. Кесаева', 1, 55, 299003);
haircolors=# select * from address;
 id |    city     | building | flat_no |         street          | zip_code 
----+-------------+----------+---------+-------------------------+----------
  1 | Севастополь |       77 |      54 | ул. Александра Косарева |   299006
  2 | Севастополь |        1 |      55 | ул. Кесаева             |   299003
(2 rows)

Теперь переименуем столбец address в таблице CUSTOMERS в address_old, создадим столбец address и заполним соответствующими значениями (пока не будем использовать никакие ограничения)

ALTER TABLE CUSTOMERS RENAME COLUMN address TO address_old;
ALTER TABLE CUSTOMERS ADD COLUMN address INT;

UPDATE customers SET address=1 WHERE id in (5,6,7,8,9,10);

UPDATE customers SET address=2 WHERE id not in (5,6,7,8,9,10);

Проверим, что не потеряли информацию о адресе и удалим столбец address_old

ALTER TABLE customers DROP COLUMN address_old ;

В результате получится так:

haircolors=# ALTER TABLE customers DROP COLUMN address_old ;
ALTER TABLE
haircolors=# select * from customers ;
 id | firstname | lastname | phonenumber  | manufacturer |     product_name      |      dateorder      |   price    | qty | salon_name | address 
----+-----------+----------+--------------+--------------+-----------------------+---------------------+------------+-----+------------+---------
  5 | Денис     | Петров   | +79784567897 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          1 |       1
  6 | Денис     | Петров   | +79784567897 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          2 |       1
  7 | Денис     | Петров   | +79784567897 | Matrix       | Краска для волос      | 2017-07-03 12:15:01 | 899.01 руб |   3 |          4 |       1
  8 | Денис     | Петров   | +79784567897 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          1 |       1
  9 | Денис     | Петров   | +79784567897 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          2 |       1
 10 | Денис     | Петров   | +79784567897 | Loreal       | Краска для волос      | 2017-07-03 12:16:01 | 599.12 руб |   2 |          4 |       1
 11 | Юлия      | Бабкина  | +79784168585 | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          3 |       2
 12 | Юлия      | Бабкина  | +79784168585 | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          4 |       2
 13 | Юлия      | Бабкина  | +79784168585 | Blond        | Краска для волос      | 2017-07-02 11:01:01 | 299.12 руб |  12 |          5 |       2
 14 | Юлия      | Бабкина  | +79784168585 | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          3 |       2
 15 | Юлия      | Бабкина  | +79784168585 | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          4 |       2
 16 | Юлия      | Бабкина  | +79784168585 | Союз         | Полотенца одноразовые | 2017-07-02 11:01:01 | 199.12 руб | 120 |          5 |       2
(12 rows)

haircolors=# select * from salon;
 salon_id | salon_name 
----------+------------
        1 | Е-Студия
        2 | Ле-туаль
        3 | UpDo
        4 | ViVa
        5 | Diana
(5 rows)

haircolors=# select * from address ;
 id |    city     | building | flat_no |         street          | zip_code 
----+-------------+----------+---------+-------------------------+----------
  1 | Севастополь |       77 |      54 | ул. Александра Косарева |   299006
  2 | Севастополь |        1 |      55 | ул. Кесаева             |   299003
(2 rows)

Итак, мы в конце концов привели нашу первоначальную таблицу к первой нормальной форме 1НФ. Как видим никакие данные в столбцах более не перечисляются.

В итоге мы получили 3 разные таблицы которые уже имеют связи, которые несложно проследить такие как:

ОДИН КО МНОГИМ salon.salon_id (1) -> customers.salon_name (*), такая связь как видим организована с помощью customers.salon_name (FOREIGN KEY)(*) -> salon.salon_id (PRIMARY KEY)(1)

ОДИН К ОДНОМУ Мы видим что пара значений firstname, lastname образуют первичный ключ, который может идентифицировать уникального пользователя, который живет по некоторому адресу следовательно прослеживается связь (firstname, lastname) (1) <-> address.id (1). В дальнейшем я вижу возможность использовать это, поэтому решил для упрощения примера не навешивать никаких ограничений, просто опишу эту связь словами.

Думаю можно переходить к приведению наших данных ко 2й нормальной форме, о чем будет мой завтрашний рассказ.


Comments

comments powered by Disqus