БД:Теория:Глава 3: различия между версиями
Ivabus (обсуждение | вклад) Добавить категории Метка: визуальный редактор отключён |
Zhabka (обсуждение | вклад) Пофиксил проблемы с отображением формул, добавил TeX там, где он нужен. |
||
(не показано 7 промежуточных версий 2 участников) | |||
Строка 17: | Строка 17: | ||
Это набор операций над отношениями, результатом которых всегда является новое отношение. Нам понадобятся основные операции для понимания того, как СУБД может выполнять запросы и как устроена нормализация. | Это набор операций над отношениями, результатом которых всегда является новое отношение. Нам понадобятся основные операции для понимания того, как СУБД может выполнять запросы и как устроена нормализация. | ||
# '''Выборка (Selection, σ):''' Выбирает кортежи (строки) из отношения, удовлетворяющие заданному условию (предикату < | # '''Выборка (Selection, σ):''' Выбирает кортежи (строки) из отношения, удовлетворяющие заданному условию (предикату <math display="inline">\varphi</math>). | ||
#* '''Обозначение:''' < | #* '''Обозначение:''' <math display="inline">\sigma_{\varphi}(\text{R})</math> - выбрать строки из отношения R, где условие <math display="inline">\varphi</math> истинно. | ||
#* '''SQL аналог:''' Условие в секции <code>WHERE</code>. | #* '''SQL аналог:''' Условие в секции <code>WHERE</code>. | ||
#* '''Пример:''' Выбрать студентов группы ‘3100’, чей ID | #* '''Пример:''' Выбрать студентов группы ‘3100’, чей ID >= 150000. | ||
#** SQL: <code>SELECT * FROM STUDENTS WHERE STUDENTS.GROUP = '3100' AND STUDENTS.ID | #** SQL: <code>SELECT * FROM STUDENTS WHERE STUDENTS.GROUP = '3100' AND STUDENTS.ID >= 150000;</code> | ||
#** Рел. Алгебра: < | #** Рел. Алгебра: <math display="inline">\sigma_{(\text{STUDENTS.GROUP='3100'}) \land (\text{STUDENTS.ID} \ge 150000)}(\text{STUDENTS})</math> ''(Здесь <math display="inline">\land</math> означает логическое “И”)'' | ||
# '''Проекция (Projection, π):''' Выбирает указанные атрибуты (столбцы) из отношения, удаляя дубликаты строк в результирующем отношении. | # '''Проекция (Projection, π):''' Выбирает указанные атрибуты (столбцы) из отношения, удаляя дубликаты строк в результирующем отношении. | ||
#* '''Обозначение:''' < | #* '''Обозначение:''' <math display="inline">\pi_{\text{атрибуты}}(\text{R})</math> - выбрать столбцы <math display="inline">\text{атрибуты}</math> из отношения R. | ||
#* '''SQL аналог:''' Список столбцов в секции <code>SELECT</code>. Удаление дубликатов соответствует <code>SELECT DISTINCT</code>. | #* '''SQL аналог:''' Список столбцов в секции <code>SELECT</code>. Удаление дубликатов соответствует <code>SELECT DISTINCT</code>. | ||
#* '''Пример:''' Получить имена и группы всех студентов. | #* '''Пример:''' Получить имена и группы всех студентов. | ||
#** SQL: <code>SELECT DISTINCT name, group FROM STUDENTS;</code> | #** SQL: <code>SELECT DISTINCT name, group FROM STUDENTS;</code> | ||
#** Рел. Алгебра: < | #** Рел. Алгебра: <math display="inline">\pi_{\text{name, group}}(\text{STUDENTS})</math> | ||
# '''Соединение (Join, ⋈):''' Комбинирует кортежи из двух отношений на основе некоторого условия (< | # '''Соединение (Join, ⋈):''' Комбинирует кортежи из двух отношений на основе некоторого условия (<math display="inline">\theta</math>, тета). | ||
#* '''Тета-соединение (Theta Join):''' < | #* '''Тета-соединение (Theta Join):''' <math display="inline">\text{R} \bowtie_{\theta} \text{S}</math>. Общий случай, где <math display="inline">\theta</math> - любое условие сравнения между атрибутами R и S. Логически эквивалентно: <math display="inline">\sigma_{\theta}(\text{R} \times \text{S})</math> (выборка из декартова произведения). | ||
#* '''Эквисоединение (Equijoin):''' Частный случай тета-соединения, где условие < | #* '''Эквисоединение (Equijoin):''' Частный случай тета-соединения, где условие <math display="inline">\theta</math> содержит только операции равенства (<math display="inline">=</math>). | ||
#* '''Естественное соединение (Natural Join):''' Эквисоединение по ''всем'' атрибутам с одинаковыми именами, причем совпадающие столбцы включаются в результат только один раз. | #* '''Естественное соединение (Natural Join):''' Эквисоединение по ''всем'' атрибутам с одинаковыми именами, причем совпадающие столбцы включаются в результат только один раз. | ||
#* '''SQL аналог:''' Операторы <code>JOIN</code>. <code>INNER JOIN ON условие</code> соответствует тета-соединению или эквисоединению. <code>NATURAL JOIN</code> соответствует естественному соединению. | #* '''SQL аналог:''' Операторы <code>JOIN</code>. <code>INNER JOIN ON условие</code> соответствует тета-соединению или эквисоединению. <code>NATURAL JOIN</code> соответствует естественному соединению. | ||
#* '''Пример:''' Соединить студентов и их экзамены по ID студента. | #* '''Пример:''' Соединить студентов и их экзамены по ID студента. | ||
#** SQL: <code>SELECT * FROM STUDENTS JOIN EXAMS ON STUDENTS.ID = EXAMS.STUD_ID;</code> | #** SQL: <code>SELECT * FROM STUDENTS JOIN EXAMS ON STUDENTS.ID = EXAMS.STUD_ID;</code> | ||
#** Рел. Алгебра: < | #** Рел. Алгебра: <math display="inline">\text{STUDENTS} \bowtie_{\theta} \text{EXAMS}</math>, где условие <math display="inline">\theta</math> это <math display="inline">\text{STUDENTS.ID} = \text{EXAMS.STUD}\_\text{ID}</math> | ||
'''Законы Реляционной Алгебры (Примеры Эквивалентных Преобразований):''' | '''Законы Реляционной Алгебры (Примеры Эквивалентных Преобразований):''' | ||
Строка 42: | Строка 42: | ||
СУБД использует правила эквивалентности для преобразования запроса в ''различные планы выполнения'', чтобы выбрать наиболее эффективный. | СУБД использует правила эквивалентности для преобразования запроса в ''различные планы выполнения'', чтобы выбрать наиболее эффективный. | ||
* Соединение коммутативно (для <code>INNER JOIN</code>): < | * Соединение коммутативно (для <code>INNER JOIN</code>): <math display="inline">\text{R} \bowtie_{\theta} \text{S} \equiv \text{S} \bowtie_{\theta} \text{R}</math> | ||
* Соединение ассоциативно (для <code>INNER JOIN</code>): < | * Соединение ассоциативно (для <code>INNER JOIN</code>): <math display="inline">\text{R} \bowtie_{\theta} (\text{S} \bowtie_{\varphi} \text{T}) \equiv (\text{R} \bowtie_{\theta} \text{S}) \bowtie_{\varphi} \text{T}</math> | ||
* Каскад выборок: < | * Каскад выборок: <math display="inline">\sigma_{\theta \land \varphi}(\text{R}) \equiv \sigma_{\theta}(\sigma_{\varphi}(\text{R}))</math> | ||
* “Проталкивание” выборки через соединение: Если условие < | * “Проталкивание” выборки через соединение: Если условие <math display="inline">\varphi</math> относится только к атрибутам <math display="inline">\text{R}</math>, то: <math display="inline">\sigma_{\varphi}(\text{R} \bowtie_{\theta} \text{S}) \equiv (\sigma_{\varphi}(\text{R})) \bowtie_{\theta} \text{S}</math>. ''Это важное правило оптимизации: выполнять выборку как можно раньше, чтобы уменьшить размер данных для соединения.'' | ||
* “Проталкивание” проекции через соединение (сложнее): < | * “Проталкивание” проекции через соединение (сложнее): <math display="inline">\pi_{\text{A}}(\text{R} \bowtie_{\theta} \text{S})</math> можно преобразовать, оставив в <math display="inline">\text{R}</math> и <math display="inline">\text{S}</math> только те атрибуты, которые нужны для результата (<math display="inline">\text{A}</math>) и для условия соединения (<math display="inline">\theta</math>). ''Тоже важное правило: отбрасывать ненужные столбцы как можно раньше.'' | ||
'''План Выполнения Запроса:''' | '''План Выполнения Запроса:''' | ||
Строка 52: | Строка 52: | ||
Это последовательность операций реляционной алгебры (и конкретных алгоритмов их выполнения), которую СУБД строит для ответа на SQL-запрос. Часто представляется в виде дерева операций. Для одного SQL-запроса может существовать несколько ''эквивалентных'' планов (дающих одинаковый результат), но с разной ''стоимостью'' выполнения. Оптимизатор запросов выбирает план с наименьшей предполагаемой стоимостью. | Это последовательность операций реляционной алгебры (и конкретных алгоритмов их выполнения), которую СУБД строит для ответа на SQL-запрос. Часто представляется в виде дерева операций. Для одного SQL-запроса может существовать несколько ''эквивалентных'' планов (дающих одинаковый результат), но с разной ''стоимостью'' выполнения. Оптимизатор запросов выбирает план с наименьшей предполагаемой стоимостью. | ||
{{#mermaid:graph TD | |||
JOIN1[ | JOIN1["⋈(ST.ID = EXAM.STUD_ID)"] --> SELECT1["σ(ST.ID >= 150000)"] | ||
JOIN1 -- | JOIN1 --> EXAMS[EXAMS] | ||
SELECT1 -- | SELECT1 --> SELECT2["σ(ST.GROUP = '3100')"] | ||
SELECT2 -- | SELECT2 --> STUDENTS[STUDENTS] | ||
RESULT(result) -- | RESULT(result) --> JOIN1 | ||
''Пример одного из возможных планов для запроса <code>SELECT * FROM STUDENTS ST JOIN EXAMS EXAM ON ST.ID = EXAM.STUD_ID WHERE ST.GROUP = '3100' AND ST.ID | }} | ||
''Пример одного из возможных планов для запроса <code>SELECT * FROM STUDENTS ST JOIN EXAMS EXAM ON ST.ID = EXAM.STUD_ID WHERE ST.GROUP = '3100' AND ST.ID >= 150000;</code>'' | |||
<span id="часть-2-проблемы-плохого-дизайна-и-аномалии"></span> | <span id="часть-2-проблемы-плохого-дизайна-и-аномалии"></span> | ||
Строка 67: | Строка 68: | ||
'''Таблица <code>STUDENTS</code> (Ненормализованная)''' | '''Таблица <code>STUDENTS</code> (Ненормализованная)''' | ||
{| | {| class="wikitable" | ||
|- | |- | ||
| | ! style="text-align: left;"| StudID | ||
| | ! style="text-align: left;"| StudName | ||
| | ! style="text-align: left;"| Group | ||
| | ! style="text-align: left;"| GrMentor | ||
|- | |- | ||
| | | style="text-align: left;"| 1 | ||
| | | style="text-align: left;"| Ivan Petrov | ||
| | | style="text-align: left;"| P3100 | ||
| | | style="text-align: left;"| Egor Kirov | ||
|- | |- | ||
| 34 | | style="text-align: left;"| 3 | ||
| Gleb Anisimov | | style="text-align: left;"| Vasily Ivanov | ||
| P3100 | | style="text-align: left;"| P3101 | ||
| Egor Kirov | | style="text-align: left;"| Roman Ivov | ||
|- | |||
| style="text-align: left;"| 34 | |||
| style="text-align: left;"| Gleb Anisimov | |||
| style="text-align: left;"| P3100 | |||
| style="text-align: left;"| Egor Kirov | |||
|} | |} | ||
Строка 93: | Строка 95: | ||
<ol style="list-style-type: decimal;"> | <ol style="list-style-type: decimal;"> | ||
<li><p>'''Избыточность Данных:''' Информация о кураторе (<code>GrMentor</code>) повторяется для каждого студента из одной и той же группы (Egor Kirov для группы P3100). Это ведет к неэффективному использованию памяти.</p></li> | <li><p>'''Избыточность Данных:''' Информация о кураторе (<code>GrMentor</code>) повторяется для каждого студента из одной и той же группы (Egor Kirov для группы P3100). Это ведет к неэффективному использованию памяти.</p></li> | ||
<li><p>'''Аномалии Обновления (Update Anomalies):''' Если куратор группы P3100 сменится, нам придется обновить поле <code>GrMentor</code> во ''всех'' строках, где <code>Group = 'P3100'</code>. Если мы обновим не все строки, данные станут противоречивыми (у одной группы окажется несколько кураторов). < | <li><p>'''Аномалии Обновления (Update Anomalies):''' Если куратор группы P3100 сменится, нам придется обновить поле <code>GrMentor</code> во ''всех'' строках, где <code>Group = 'P3100'</code>. Если мы обновим не все строки, данные станут противоречивыми (у одной группы окажется несколько кураторов).</p> | ||
<syntaxhighlight lang="sql">-- Попытка сменить куратора только для одного студента | |||
UPDATE STUDENTS | |||
SET GrMentor = 'Eugene Lomov' | |||
WHERE StudName = 'Ivan Petrov';</syntaxhighlight> | |||
<p>'''Результат (Несогласованность):''' | |||
{| class="wikitable" | |||
|- | |||
! style="text-align: left;"| StudID | |||
! style="text-align: left;"| StudName | |||
! style="text-align: left;"| Group | |||
! style="text-align: left;"| GrMentor | |||
|- | |||
| style="text-align: left;"| 1 | |||
| style="text-align: left;"| Ivan Petrov | |||
| style="text-align: left;"| P3100 | |||
| style="text-align: left;"| Eugene Lomov (Это новый куратор) | |||
|- | |||
| style="text-align: left;"| 3 | |||
| style="text-align: left;"| Vasily Ivanov | |||
| style="text-align: left;"| P3101 | |||
| style="text-align: left;"| Roman Ivov | |||
|- | |||
| style="text-align: left;"| 34 | |||
| style="text-align: left;"| Gleb Anisimov | |||
| style="text-align: left;"| P3100 | |||
| style="text-align: left;"| Egor Kirov (А тут остался старый) | |||
|} | |||
</p></li> | |||
<li><p>'''Аномалии Вставки (Insertion Anomalies):'''</p> | <li><p>'''Аномалии Вставки (Insertion Anomalies):'''</p> | ||
<ul> | <ul> | ||
Строка 102: | Строка 132: | ||
INSERT INTO STUDENTS VALUES(57, 'Nina Simonova', 'P3100', 'E. Kirov'); | INSERT INTO STUDENTS VALUES(57, 'Nina Simonova', 'P3100', 'E. Kirov'); | ||
INSERT INTO STUDENTS VALUES(58, 'Petr Uvarov', 'P3100', 'Egor Lomov');</syntaxhighlight></li> | INSERT INTO STUDENTS VALUES(58, 'Petr Uvarov', 'P3100', 'Egor Lomov');</syntaxhighlight></li> | ||
<li><p>'''Аномалии Удаления (Deletion Anomalies):''' Если мы удалим последнего студента из какой-либо группы (например, Василия Иванова из P3101), мы потеряем информацию о самой группе P3101 и ее кураторе Романе Ивове, даже если эта информация нам еще нужна. < | <li><p>'''Аномалии Удаления (Deletion Anomalies):''' Если мы удалим последнего студента из какой-либо группы (например, Василия Иванова из P3101), мы потеряем информацию о самой группе P3101 и ее кураторе Романе Ивове, даже если эта информация нам еще нужна.</p> | ||
<syntaxhighlight lang="sql">DELETE FROM STUDENTS | |||
WHERE StudName = 'Vasily Ivanov';</syntaxhighlight> | |||
<p>''После этого запроса информация о группе P3101 и ее кураторе исчезнет из таблицы.''</p></li></ol> | |||
Все эти проблемы возникают из-за того, что в одной таблице смешаны данные о разных сущностях (студенты и группы/кураторы) и существуют “неправильные” зависимости между атрибутами. Нормализация помогает выявить и устранить эти проблемы. | Все эти проблемы возникают из-за того, что в одной таблице смешаны данные о разных сущностях (студенты и группы/кураторы) и существуют “неправильные” зависимости между атрибутами. Нормализация помогает выявить и устранить эти проблемы. | ||
Строка 112: | Строка 145: | ||
* '''Функциональная зависимость (Functional Dependency, FD)''' описывает смысловую связь между атрибутами ''внутри одного отношения (таблицы)''. | * '''Функциональная зависимость (Functional Dependency, FD)''' описывает смысловую связь между атрибутами ''внутри одного отношения (таблицы)''. | ||
* Говорят, что атрибут (или набор атрибутов) < | * Говорят, что атрибут (или набор атрибутов) <math display="inline">\text{B}</math> '''функционально зависит''' от атрибута (или набора атрибутов) <math display="inline">\text{A}</math>, если для ''каждого'' возможного значения <math display="inline">\text{A}</math> существует ''ровно одно'' соответствующее значение <math display="inline">\text{B}</math>. | ||
* '''Обозначение:''' < | * '''Обозначение:''' <math display="inline">\text{A} \to \text{B}</math> (читается “A функционально определяет B” или “B функционально зависит от A”). | ||
* < | * <math display="inline">\text{A}</math> называется '''детерминантом'''. | ||
'''Важно:''' ФЗ определяются '''смыслом данных (семантикой)''' предметной области, а не текущими данными в таблице! | '''Важно:''' ФЗ определяются '''смыслом данных (семантикой)''' предметной области, а не текущими данными в таблице! | ||
Строка 120: | Строка 153: | ||
'''Примеры ФЗ для таблицы <code>STUDENTS</code> (StudID, StudName, Group, GrMentor):''' | '''Примеры ФЗ для таблицы <code>STUDENTS</code> (StudID, StudName, Group, GrMentor):''' | ||
* < | * <math display="inline">\text{StudID} \to \text{StudName}</math> (По ID студента однозначно определяется его имя). | ||
* < | * <math display="inline">\text{StudID} \to \text{Group}</math> (По ID студента однозначно определяется его группа). | ||
* < | * <math display="inline">\text{StudID} \to \text{GrMentor}</math> (По ID студента можно узнать его группу, а по группе - куратора, т.е. косвенно ID определяет куратора). | ||
* < | * <math display="inline">\text{Group} \to \text{GrMentor}</math> (По номеру группы однозначно определяется ее куратор. Предполагаем, что у группы только один куратор). | ||
* < | * <math display="inline">\text{StudID, Group} \to \text{GrMentor}</math> (Тоже верно, но избыточно, т.к. <math display="inline">\text{Group}</math> уже зависит от <math display="inline">\text{StudID}</math>). | ||
'''Примеры НЕ ФЗ:''' | '''Примеры НЕ ФЗ:''' | ||
* < | * <math display="inline">\text{Group} \to \text{StudID}</math> (Неверно, т.к. в одной группе много студентов). | ||
* < | * <math display="inline">\text{StudName} \to \text{StudID}</math> (Неверно, т.к. могут быть тезки). | ||
'''Типы ФЗ:''' | '''Типы ФЗ:''' | ||
* '''Тривиальная ФЗ:''' Зависимость вида < | * '''Тривиальная ФЗ:''' Зависимость вида <math display="inline">\text{A} \to \text{B}</math>, где <math display="inline">\text{B}</math> является подмножеством <math display="inline">\text{A}</math>. Например, <math display="inline">\{\text{StudID}, \text{StudName}\} \to \text{StudName}</math>. Такие зависимости выполняются всегда и не несут полезной информации для нормализации. Обычно рассматривают только '''нетривиальные''' ФЗ. | ||
* '''Полная ФЗ:''' Зависимость < | * '''Полная ФЗ:''' Зависимость <math display="inline">\text{A} \to \text{B}</math>, где <math display="inline">\text{A}</math> – составной детерминант (несколько атрибутов), и <math display="inline">\text{B}</math> не зависит ни от какого ''подмножества'' <math display="inline">\text{A}</math>. Мы уже обсуждали это в контексте 2НФ. | ||
* '''Частичная ФЗ:''' Зависимость < | * '''Частичная ФЗ:''' Зависимость <math display="inline">\text{A} \to \text{B}</math>, где <math display="inline">\text{A}</math> – составной детерминант, и <math display="inline">\text{B}</math> зависит от ''части'' <math display="inline">\text{A}</math>. (Пример: <math display="inline">\{\text{StudID}, \text{ExamID}\} \to \text{StudName}</math>, но при этом <math display="inline">\text{StudID} \to \text{StudName}</math>. Здесь <math display="inline">\text{StudName}</math> частично зависит от ключа). | ||
* '''Транзитивная ФЗ:''' Зависимость < | * '''Транзитивная ФЗ:''' Зависимость <math display="inline">\text{A} \to \text{C}</math>, которая существует только через промежуточный атрибут <math display="inline">\text{B}</math>, такой что <math display="inline">\text{A} \to \text{B}</math> и <math display="inline">\text{B} \to \text{C}</math>, при этом <math display="inline">\text{B}</math> не зависит от <math display="inline">\text{A}</math> (<math display="inline">\text{B} \to \text{A}</math> неверно) и <math display="inline">\text{B}</math> не является частью ключа <math display="inline">\text{A}</math>. (Пример: <math display="inline">\text{StudID} \to \text{Group}</math> и <math display="inline">\text{Group} \to \text{GrMentor}</math>, следовательно, <math display="inline">\text{StudID} \to \text{GrMentor}</math> транзитивно через <math display="inline">\text{Group}</math>). | ||
'''Аксиомы Армстронга:''' Формальные правила для вывода новых ФЗ из существующих: | '''Аксиомы Армстронга:''' Формальные правила для вывода новых ФЗ из существующих: | ||
# '''Рефлексивность:''' Если < | # '''Рефлексивность:''' Если <math display="inline">\text{B} \subseteq \text{A}</math>, то <math display="inline">\text{A} \to \text{B}</math>. (Тривиальная зависимость). | ||
# '''Дополнение (Augmentation):''' Если < | # '''Дополнение (Augmentation):''' Если <math display="inline">\text{A} \to \text{B}</math>, то <math display="inline">\text{A, C} \to \text{B, C}</math>. (Добавление атрибута <math display="inline">\text{C}</math> к обеим частям). | ||
# '''Транзитивность:''' Если < | # '''Транзитивность:''' Если <math display="inline">\text{A} \to \text{B}</math> и <math display="inline">\text{B} \to \text{C}</math>, то <math display="inline">\text{A} \to \text{C}</math>. | ||
Эти аксиомы позволяют формально вывести все возможные ФЗ для отношения, зная некоторый начальный набор. | Эти аксиомы позволяют формально вывести все возможные ФЗ для отношения, зная некоторый начальный набор. | ||
Строка 155: | Строка 188: | ||
'''Пример (из слайда 38):''' | '''Пример (из слайда 38):''' | ||
{| | {| class="wikitable" | ||
! | |- | ||
! | ! style="text-align: left;"| StudID | ||
! | ! style="text-align: left;"| StudName | ||
! | ! style="text-align: left;"| ExamID | ||
! | ! style="text-align: left;"| ExamName | ||
! | ! style="text-align: left;"| ExDate | ||
! | ! style="text-align: left;"| ProfID | ||
! style="text-align: left;"| ProfName | |||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| Ivan Ivanov | | style="text-align: left;"| Ivan Ivanov | ||
| 34 | | style="text-align: left;"| 34 | ||
| OPD | | style="text-align: left;"| OPD | ||
| 14.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 55 | | style="text-align: left;"| 55 | ||
| Rebrov A. | | style="text-align: left;"| Rebrov A. | ||
|- | |- | ||
| | | style="text-align: left;"| | ||
| | | style="text-align: left;"| | ||
| 78 | | style="text-align: left;"| 78 | ||
| DBMS | | style="text-align: left;"| DBMS | ||
| 29.12.20 | | style="text-align: left;"| 29.12.20 | ||
| 789 | | style="text-align: left;"| 789 | ||
| Uvarov S. | | style="text-align: left;"| Uvarov S. | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| Egor Kirov | | style="text-align: left;"| Egor Kirov | ||
| 34 | | style="text-align: left;"| 34 | ||
| OPD | | style="text-align: left;"| OPD | ||
| 14.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 55 | | style="text-align: left;"| 55 | ||
| Rebrov A. | | style="text-align: left;"| Rebrov A. | ||
|- | |- | ||
| | | style="text-align: left;"| | ||
| | | style="text-align: left;"| | ||
| 87 | | style="text-align: left;"| 87 | ||
| History | | style="text-align: left;"| History | ||
| 25.01.19 | | style="text-align: left;"| 25.01.19 | ||
| 342 | | style="text-align: left;"| 342 | ||
| Serov G. | | style="text-align: left;"| Serov G. | ||
|} | |} | ||
Строка 205: | Строка 239: | ||
Как достичь: 1. '''(Плохой способ) “Расплющить” таблицу:''' Создать отдельную строку для каждого значения из повторяющейся группы. Это приводит к сильной избыточности. '''Пример (из слайда 40):''' | Как достичь: 1. '''(Плохой способ) “Расплющить” таблицу:''' Создать отдельную строку для каждого значения из повторяющейся группы. Это приводит к сильной избыточности. '''Пример (из слайда 40):''' | ||
{| | {| class="wikitable" | ||
|- | |- | ||
| | ! style="text-align: left;"| StudID | ||
| | ! style="text-align: left;"| StudName | ||
| | ! style="text-align: left;"| ExamID | ||
| | ! style="text-align: left;"| ExamName | ||
| | ! style="text-align: left;"| ExDate | ||
| | ! style="text-align: left;"| ProfID | ||
| | ! style="text-align: left;"| ProfName | ||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| Ivan Ivanov | | style="text-align: left;"| Ivan Ivanov | ||
| | | style="text-align: left;"| 34 | ||
| | | style="text-align: left;"| OPD | ||
| | | style="text-align: left;"| 14.01.19 | ||
| | | style="text-align: left;"| 55 | ||
| | | style="text-align: left;"| Rebrov A. | ||
|- | |- | ||
| | | style="text-align: left;"| 123 | ||
| | | style="text-align: left;"| Ivan Ivanov | ||
| | | style="text-align: left;"| 78 | ||
| | | style="text-align: left;"| DBMS | ||
| | | style="text-align: left;"| 29.12.20 | ||
| | | style="text-align: left;"| 789 | ||
| | | style="text-align: left;"| Uvarov S. | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| Egor Kirov | | style="text-align: left;"| Egor Kirov | ||
| 87 | | style="text-align: left;"| 34 | ||
| History | | style="text-align: left;"| OPD | ||
| 25.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 342 | | style="text-align: left;"| 55 | ||
| Serov G. | | style="text-align: left;"| Rebrov A. | ||
|- | |||
| style="text-align: left;"| 345 | |||
| style="text-align: left;"| Egor Kirov | |||
| style="text-align: left;"| 87 | |||
| style="text-align: left;"| History | |||
| style="text-align: left;"| 25.01.19 | |||
| style="text-align: left;"| 342 | |||
| style="text-align: left;"| Serov G. | |||
|} | |} | ||
Строка 254: | Строка 289: | ||
'''Таблица <code>STUDENTS</code>''' | '''Таблица <code>STUDENTS</code>''' | ||
{| | {| class="wikitable" | ||
|- | |- | ||
| | ! style="text-align: left;"| StudID (PK) | ||
| | ! style="text-align: left;"| StudName | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 123 | ||
| Egor Kirov | | style="text-align: left;"| Ivan Ivanov | ||
|- | |||
| style="text-align: left;"| 345 | |||
| style="text-align: left;"| Egor Kirov | |||
|} | |} | ||
'''Таблица <code>EXAMS</code>''' | '''Таблица <code>EXAMS</code>''' | ||
{| | {| class="wikitable" | ||
! StudID (FK) | |- | ||
! ExamID | ! style="text-align: left;"| StudID (FK) | ||
! ExamName | ! style="text-align: left;"| ExamID | ||
! ExDate | ! style="text-align: left;"| ExamName | ||
! ProfID | ! style="text-align: left;"| ExDate | ||
! ProfName | ! style="text-align: left;"| ProfID | ||
! style="text-align: left;"| ProfName | |||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| 34 | | style="text-align: left;"| 34 | ||
| OPD | | style="text-align: left;"| OPD | ||
| 14.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 55 | | style="text-align: left;"| 55 | ||
| Rebrov A. | | style="text-align: left;"| Rebrov A. | ||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| 78 | | style="text-align: left;"| 78 | ||
| DBMS | | style="text-align: left;"| DBMS | ||
| 29.12.20 | | style="text-align: left;"| 29.12.20 | ||
| 789 | | style="text-align: left;"| 789 | ||
| Uvarov S. | | style="text-align: left;"| Uvarov S. | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| 34 | | style="text-align: left;"| 34 | ||
| OPD | | style="text-align: left;"| OPD | ||
| 14.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 55 | | style="text-align: left;"| 55 | ||
| Rebrov A. | | style="text-align: left;"| Rebrov A. | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| 87 | | style="text-align: left;"| 87 | ||
| History | | style="text-align: left;"| History | ||
| 25.01.19 | | style="text-align: left;"| 25.01.19 | ||
| 342 | | style="text-align: left;"| 342 | ||
| Serov G. | | style="text-align: left;"| Serov G. | ||
|} | |} | ||
Строка 315: | Строка 352: | ||
'''Пример (используем “расплющенную” таблицу из 1НФ):''' | '''Пример (используем “расплющенную” таблицу из 1НФ):''' | ||
* Первичный ключ: < | * Первичный ключ: <math display="inline">\{\text{StudID}, \text{ExamID}\}</math>. | ||
* Неключевые атрибуты: < | * Неключевые атрибуты: <math display="inline">\text{StudName}</math>, <math display="inline">\text{ExamName}</math>, <math display="inline">\text{ExDate}</math>, <math display="inline">\text{ProfID}</math>, <math display="inline">\text{ProfName}</math>. | ||
* '''Частичные зависимости:''' | * '''Частичные зависимости:''' | ||
** < | ** <math display="inline">\text{StudID} \to \text{StudName}</math> (Зависит только от части ключа - <math display="inline">\text{StudID}</math>). | ||
** < | ** <math display="inline">\text{ExamID} \to \text{ExamName}</math> (Зависит только от части ключа - <math display="inline">\text{ExamID}</math>). | ||
* '''Полные зависимости:''' | * '''Полные зависимости:''' | ||
** < | ** <math display="inline">\{\text{StudID}, \text{ExamID}\} \to \text{ExDate}</math> (Предполагаем, что дата зависит от студента и экзамена). | ||
** < | ** <math display="inline">\{\text{StudID}, \text{ExamID}\} \to \text{ProfID}</math> (Предполагаем, что преподаватель зависит от студента и экзамена). | ||
* '''Другие зависимости:''' | * '''Другие зависимости:''' | ||
** < | ** <math display="inline">\text{ProfID} \to \text{ProfName}</math> (Имя преподавателя зависит от его ID). | ||
''Таблица не в 2НФ из-за частичных зависимостей < | ''Таблица не в 2НФ из-за частичных зависимостей <math display="inline">\text{StudName}</math> и <math display="inline">\text{ExamName}</math>.'' | ||
'''Декомпозиция для 2НФ:''' | '''Декомпозиция для 2НФ:''' | ||
# Выносим < | # Выносим <math display="inline">\text{StudID} \to \text{StudName}</math>: | ||
#* Новая таблица < | #* Новая таблица <math display="inline">\text{STUDENTS}</math>: <math display="inline">\{\text{StudID} (\text{PK}), \text{StudName}\}</math>. | ||
#* Старая таблица (переименуем в < | #* Старая таблица (переименуем в <math display="inline">\text{EXAMS}\_\text{PARTICIPATION}</math>): <math display="inline">\{\text{StudID} (\text{FK}), \text{ExamID} (\text{PK}), \text{ExamName}, \text{ExDate}, \text{ProfID}, \text{ProfName}\}</math>. (<math display="inline">\text{StudName}</math> удален). | ||
# Выносим < | # Выносим <math display="inline">\text{ExamID} \to \text{ExamName}</math> из <math display="inline">\text{EXAMS}\_\text{PARTICIPATION}</math>: | ||
#* Новая таблица < | #* Новая таблица <math display="inline">\text{EXAMS}</math>: <math display="inline">\{\text{ExamID} (\text{PK}), \text{ExamName}\}</math>. | ||
#* Таблица < | #* Таблица <math display="inline">\text{EXAMS}\_\text{PARTICIPATION}</math>: <math display="inline">\{\text{StudID} (\text{FK}), \text{ExamID} (\text{FK}), \text{ExDate}, \text{ProfID}, \text{ProfName}\}</math>. (<math display="inline">\text{ExamName}</math> удален). Первичный ключ теперь <math display="inline">\{\text{StudID}, \text{ExamID}\}</math>. | ||
'''Результат (все таблицы в 2НФ):''' | '''Результат (все таблицы в 2НФ):''' | ||
Строка 341: | Строка 378: | ||
'''Таблица <code>STUDENTS</code>''' | '''Таблица <code>STUDENTS</code>''' | ||
{| | {| class="wikitable" | ||
! StudID (PK) | |- | ||
! StudName | ! style="text-align: left;"| StudID (PK) | ||
! style="text-align: left;"| StudName | |||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| Ivan Ivanov | | style="text-align: left;"| Ivan Ivanov | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| Egor Kirov | | style="text-align: left;"| Egor Kirov | ||
|} | |} | ||
'''Таблица <code>EXAMS</code>''' | '''Таблица <code>EXAMS</code>''' | ||
{| | {| class="wikitable" | ||
|- | |- | ||
| | ! style="text-align: left;"| ExamID (PK) | ||
| | ! style="text-align: left;"| ExamName | ||
|- | |- | ||
| | | style="text-align: left;"| 34 | ||
| | | style="text-align: left;"| OPD | ||
|- | |- | ||
| 87 | | style="text-align: left;"| 78 | ||
| History | | style="text-align: left;"| DBMS | ||
|- | |||
| style="text-align: left;"| 87 | |||
| style="text-align: left;"| History | |||
|} | |} | ||
'''Таблица <code>EXAMS_PARTICIPATION</code>''' | '''Таблица <code>EXAMS_PARTICIPATION</code>''' | ||
{| | {| class="wikitable" | ||
|- | |- | ||
| | ! style="text-align: left;"| StudID (FK, PK) | ||
| | ! style="text-align: left;"| ExamID (FK, PK) | ||
| | ! style="text-align: left;"| ExDate | ||
| | ! style="text-align: left;"| ProfID | ||
| | ! style="text-align: left;"| ProfName | ||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| | | style="text-align: left;"| 34 | ||
| | | style="text-align: left;"| 14.01.19 | ||
| | | style="text-align: left;"| 55 | ||
| | | style="text-align: left;"| Rebrov A. | ||
|- | |- | ||
| | | style="text-align: left;"| 123 | ||
| | | style="text-align: left;"| 78 | ||
| | | style="text-align: left;"| 29.12.20 | ||
| | | style="text-align: left;"| 789 | ||
| | | style="text-align: left;"| Uvarov S. | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| 87 | | style="text-align: left;"| 34 | ||
| 25.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 342 | | style="text-align: left;"| 55 | ||
| Serov G. | | style="text-align: left;"| Rebrov A. | ||
|- | |||
| style="text-align: left;"| 345 | |||
| style="text-align: left;"| 87 | |||
| style="text-align: left;"| 25.01.19 | |||
| style="text-align: left;"| 342 | |||
| style="text-align: left;"| Serov G. | |||
|} | |} | ||
Строка 410: | Строка 450: | ||
'''Пример (используем таблицы из 2НФ):''' | '''Пример (используем таблицы из 2НФ):''' | ||
* В таблицах < | * В таблицах <math display="inline">\text{STUDENTS}</math> и <math display="inline">\text{EXAMS}</math> нет транзитивных зависимостей (там просто ключ и один неключевой атрибут). | ||
* Рассмотрим < | * Рассмотрим <math display="inline">\text{EXAMS}\_\text{PARTICIPATION}</math>: | ||
** Первичный ключ: < | ** Первичный ключ: <math display="inline">\{\text{StudID}, \text{ExamID}\}</math>. | ||
** Неключевые атрибуты: < | ** Неключевые атрибуты: <math display="inline">\text{ExDate}</math>, <math display="inline">\text{ProfID}</math>, <math display="inline">\text{ProfName}</math>. | ||
** '''Зависимости:''' | ** '''Зависимости:''' | ||
*** < | *** <math display="inline">\{\text{StudID}, \text{ExamID}\} \to \text{ExDate}</math> (Полная) | ||
*** < | *** <math display="inline">\{\text{StudID}, \text{ExamID}\} \to \text{ProfID}</math> (Полная) | ||
*** < | *** <math display="inline">\text{ProfID} \to \text{ProfName}</math> (Зависимость между неключевыми атрибутами!) | ||
** '''Транзитивная зависимость:''' < | ** '''Транзитивная зависимость:''' <math display="inline">\{\text{StudID}, \text{ExamID}\} \to \text{ProfID}</math> и <math display="inline">\text{ProfID} \to \text{ProfName}</math>, следовательно, <math display="inline">\text{ProfName}</math> ''транзитивно'' зависит от первичного ключа через <math display="inline">\text{ProfID}</math>. | ||
''Таблица < | ''Таблица <math display="inline">\text{EXAMS}\_\text{PARTICIPATION}</math> не в 3НФ.'' | ||
'''Декомпозиция для 3НФ:''' | '''Декомпозиция для 3НФ:''' | ||
# Выносим < | # Выносим <math display="inline">\text{ProfID} \to \text{ProfName}</math> из <math display="inline">\text{EXAMS}\_\text{PARTICIPATION}</math>: | ||
#* Новая таблица < | #* Новая таблица <math display="inline">\text{PROFS}</math>: <math display="inline">\{\text{ProfID} (\text{PK}), \text{ProfName}\}</math>. | ||
#* Таблица < | #* Таблица <math display="inline">\text{EXAMS}\_\text{PARTICIPATION}</math>: <math display="inline">\{\text{StudID} (\text{FK, PK}), \text{ExamID} (\text{FK, PK}), \text{ExDate}, \text{ProfID} (\text{FK})\}</math>. (<math display="inline">\text{ProfName}</math> удален, <math display="inline">\text{ProfID}</math> остался как внешний ключ к <math display="inline">\text{PROFS}</math>). | ||
'''Результат (все таблицы в 3НФ):''' | '''Результат (все таблицы в 3НФ):''' | ||
Строка 434: | Строка 474: | ||
'''Таблица <code>PROFS</code>''' | '''Таблица <code>PROFS</code>''' | ||
{| | {| class="wikitable" | ||
|- | |- | ||
| | ! style="text-align: left;"| ProfID (PK) | ||
| | ! style="text-align: left;"| ProfName | ||
|- | |- | ||
| | | style="text-align: left;"| 55 | ||
| | | style="text-align: left;"| Rebrov A. | ||
|- | |- | ||
| 342 | | style="text-align: left;"| 789 | ||
| Serov G. | | style="text-align: left;"| Uvarov S. | ||
|- | |||
| style="text-align: left;"| 342 | |||
| style="text-align: left;"| Serov G. | |||
|} | |} | ||
'''Таблица <code>EXAMS_PARTICIPATION</code>''' | '''Таблица <code>EXAMS_PARTICIPATION</code>''' | ||
{| | {| class="wikitable" | ||
! StudID (FK, PK) | |- | ||
! ExamID (FK, PK) | ! style="text-align: left;"| StudID (FK, PK) | ||
! ExDate | ! style="text-align: left;"| ExamID (FK, PK) | ||
! ProfID (FK) | ! style="text-align: left;"| ExDate | ||
! style="text-align: left;"| ProfID (FK) | |||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| 34 | | style="text-align: left;"| 34 | ||
| 14.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 55 | | style="text-align: left;"| 55 | ||
|- | |- | ||
| 123 | | style="text-align: left;"| 123 | ||
| 78 | | style="text-align: left;"| 78 | ||
| 29.12.20 | | style="text-align: left;"| 29.12.20 | ||
| 789 | | style="text-align: left;"| 789 | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| 34 | | style="text-align: left;"| 34 | ||
| 14.01.19 | | style="text-align: left;"| 14.01.19 | ||
| 55 | | style="text-align: left;"| 55 | ||
|- | |- | ||
| 345 | | style="text-align: left;"| 345 | ||
| 87 | | style="text-align: left;"| 87 | ||
| 25.01.19 | | style="text-align: left;"| 25.01.19 | ||
| 342 | | style="text-align: left;"| 342 | ||
|} | |} | ||
Строка 480: | Строка 522: | ||
* '''Более строгая версия 3НФ.''' | * '''Более строгая версия 3НФ.''' | ||
* '''Правило:''' Отношение находится в НФБК, если для ''каждой'' нетривиальной функциональной зависимости < | * '''Правило:''' Отношение находится в НФБК, если для ''каждой'' нетривиальной функциональной зависимости <math display="inline">\text{A} \to \text{B}</math>, детерминант <math display="inline">\text{A}</math> является '''суперключом''' (т.е. содержит в себе потенциальный ключ). | ||
* Большинство таблиц в 3НФ также находятся и в НФБК. Проблемы могут возникнуть при наличии нескольких перекрывающихся потенциальных ключей. | * Большинство таблиц в 3НФ также находятся и в НФБК. Проблемы могут возникнуть при наличии нескольких перекрывающихся потенциальных ключей. | ||
* Часто является конечной целью нормализации на практике. | * Часто является конечной целью нормализации на практике. |
Текущая версия от 14:23, 30 апреля 2025
В предыдущей главе мы научились проектировать структуру базы данных с помощью ER-моделей и создавать таблицы с помощью SQL. Но как убедиться, что полученная структура таблиц (схема) является “хорошей”? Не приведет ли она к проблемам при работе с данными?
Именно для ответа на эти вопросы существует процесс нормализации.
Часть 1: Реляционное представление и Реляционная Алгебра (Краткое Повторение и Дополнение)
Прежде чем говорить о нормализации, вспомним ключевые моменты реляционной модели и введем несколько понятий из реляционной алгебры – формального языка для манипулирования отношениями (таблицами).
- Отношение (Таблица): Набор кортежей (строк).
- Атрибут (Столбец): Имеет имя и домен (тип данных).
- Кортеж (Строка): Набор значений атрибутов.
- Ключ: Атрибут(ы), уникально идентифицирующий кортеж.
Реляционная Алгебра:
Это набор операций над отношениями, результатом которых всегда является новое отношение. Нам понадобятся основные операции для понимания того, как СУБД может выполнять запросы и как устроена нормализация.
- Выборка (Selection, σ): Выбирает кортежи (строки) из отношения, удовлетворяющие заданному условию (предикату ).
- Обозначение: - выбрать строки из отношения R, где условие истинно.
- SQL аналог: Условие в секции
WHERE
. - Пример: Выбрать студентов группы ‘3100’, чей ID >= 150000.
- SQL:
SELECT * FROM STUDENTS WHERE STUDENTS.GROUP = '3100' AND STUDENTS.ID >= 150000;
- Рел. Алгебра: (Здесь означает логическое “И”)
- SQL:
- Проекция (Projection, π): Выбирает указанные атрибуты (столбцы) из отношения, удаляя дубликаты строк в результирующем отношении.
- Обозначение: - выбрать столбцы из отношения R.
- SQL аналог: Список столбцов в секции
SELECT
. Удаление дубликатов соответствуетSELECT DISTINCT
. - Пример: Получить имена и группы всех студентов.
- SQL:
SELECT DISTINCT name, group FROM STUDENTS;
- Рел. Алгебра:
- SQL:
- Соединение (Join, ⋈): Комбинирует кортежи из двух отношений на основе некоторого условия (, тета).
- Тета-соединение (Theta Join): . Общий случай, где - любое условие сравнения между атрибутами R и S. Логически эквивалентно: (выборка из декартова произведения).
- Эквисоединение (Equijoin): Частный случай тета-соединения, где условие содержит только операции равенства ().
- Естественное соединение (Natural Join): Эквисоединение по всем атрибутам с одинаковыми именами, причем совпадающие столбцы включаются в результат только один раз.
- SQL аналог: Операторы
JOIN
.INNER JOIN ON условие
соответствует тета-соединению или эквисоединению.NATURAL JOIN
соответствует естественному соединению. - Пример: Соединить студентов и их экзамены по ID студента.
- SQL:
SELECT * FROM STUDENTS JOIN EXAMS ON STUDENTS.ID = EXAMS.STUD_ID;
- Рел. Алгебра: , где условие это
- SQL:
Законы Реляционной Алгебры (Примеры Эквивалентных Преобразований):
СУБД использует правила эквивалентности для преобразования запроса в различные планы выполнения, чтобы выбрать наиболее эффективный.
- Соединение коммутативно (для
INNER JOIN
): - Соединение ассоциативно (для
INNER JOIN
): - Каскад выборок:
- “Проталкивание” выборки через соединение: Если условие относится только к атрибутам , то: . Это важное правило оптимизации: выполнять выборку как можно раньше, чтобы уменьшить размер данных для соединения.
- “Проталкивание” проекции через соединение (сложнее): можно преобразовать, оставив в и только те атрибуты, которые нужны для результата () и для условия соединения (). Тоже важное правило: отбрасывать ненужные столбцы как можно раньше.
План Выполнения Запроса:
Это последовательность операций реляционной алгебры (и конкретных алгоритмов их выполнения), которую СУБД строит для ответа на SQL-запрос. Часто представляется в виде дерева операций. Для одного SQL-запроса может существовать несколько эквивалентных планов (дающих одинаковый результат), но с разной стоимостью выполнения. Оптимизатор запросов выбирает план с наименьшей предполагаемой стоимостью.
Пример одного из возможных планов для запроса SELECT * FROM STUDENTS ST JOIN EXAMS EXAM ON ST.ID = EXAM.STUD_ID WHERE ST.GROUP = '3100' AND ST.ID >= 150000;
Часть 2: Проблемы Плохого Дизайна и Аномалии
Зачем нам вообще нужна нормализация, если мы можем просто создать одну большую таблицу со всеми нужными данными? Рассмотрим таблицу STUDENTS
с информацией о студентах, их группах и кураторах (GrMentor - Куратор Группы).
Таблица STUDENTS
(Ненормализованная)
StudID | StudName | Group | GrMentor |
---|---|---|---|
1 | Ivan Petrov | P3100 | Egor Kirov |
3 | Vasily Ivanov | P3101 | Roman Ivov |
34 | Gleb Anisimov | P3100 | Egor Kirov |
Проблемы:
Избыточность Данных: Информация о кураторе (
GrMentor
) повторяется для каждого студента из одной и той же группы (Egor Kirov для группы P3100). Это ведет к неэффективному использованию памяти.Аномалии Обновления (Update Anomalies): Если куратор группы P3100 сменится, нам придется обновить поле
GrMentor
во всех строках, гдеGroup = 'P3100'
. Если мы обновим не все строки, данные станут противоречивыми (у одной группы окажется несколько кураторов).-- Попытка сменить куратора только для одного студента UPDATE STUDENTS SET GrMentor = 'Eugene Lomov' WHERE StudName = 'Ivan Petrov';
Результат (Несогласованность):
StudID StudName Group GrMentor 1 Ivan Petrov P3100 Eugene Lomov (Это новый куратор) 3 Vasily Ivanov P3101 Roman Ivov 34 Gleb Anisimov P3100 Egor Kirov (А тут остался старый) Аномалии Вставки (Insertion Anomalies):
- Мы не можем добавить информацию о новой группе и ее кураторе, пока в этой группе нет хотя бы одного студента (потому что
StudID
, вероятно, часть первичного ключа или просто идентификатор студента, который не может бытьNULL
для информации о группе). - При добавлении нового студента в существующую группу, мы должны правильно указать ее куратора. Ошибка при вводе имени куратора приведет к несогласованности (см. пример со слайда 26, где
E.Kirov
иEgor Lomov
могут быть одним человеком).
-- Попытка добавить студентов с разными написаниями куратора INSERT INTO STUDENTS VALUES(57, 'Nina Simonova', 'P3100', 'E. Kirov'); INSERT INTO STUDENTS VALUES(58, 'Petr Uvarov', 'P3100', 'Egor Lomov');
- Мы не можем добавить информацию о новой группе и ее кураторе, пока в этой группе нет хотя бы одного студента (потому что
Аномалии Удаления (Deletion Anomalies): Если мы удалим последнего студента из какой-либо группы (например, Василия Иванова из P3101), мы потеряем информацию о самой группе P3101 и ее кураторе Романе Ивове, даже если эта информация нам еще нужна.
DELETE FROM STUDENTS WHERE StudName = 'Vasily Ivanov';
После этого запроса информация о группе P3101 и ее кураторе исчезнет из таблицы.
Все эти проблемы возникают из-за того, что в одной таблице смешаны данные о разных сущностях (студенты и группы/кураторы) и существуют “неправильные” зависимости между атрибутами. Нормализация помогает выявить и устранить эти проблемы.
Часть 3: Функциональные Зависимости (ФЗ)
Ключевое понятие для нормализации.
- Функциональная зависимость (Functional Dependency, FD) описывает смысловую связь между атрибутами внутри одного отношения (таблицы).
- Говорят, что атрибут (или набор атрибутов) функционально зависит от атрибута (или набора атрибутов) , если для каждого возможного значения существует ровно одно соответствующее значение .
- Обозначение: (читается “A функционально определяет B” или “B функционально зависит от A”).
- называется детерминантом.
Важно: ФЗ определяются смыслом данных (семантикой) предметной области, а не текущими данными в таблице!
Примеры ФЗ для таблицы STUDENTS
(StudID, StudName, Group, GrMentor):
- (По ID студента однозначно определяется его имя).
- (По ID студента однозначно определяется его группа).
- (По ID студента можно узнать его группу, а по группе - куратора, т.е. косвенно ID определяет куратора).
- (По номеру группы однозначно определяется ее куратор. Предполагаем, что у группы только один куратор).
- (Тоже верно, но избыточно, т.к. уже зависит от ).
Примеры НЕ ФЗ:
- (Неверно, т.к. в одной группе много студентов).
- (Неверно, т.к. могут быть тезки).
Типы ФЗ:
- Тривиальная ФЗ: Зависимость вида , где является подмножеством . Например, . Такие зависимости выполняются всегда и не несут полезной информации для нормализации. Обычно рассматривают только нетривиальные ФЗ.
- Полная ФЗ: Зависимость , где – составной детерминант (несколько атрибутов), и не зависит ни от какого подмножества . Мы уже обсуждали это в контексте 2НФ.
- Частичная ФЗ: Зависимость , где – составной детерминант, и зависит от части . (Пример: , но при этом . Здесь частично зависит от ключа).
- Транзитивная ФЗ: Зависимость , которая существует только через промежуточный атрибут , такой что и , при этом не зависит от ( неверно) и не является частью ключа . (Пример: и , следовательно, транзитивно через ).
Аксиомы Армстронга: Формальные правила для вывода новых ФЗ из существующих:
- Рефлексивность: Если , то . (Тривиальная зависимость).
- Дополнение (Augmentation): Если , то . (Добавление атрибута к обеим частям).
- Транзитивность: Если и , то .
Эти аксиомы позволяют формально вывести все возможные ФЗ для отношения, зная некоторый начальный набор.
Часть 4: Нормальные Формы
Процесс нормализации заключается в последовательном приведении таблиц к нормальным формам более высокого порядка. Каждая следующая нормальная форма накладывает более строгие ограничения.
Ненормализованная Форма (UNF / 0NF): Таблица содержит неатомарные значения (повторяющиеся группы, списки в ячейках). Это нарушает основные принципы реляционной модели.
Пример (из слайда 38):
StudID | StudName | ExamID | ExamName | ExDate | ProfID | ProfName |
---|---|---|---|---|---|---|
123 | Ivan Ivanov | 34 | OPD | 14.01.19 | 55 | Rebrov A. |
78 | DBMS | 29.12.20 | 789 | Uvarov S. | ||
345 | Egor Kirov | 34 | OPD | 14.01.19 | 55 | Rebrov A. |
87 | History | 25.01.19 | 342 | Serov G. |
Здесь у одного студента несколько экзаменов, дат, преподавателей - значения неатомарны.
Первая Нормальная Форма (1НФ):
- Правило: Отношение находится в 1НФ, если все его атрибуты содержат только атомарные (неделимые) значения. На пересечении строки и столбца - ровно одно значение из домена.
Как достичь: 1. (Плохой способ) “Расплющить” таблицу: Создать отдельную строку для каждого значения из повторяющейся группы. Это приводит к сильной избыточности. Пример (из слайда 40):
StudID | StudName | ExamID | ExamName | ExDate | ProfID | ProfName |
---|---|---|---|---|---|---|
123 | Ivan Ivanov | 34 | OPD | 14.01.19 | 55 | Rebrov A. |
123 | Ivan Ivanov | 78 | DBMS | 29.12.20 | 789 | Uvarov S. |
345 | Egor Kirov | 34 | OPD | 14.01.19 | 55 | Rebrov A. |
345 | Egor Kirov | 87 | History | 25.01.19 | 342 | Serov G. |
Теперь значения атомарны, но информация о студенте Иванове и Кирове дублируется.
- (Хороший способ) Декомпозиция: Разделить таблицу на несколько, вынеся повторяющиеся группы в отдельную таблицу со связью через внешний ключ. Пример (из слайда 41):
Таблица STUDENTS
StudID (PK) | StudName |
---|---|
123 | Ivan Ivanov |
345 | Egor Kirov |
Таблица EXAMS
StudID (FK) | ExamID | ExamName | ExDate | ProfID | ProfName |
---|---|---|---|---|---|
123 | 34 | OPD | 14.01.19 | 55 | Rebrov A. |
123 | 78 | DBMS | 29.12.20 | 789 | Uvarov S. |
345 | 34 | OPD | 14.01.19 | 55 | Rebrov A. |
345 | 87 | History | 25.01.19 | 342 | Serov G. |
Теперь обе таблицы в 1НФ.
Вторая Нормальная Форма (2НФ):
- Правило: Отношение находится в 2НФ, если оно находится в 1НФ и все неключевые атрибуты полностью функционально зависят от каждого потенциального ключа. (Нет частичных зависимостей от ключа).
- Актуально для таблиц с составными первичными ключами.
- Цель: Устранить избыточность, возникающую из-за того, что неключевой атрибут зависит только от части составного ключа.
- Как достичь: Вынести частично зависимые атрибуты и ту часть ключа, от которой они зависят, в отдельную таблицу.
Пример (используем “расплющенную” таблицу из 1НФ):
- Первичный ключ: .
- Неключевые атрибуты: , , , , .
- Частичные зависимости:
- (Зависит только от части ключа - ).
- (Зависит только от части ключа - ).
- Полные зависимости:
- (Предполагаем, что дата зависит от студента и экзамена).
- (Предполагаем, что преподаватель зависит от студента и экзамена).
- Другие зависимости:
- (Имя преподавателя зависит от его ID).
Таблица не в 2НФ из-за частичных зависимостей и .
Декомпозиция для 2НФ:
- Выносим :
- Новая таблица : .
- Старая таблица (переименуем в ): . ( удален).
- Выносим из :
- Новая таблица : .
- Таблица : . ( удален). Первичный ключ теперь .
Результат (все таблицы в 2НФ):
Таблица STUDENTS
StudID (PK) | StudName |
---|---|
123 | Ivan Ivanov |
345 | Egor Kirov |
Таблица EXAMS
ExamID (PK) | ExamName |
---|---|
34 | OPD |
78 | DBMS |
87 | History |
Таблица EXAMS_PARTICIPATION
StudID (FK, PK) | ExamID (FK, PK) | ExDate | ProfID | ProfName |
---|---|---|---|---|
123 | 34 | 14.01.19 | 55 | Rebrov A. |
123 | 78 | 29.12.20 | 789 | Uvarov S. |
345 | 34 | 14.01.19 | 55 | Rebrov A. |
345 | 87 | 25.01.19 | 342 | Serov G. |
Третья Нормальная Форма (3НФ):
- Правило: Отношение находится в 3НФ, если оно находится в 2НФ и все неключевые атрибуты нетранзитивно зависят от каждого потенциального ключа. (Нет транзитивных зависимостей неключевых атрибутов от ключа через другие неключевые атрибуты).
- Цель: Устранить избыточность, возникающую из-за того, что неключевой атрибут зависит от другого неключевого атрибута.
- Как достичь: Вынести транзитивно зависимые атрибуты и их непосредственный детерминант в отдельную таблицу.
Пример (используем таблицы из 2НФ):
- В таблицах и нет транзитивных зависимостей (там просто ключ и один неключевой атрибут).
- Рассмотрим :
- Первичный ключ: .
- Неключевые атрибуты: , , .
- Зависимости:
- (Полная)
- (Полная)
- (Зависимость между неключевыми атрибутами!)
- Транзитивная зависимость: и , следовательно, транзитивно зависит от первичного ключа через .
Таблица не в 3НФ.
Декомпозиция для 3НФ:
- Выносим из :
- Новая таблица : .
- Таблица : . ( удален, остался как внешний ключ к ).
Результат (все таблицы в 3НФ):
Таблица STUDENTS
(без изменений) Таблица EXAMS
(без изменений)
Таблица PROFS
ProfID (PK) | ProfName |
---|---|
55 | Rebrov A. |
789 | Uvarov S. |
342 | Serov G. |
Таблица EXAMS_PARTICIPATION
StudID (FK, PK) | ExamID (FK, PK) | ExDate | ProfID (FK) |
---|---|---|---|
123 | 34 | 14.01.19 | 55 |
123 | 78 | 29.12.20 | 789 |
345 | 34 | 14.01.19 | 55 |
345 | 87 | 25.01.19 | 342 |
Нормальная Форма Бойса-Кодда (НФБК / BCNF):
- Более строгая версия 3НФ.
- Правило: Отношение находится в НФБК, если для каждой нетривиальной функциональной зависимости , детерминант является суперключом (т.е. содержит в себе потенциальный ключ).
- Большинство таблиц в 3НФ также находятся и в НФБК. Проблемы могут возникнуть при наличии нескольких перекрывающихся потенциальных ключей.
- Часто является конечной целью нормализации на практике.
Высшие нормальные формы (4НФ, 5НФ): Существуют и другие нормальные формы (4НФ, 5НФ, DKNF), которые решают более тонкие проблемы, связанные с многозначными зависимостями и зависимостями соединения. На практике они используются редко. Обычно достаточно достичь 3НФ или НФБК.
Часть 5: Денормализация
Иногда, после приведения базы данных к высокой нормальной форме (например, 3НФ или НФБК), оказывается, что для выполнения частых запросов требуется слишком много операций соединения (JOIN
) между таблицами. Это может снижать производительность.
В таких случаях иногда прибегают к денормализации — процессу осознанного нарушения некоторых правил нормализации для повышения производительности запросов.
- Прием: Объединение нескольких таблиц в одну, добавление избыточных данных.
- Плюсы:
- Уменьшение количества соединений в запросах.
- Потенциальное ускорение выполнения частых запросов на чтение.
- Минусы:
- Увеличение избыточности данных (занимает больше места).
- Повышенный риск аномалий (вставки, обновления, удаления).
- Требуется больше усилий для поддержания целостности данных (например, с помощью триггеров или на уровне приложения).
Денормализацию следует применять осторожно, только после тщательного анализа производительности и понимания всех рисков. Обычно она является оправданной в системах с преобладанием операций чтения (например, в хранилищах данных для аналитики), где скорость выборки критически важна.