Первая нормальная форма предполагает, что таблица не должна содержать повторяющихся столбцов или таких столбцов, которые содержат наборы значений. Ненормализованная таблица в этом случае может содержать одну или несколько повторяющихся групп данных. Повторяющаяся группа - это группа из одного или нескольких атрибутов таблицы, в которой возможно наличие нескольких значений для ключевого атрибута таблицы.
Итогом применения первой формы должно стать наличие для одного атрибута сущности только одного столбца в таблице, который при этом должен содержать скалярное значение.
Есть два похода к переходу к ненормализованной таблицы к первой нормальной форме. Первый способ называется выравниванием или flattaning. Он предполагает декомпозицию строки с повторяющимися группами данных, при котором для каждой повторяющейся группы создается своя строка. Полученная в результате таблица будет содержать атомарные значения для каждого из атрибутов. Хотя в то же время этот подход увеличит избыточность данных.
Второй подход предполагает, что один атрибут или группа атрибутов назначаются ключом ненормализованной таблицы, а затем повторяющиеся группы удаляются из таблицы и помещаются в отдельную таблицу вместе с копиями ключа из исходной таблицы.
Рассмотрим применение нормализации на примере. Пусть у нас есть система, которая описывается следующей информацией:
Том посещает курс по математике, который преподает Смит. Дата записи 11/06/2017. Сэм посещает курс по алгоритмам, которые преподает Адамс. Дата записи 12/06/2017. Боб посещает курс по математике, который преподает Смит. Дата записи 13/06/2017. Том посещает курс по языку JavaScript, который преподает Адамс. Дата записи 14/06/2017. Сэм имеет два электронных адреса: sam@gmail.com и sam@hotmail.com. В университете может быть только один курс с определенным именем. Один преподаватель может преподавать несколько курсов.
Вначале определим ненормализованную таблицу StudentCourses, которая содержит всю эту информацию:
Для каждого студента определен уникальный идентификатор StudentId, а также атрибут Name (имя), Emails (все электронные адреса), Course1 /Course2(курс), Date1/Date2 (дата поступления), Teacher1/Teacher2 (преподаватель). Также чтобы различать преподавателей (так как теоретически могут быть преподаватели с одной и той же фамилией), добавлен атрибут TeacherId1/TeacherId2. Для курсов такой идентификатор не требуется, так как в нашем случае название курса уникально.
Поскольку Том записан сразу на два курса, то несколько атрибутов пришлось дублировать. Но что будет, когда Том в стремлении получить никому не нужные сертификаты запишется еще на десяток курсов?
Эта таблица представляет прекрасный пример отклонения от первой нормальной формы. В первую очередь мы видим группу повторяющихся атрибутов, которые представляют данные по одному курсу: Course, Date, TeacherId, Teacher. Эти атрибуты представляют повторяющуюся группу, которую можно условно назвать StudentCourse.
StudentCourse = (Course, Date, TeacherId, Teacher)
Вторая проблема - атрибут Emails содержит набор электронных адресов. Фактически этот атрибут также образует повторяющуюся группу.
Для избавления от первой повторяющейся группы атрибутов применим первый подход: создадим для каждой повторяющейся группы отдельную строку.
StudentId | Name | Emails | CourseId | Course | Date | TeacherId | Teacher |
1 | Том | 1 | Математика | 11/06/2017 | 1 | Смит | |
1 | Том | 2 | JavaScript | 14/06/2017 | 2 | Адамс | |
2 | Сэм | sam@gmail.comsam@hotmail.com | 3 | Алгоритмы | 12/06/2017 | 2 | Адамс |
3 | Боб | 1 | Математика | 13/06/2017 | 1 | Смит |
В данном случае увеличилась избыточность данных, но тем не менее мы избавились от повторяющейся группы. Также следует отметить, что теперь атрибут StudentId не может использоваться в качестве первичного ключа. И в данном случае просматривается только один потенциальный ключ, который и будет использоваться в качестве первичного - это сразу два столбца StudentId и Course. Но название курса - не лучший ключ, если учитывать, что это название может редактироваться и изменяться. Поэтому для каждого курса добавлен еще один атрибут - CourseId - уникальной номер курса, который вместе с StudentId составляет первичный ключ. Хотя в принципе может было бы и оставить в качестве части первичного ключа имя курса с учетом, что оно уникально.
Для избавления от второй повторяющейся группы - атрибута Emails применим второй подход: вынесение этой группы с копией ключа в отдельную таблицу. Для этого определим таблицу Emails:
StudentId | |
sam@gmail.com | 2 |
sam@hotmail.com | 2 |
Так как электронный адрес в принципе уникален, то его можно сделать первичным ключом.
Таким образом, таблицы Emails с таблицей StudentCourses будет связана связью один ко многим (один студент - много электронных адресов). И в этом случае таблица StudentCourses сократится следующим образом:
StudentId | Name | CourseId | Course | Date | TeacherId | Teacher |
1 | Том | 1 | Математика | 11/06/2017 | 1 | Смит |
1 | Том | 2 | JavaScript | 14/06/2017 | 2 | Адамс |
2 | Сэм | 3 | Алгоритмы | 12/06/2017 | 2 | Адамс |
3 | Боб | 1 | Математика | 13/06/2017 | 1 | Смит |
Теперь у нас нет повторяющихся столбцов, но увеличилась избыточность данных, так как для студента Том определено уже две строки в таблице, и соответственно Id повторяется. Но тем не менее 1-я нормальная форма применена.
В принципе можно отметить, что если повторяющиеся группы содержат уникальные значения для каждой строки таблицы (как в случае с электронными адресами), то мы имеем дело с потенциальной связью один ко многим. Если же повторяющиеся группы содержат неуникальные значения, которые могут иметь разные строки таблицы (как в случае с атрибутами курсов), то это скрывается потенциальная связь многие ко многим.