Professional
SQL Server 2000 Database Design
Louis Davidson
wrox
Wrox Press Ltd.
Луис Дэвидсон
Проектирование баз данных на
SQL Server 2000 Перевод с английского
Москва БИНОМ. Лаборатория знаний 2003
УДК 004.65 ББК 32.973 ДП Переводчик В. А. Епанешников
Д11
Дэвидсон Л. Проектирование баз данных на SQL Server 2000 / Л. Дэвидсон; Пер. с англ. — М.: БИНОМ. Лаборатория знаний, 2003. — 680 с , ил. ISBN 5-94774-074-5 Книга посвящена вопросам проектирования баз данных (БД) с использованием пакета профессионального проектирования SQL Server 2000 фирмы Microsoft. Здесь приведен методический подход ко всем стадиям проектирования БД па основе опыта автора. Рассмотрены как вопросы логического проектирования, когда выбирается структура и основные характеристики базы данных, так и вопросы физического проектирования, включая реализацию, когда выбираются технические средства, программное обеспечение, пишутся программы, проводится необходимая отладка. Автор глубоко не вдавался в теоретические вопросы и использовал только необходимые с его точки зрения результаты. Изложенный материал иллюстрируется лишь небольшими примерами, позволяющими лучше понять те или иные рассматриваемые вопросы. Книга будет полезна как специалистам по проектированию БД, так и инженерам, обслуживающим ту или иную базу данных, созданную на основе SQL Server 2000. Отдельные части могут быть использованы преподавателями вузов для подготовки курсов по проектированию баз данных. УДК 004.65 ББК 32.973
По вопросам приобретения обращаться: в Москве «БИНОМ. Лаборатория знаний» (095)955-03-98, e-mail:
[email protected] в Санкт-Петербурге
«Диалект» (812)247-93-01, e-mail:
[email protected] Copyright © 2001, Wrox Press, Ltd © Перевод на русский язык, БИНОМ. Лаборатория знаний, 2003
ISBN 5-94774-074-5 (русск.) ISBN 1-861004-76-1 (англ.)
II
Подтверждения торговых марок Wrox старался обеспечить информацию о торговых марках всех компаний и продукции, упомянутых в этой книге соответствующим использованием заголовков. Однако Wrox не может гарантировать точность этой информации.
Список участников издания Автор Луис Дэвидсон
Менеджер серии Брюс Лоусон
Дополнительный материал Гэри Эванс Чарлз Хокинс Крис Ульман Адриан Янг
Технические редакторы Ховард Дэвис Пол Джефкот Гарет Окли Дуглас Патерсон
Технические рецензенты Мартен Больё Майкл Бенкович Максим Бомбардьер Роберт Чанг Джемс Р. Де-Карли Робин Дьюсон Майк Эриксон Эдвард Д. Феррон Скотт Ханзельман Марк Хорнер Симон Робинсон Марк X. Симкин Ричард Уорд Хельмут Уотсон Захр Юнее
Агент автора Тони Берри
Технические конструкторы Кэтрин Александер Кэт Холл Корректор Фиона Берриман
Администраторы проекта Аврил Корбин Силмара Лайон Координатор производства Пип Вонсон Индексация Билл Джонкокс Рисунки Шабнам Хуссейн Дополнительные рисунки Пип Вонсон Симон Хардвеа Обложка Шелли Фразьер Менеджер производства Симон Хардвеа
Сведения об авторе Луис — общепризнанный знаток баз данных. В настоящее время он трудится в качестве разработчика структуры базы данных для Сетей образования Америки в Нэшвилле, штат Теннесси, не говоря уже о написании этой книги и, возможно, еще одной. Он был проектировщиком и разработчиком баз данных на Microsoft SQL Server в течение девяти лет, начиная с его знаменательной неудачи как администратора локальной сети для Международной церковной штаб-квартиры в Кливленде, штат Теннесси (да, имеется такая в Кливленде, штат Теннесси). Начиная с объединения SQL Server с Visual Basic 1.0, он занимался этим продуктом в течение девяти лет, хотя его навыки в Visual Basic прогрессивно ухудшались с версии 4.0, поскольку он сосредоточился прежде всего на SQL Server. Луис выступал на ряде конференций, таких как CA-World (конференция Всемирной компьютерной ассоциации) в 1999 и 2000 гг., относительно решений с ERwin (пакет для проектирования структур баз данных); и PASS 2000 Europe, где он сделал две презентации: одну — по нормализации и другую — по оптимизации запросов. В семье у него жена Валери и дочь Крисси, с которыми он мечтает провести хотя бы один свободный день с тех пор, как установил на своем столе SQL Server и Visual Basic девять лет тому назад...
Благодарности Эта книга посвящена памяти моего отца
Я хотел бы также поблагодарить следующих лиц, которые непосредственно или косвенно влияли на появление этой книги: •
Господа нашего Иисуса Христа, кто создал меня со способностями проектировать и создавать компьютерное программное обеспечение.
•
Мою жену Валери и дочь Крисси, которые не видели меня в течение шести месяцев без переносного компьютера, даже во время отпуска; благодарю их за любовь и терпение.
•
Мою маму, без которой ...
•
Чака Хокинса за помощь в преодолении моих затруднений по материалам главы 13.
•
Чипа Брокера, Доналда Пластера, Майкла Фармера, которые были моими наставниками в течение последних лет.
•
Всех влиятельных людей, с которыми я в настоящее время работаю, и которые терпели меня в течение ряда месяцев при условии ограниченного сна. > ;
•
Всех профессоров, академиков, авторов и т. д., чьи курсы, книги и Web-страницы я читал в течение ряда лет, и которые прямо или косвенно заронили семена знаний в мою память.
•
Штат издательства WROX, кто (за исключением долгих ночей!) общался со мной очень хорошо и сделал все, что произошло.
•
Редакторов книги, которые помогли сформировать и объединить совокупность хаотичных идей в книгу, которую вы держите в своих руках.
Содержание Введение Что охватывает эта книга Кто должен читать эту книгу? Что вам нужно, чтобы использовать эту книгу Используемые соглашения по шрифтам Поддержка клиента
\ 2 2 2 3 4
Часть 1 — Л о г и ч е с к о е п р о е к т и р о в а н и е
5
Глава 1 : В в е д е н и е в м е т о д о л о г и ю Б Д
7
Введение История структур БД OLTP и организация хранилища данных Четыре модуля современной СУБД OLTP Оперативное хранилище данных (ODS) Хранилище данных Витрины данных Структура
Учебный пример Реляционное программирование по сравнению с процедурным программированием Организация циклов Доступ к сети
Краткий обзор процесса проектирования БД на основе SQL Server Резюме
7 8 9 10 11 12 13 14 16
16 17 17 18
19 20
Глава 2 : С б о р и н ф о р м а ц и и д л я п р о е к т а Б Д
21^
Команда проектировщиков БД Документация и связь Одобрение клиента Минимальные информационные требования Опытные образцы БД
22 23 23 24 24
Содержание Интервью с клиентами О чем следует спрашивать Кто будет использовать данные? Как данные будут использоваться? Что вы хотите видеть в отчетах? Где сейчас находятся ваши данные? Сколько эти данные стоят? Как будут данные в новой БД сочетаться с другими данными? Имеются ли какие-то правила, которые управляют использованием данных?
Другие источники для определения правил данных Запрос о цене или запрос предложений Контракты или наряды на работу клиента Соглашения об уровне обслуживания Не забудьте о ревизиях Старые системы Отчеты, формы, электронные таблицы
Схема учебного примера Интервью клиента Предварительная документация
Резюме Глава 3 : Фундаментальные концепции БД Введение Реляционная модель База данных Таблица Строки и столбцы Ограничение значений столбца только допустимыми значениями Идентификаторы строк Необязательные значения столбца Число строк Дополнительные соображения насчет таблиц SQL-операции Ограничение Набор Соединение Произведение Объединение Пересечение Разность Деление Другие операторы Отношения Бинарные отношения Небинарные отношения Последнее замечание относительно реляционной/SQL терминологии
ii
25 27 27 27 27 28 28 29 29 29 29 30 30 30 30 31 31 32 33 36 37
37 38 38 39 41 43 47 49 50 51 54 55 55 55 56 57 57 58 58 58 59 59 62 63
Содержание Определения существенных понятий Функциональная зависимость Зависимость с несколькими значениями
Резюме Глава 4 : Сущности, атрибуты, отношения и бизнес-правила Введение Отказ от выбора окончательной структуры на ранних стадиях Простой пример
Определение сущностей Люди Места Объекты Идеи Документы Другие сущности
Список сущностей
63 63 64
64 65 65 66 67
67 68 68 69 69 69 69
70
Определение атрибутов и доменов
72
Идентификаторы Описательная информация Указатели на расположение Связанная информация Список сущностей, атрибутов и доменов
72 73 74 75 75
Отношения между таблицами Отношения "один к п" Отношение "имеет" Отношение "является" Отношения "многие ко многим" Список отношений
Определение бизнес-правил Идентификация основных процессов Где мы сейчас? Определение необходимых дополнительных данных Повторное рассмотрение с клиентом Повторяйте ваши действия, пока не получите одобрение заказчика
Учебный пример книги Интервью клиента Документы Перечень объектов банковского счета Бизнес-правила и процессы
Метод водопада
77 77 78 78 79 79
81 83 84 85 86 86
87 87 90 93 98
100 iii
Содержание Резюме
101
Глава 5: Моделирование данных Введение Методологии моделирования UML IDEF1X Модели действий Модели данных
103 103 104 104 105 105 108
Сущности Тип сущности Обозначение Атрибуты Первичный ключ Вторичные ключи Внешние ключи Домены Обозначение Отношения Один ко многим Идентифицирующие отношения Неидентифицирующие отношения Обязательное отношение Необязательное отношение Мощность отношения Функциональные имена Другие типы отношений Рекурсия Отношения классификации Многие ко многим Глагольные конструкции (обозначения) Другие методы изображения отношений
109 109 111 112 112 113 114 115 117 118 119 119 120 121 122 123 124 125 125 127 129 130 132
Описательная информация
135
Учебный пример
137
Модель действий М одел ь дан н ых
137 140
Резюме
147
Глава 6 : Методы нормализации
149
Почему нужна нормализация?
150
Уменьшение значений NULL Устранение избыточных данных Устранение ненужного кодирования Максимизация кластерных индексов Уменьшение числа индексов на таблицу Хранение "тонких" таблиц
150 150 151 151 151 151
iv I
I
I!
Содержание Процесс нормализации
152
Первая нормальная форма Все атрибуты должны быть элементарными Экземпляры сущности должны содержать одно и то же число значений Все экземпляры записей сущности должны быть различны Программирование аномалий, которых следует избегать в 1НФ Признаки, что существующие данные не удовлетворяют 1НФ
152 153 155 155 155 158
Вторая нормальная форма
158
Сущность должна соответствовать 1НФ Каждый неключевой атрибут должен описывать весь ключ Проблемы программирования, которых можно избежать с помощью 2НФ Признаки, что ваши сущности не удовлетворяют 2НФ Кодирование при наличии этой проблемы
159 159 161 162 163
Третья нормальная форма
163
Сущность должна быть в 2НФ Неключевые атрибуты не могут описывать неключевые атрибуты Проблемы программирования, которые можно избежать с помощью ЗНФ Признаки, что ваши сущности не удовлетворяют ЗНФ Нормальная форма Бойса-Кодда Признаки программных аномалий
164 164 165 167 168 172
Учебный пример
172
Первая нормальная форма
172
Атрибуты должны быть элементарными Все экземпляры записей сущности должны содержать одно и то же число значений Все экземпляры записей сущности должны быть различными Нормальная форма Бойса-Кодда Итоговые данные Несколько полей с одинаковыми префиксами . Каждый детерминант должен быть ключом Модель
172 174 174 174 175 175 176 179
Резюме Глава 7 : Расширенные темы нормализации Введение Четвертая нормальная форма Тройные отношения Скрытые атрибуты с несколькими величинами Предыстория атрибутов
Дополнительные нормальные формы Денормализация Учебный пример Резюме Глава 8 : Завершение фазы логического проектирования Введение
179 181 181 182 183 189 192
195 196 197 203 205 205
Содержание Использование данных
206
Создание отчетов Стратегия получения отчетов Прототипы отчетов Определение использования данных и права доступа Безопасность Известные ограничения структуры Взаимодействие с внешними системами Пример 1 — Проблематичная система планирования ресурса предприятия Пример 2 — Другой пример "Как не надо делать" Пример 3 — Системы, страдающие от денормализации Дополнительные проблемы, возникающие при взаимодействии с системами третьих лиц Планы преобразования данных
207 208 210 210 211 212 213 213 214 215 216 217
Планирование требуемых объемов План проекта Окончательный обзор документации
217 218 218
Дальнейшие требования
220
Учебный пример
220
Использование данных Отчеты Взаимодействие с внешними системами План преобразования данных Требуемые объемы План проектирования Безопасность Окончательный обзор документации
220 222 224 225 225 227 228 229
Резюме
229
Часть II — Физическое проектирование и реализация
231
Глава 9 : Планирование физической структуры
233
Введение
233
Проблемы формирования отчетов
235
Размер данных Сложность Требования поиска Конкуренция доступа пользователей Своевременность Частота
Влияние характеристик Скорость соединения Количество данных Бюджет Число пользователей vi
•
235 237 237 238 238 239
239 240 242 243 243
Содержание Возможности SQL Server Тиражирование Связанные серверы Сервисы преобразования данных Контроллер распределенных транзакций Объекты управления данными SQL Реализация СОМ-объектов Почта SQL Полнотекстовый поиск Агент SQL Server
Примеры основных топологий "Тонкий" клиент в сравнении с "толстым" клиентом "Толстый" клиент "Тонкий" клиент То, что в промежутке Клиент и конфигурация данных Классический клиент-сервер Трехзвенная конфигурация Сложное использование Web-серверов Глобальная сеть
Учебный пример Резюме Глава 1 0 : Планирование и реализация основной физической структуры Введение Средства генерации БД Схема физического проектирования Преобразование нашего логического проекта Подтипы Другие причины отхода от логического проекта Таблицы Обозначение Владелец Ограничения Столбцы Обозначение Выбор типов данных Точные числовые данные Приближенные числовые данные Дата и время-дата Двоичные данные Строки символов Вариантные данные Другие типы данных Типы данных, определяемые пользователем Необязательные данные
245 245 246 246 246 247 248 248 249 249
250 250 250 250 252 253 253 254 255 256
256 257 259 259 260 261 261 262 266 266 267 268 269 270 270 272 272 277 279 282 283 285 288 292 296 vii
Содержание Вычисляемые столбцы Служебные столбцы Управление параллелизмом
.
Сопоставление (порядок сортировки) Ключи
304 307
Индексы Первичные ключи Вторичные ключи Обозначение Другие индексы Отношения Внешние ключи Обозначение Каскадирование удалений и обновлений Взаимные отношения БД Распределение деталей вашей БД между разработчиками Информационная схема и системные хранимые процедуры Новые описательные свойства
307 315 317 318 319 319 319 320 322 324 325 325 330
Учебный пример Очистка модели Подтипы Упрощение сложных отношений Таблицы доменов Удаление ненужных сущностей Коррекция ошибок в модели
Подготовка модели для реализации Служебные столбцы Первичные ключи Типы данных
Физическая модель
Резюме
Глава 1 1 : Обеспечение целостности данных Введение Примеры таблиц Определяемые пользователем функции Скалярные функции Встроенные функции формирования таблицы Связывание схемы Функции со многими операторами, формирующие таблицы Что определяемые пользователем функции не могут делать
VIII
298 300 300
.
335 335 336 338 339 340 340
342 342 342 346
353
355
357 357 359 360 361 365 366 369 369
Ограничения
371
Ограничения, связанные со значениями по умолчанию Функции Ограничения-проверки Обработка ошибок, вызванных ограничениями Триггеры Кодирование триггеров Обработка ошибок
372 373 375 383 386 387 397
Содержание Т р и г г е р ы AFTER
400
Триггеры INSTEAD OF Использования триггеров INSTEAD OF
407 411
Код клиента и хранимые процедуры Изменчивые правила
417 419
Учебный пример
423
Ограничения по умолчанию Ограничения-проверки Триггеры Удаление времени из дат сделок Проверка согласования города и почтового индекса Распределения сделок не могут превышать размещенную сумму
423 424 431 431 432 433
Необязательные правила
435
Резюме
Глава 1 2 : Расширенный доступ к данным и методы корректировки Введение Рассмотрение запросов
436
437 437 438
Транзакции Блокировка Основные рекомендации
438 443 451
Основные проблемы кодирования
454
Временные таблицы Курсоры Обработка значений NULL Представления Что нового в представлениях? Хранимые процедуры Возвращаемые хранимыми процедурами значения Обработка ошибок Инкапсуляция ' Безопасность Счетчик транзакций
454 459 468 474 478 481 482 485 492 494 495
Общие способы работы с хранимыми процедурами
500
Восстановление данных Создание строк Корректировка строк Удаление строк Пакеты SQL-кода . Пакеты с одним оператором Пакеты с несколькими операторами Компилируемый SQL-код по сравнению с нерегламентированным SQL-кодом при создании приложений Нерегламентированный SQL-код Компилируемый SQL-код Советы
500 506 508 511 513 514 514 517 517 520 523
IX
Содержание
Рассмотрение безопасности Безопасность на уровне строк Безопасность на уровне столбцов Р а с с м о т р е н и е взаимодействия м е ж д у БД Тот же самый сервер Обозначение при использовании того же самого сервера Безопасность на том же самом сервере Корректировка данных на том же самом сервере Различные серверы (распределенные запросы) Учебный пример Основные хранимые процедуры Процедура размещения сделки Процедура обновления таблицы bank Процедура удаления получателя платежа Процедура формирования списка счетов Процедура заполнения типов доменов сделок Специальные хранимые процедуры Действие по выполнению баланса счета Действие по получению информации о счете Безопасность в учебном примере Резюме
Глава 1 3 :О п р е д е л е н и е т р е б о в а н и й к а п п а р а т н ы м с р е д с т в а м
523 527 529 530 530 530 530 531 532 533 534 534 535 537 538 539 539 540 541 542 543
545
Введение
545
Типы БД
546
OLTP OLAP
546 546
Рост OLTP-таблиц
547
Быстрый рост Медленный рост Отсутствие роста
547 547 548
Рост OLAP-таблиц
548
Рост пакетов Рост в связи с ростом компании "Нам нужно больше отчетов!" Не забывайте об индексах
548 549 549 549
Вычисление полного размера таблицы
549
Вычисление размера данных
550
Повторное рассмотрение индексируемых структур в виде В-дерева
553
Вычисление размера индекса
554
Размер загрузки транзакции
557
Содержание
Архивирование данных в случае необходимости Стоимость архивирования Детали архивирования Период архивирования Архивирование таблиц фактов с разделением по датам Архивирование таблиц фактов в соответствии с характеристиками Доступ к архивированным данным
Характеристики сервера Подсистемы памяти Подсистемы памяти на сервере Windows NT Подсистемы памяти на сервере Windows 2000
Управление работой памяти Искусство управления работой Управление работой SQL и узкие места Настройка памяти: ОС и SQL Server Динамическая настройка памяти в SQL Server 2000 Целевая свободная память Настройка памяти для нескольких экземпляров Настройка памяти процесса в SQL Server
Настройка работы сервера Подсистемы ЦП Контроль работы ЦП Волоконное управление
Дисковые подсистемы Основная дисковая подсистема Кэширование записи и SQL Server
Подсистема RAID RAID 0 RAID 1 RAID 5 RAID 10 — где 0 + 1 не равняется 1 Решения для нескольких контроллеров/каналов Настройка диска и контроль работы
Соединения пользователя Запирание и блокировка Демонстрация блокировки Контроль блокировок
Учебный пример Резюме Глава 14: Завершение проектирования Введение
559 560 560 561 561 562 563
563 563 564 564
565 566 567 568 569 570 571 572
573 574 575 576
576 577 577
578 578 579 580 581 581 582
583 585 585 586
588 594 595 595 XI
Содержание Настройка функционирования Поддержка Б Д только для чтения Моделирование изменений
596 598 600
Метод, ориентируемый на таблицы Метод, ориентируемый на решение Реализация Время ожидания Реализация Использование Задачи простого формирования отчетов Оперативное хранилище данных
601 , 605 607 609 610 613 614 615
Модели данных предприятия
618
Переход от опытного варианта Разработка
619 621
Аппаратные средства разработки Проверка характеристик Аппаратные средства проверки характеристик Реализация Обслуживание и планы восстановления поломок
Учебный пример Резюме П р и л о ж е н и е А: 1 2 правил К о д д а для С У Р Б Д Правило Правило Правило Правило
1 : Информационное правило 2: Правило гарантированного доступа 3: Систематическая обработка значений NULL 4: Д и н а м и ч е с к и й оперативный каталог, основанный на реляционной модели Правило 5: Правило общего языка манипулирования данными (ЯМД) Правило 6: Правило к о р р е к т и р о в к и представления Правило 7 : Операции добавления, к о р р е к т и р о в к и и удаления высокого уровня Правило 8: Ф и з и ч е с к а я независимость данных Правило 9 : Л о г и ч е с к а я независимость данных Правило 1 0 : Независимость целостности данных Правило 1 1 : Независимость распределения Правило 1 2 : Правило единственности Заключение
Предметный указатель
XII
621 621 622 622 622
624 628 629 629 630 630 630 631 631 632 632 632 634 634 634 635 637
Введение Если вы, находясь в вашем любимом книжном магазине, внимательно листаете эту книгу, потому что это — книга издательства WROX, я знаю, что вы, вероятно, думаете: "А где же здесь все про коды, настройки и всякое подобное?" Да, это книга не совсем такого рода. (Нет ничего предосудительного в этом виде книг; у меня на моем рабочем столе их тоже очень много.) Что я хотел собрать воедино в этом случае — это создать книгу по проектированию баз данных (БД), которая тонко балансирует между ориентированными на реализацию книгами по SQL Server, сосредоточенными на настройках DBCC (database consistency checker — модуль контроля непротиворечивости БД), выборе индексов и использовании всех кнопок и средств управления SQL Server, с одной стороны, и в полном смысле слова академическими томами, которые уходят глубоко в теорию БД, но дают немного или совсем никакой практической информации, с другой стороны. Эта книга охватывает процесс реализации БД с момента, когда кто-то говорит вам, что он хочет иметь базу данных, всю процедуру формирования таблиц и осуществление доступа к этим таблицам. Она включает также нормализацию таблиц вплоть до пятой нормальной формы. Цель этой книги проста; создать лучшую в мире книгу, такую, что если вы купите десять ее экземпляров, пошлете их десяти вашим лучшим друзьям, и попросите их сделать то же самое, я смогу купить прелестный компьютер с DVD-плеером. Реальная цель, однако, не столь очевидна. Я рассмотрел в течение ряда лет большое количество созданных программ, и независимо от того, насколько хороши были модели объекта, сопровождающий код, или даже сопутствующая документация, сама БД обычно находилась в диапазоне от плохого до ужасного. Мое желание состоит в том, чтобы обеспечить информацией конструкторов БД, что поможет им создавать надлежащие БД. Для
Введение любого, кто имел удовольствие читать учебники по БД для колледжей, ясно, что они несколько суховаты, — что-то вроде пустыни Мохаве в засуху. Это довольно плохо, так как большая часть пропущенной в них информации полезна и уместна. Так что моя переформулированная цель состоит в том, чтобы устранить разрыв между интеллектуальными гениями и нами, рабочими-программистами, и дать необходимые знания, подходящие для реального мира.
Что охватывает эта книга Эта книга состоит из двух частей. Первая охватывает логическое проектирование БД, а вторая рассматривает физическое проектирование и реализацию. Каждая из этих частей начинается с введения, содержащего общий краткий обзор, затем резюме тем, охватываемых каждой из глав. Мы не будем воспроизводить данный материал здесь — обратитесь, пожалуйста, непосредственно к соответствующим страницам.
Кто должен читать эту книгу? Не любая глава этой книги обязательно потребуется каждому читателю, и части ее также не предназначены для каждого программиста. Мне жаль, что это так, потому что в каждой главе имеется материал, который обогатит их способность проектировать и реализовывать БД. Однако это неизбежное зло — каждый знакомится с тем, что ему нужно. Системный архитектор БД Если вы уже системный архитектор БД, кто отвечает за сбор требований и проектирование БД (с причастностью/ответственностью за реализацию), то, пожалуйста, читайте всю книгу, — возможно, пропустив третью главу при первом чтении. Конструктор БД Если вы занимаетесь только реализацией БД, большая часть этой книги должна интересовать вас так, чтобы вы поняли причины, почему "безумный" проектировщик структуры данных хочет создать БД с пятьюдесятью таблицами, когда вы думаете, что требуется только три. Первое чтение могло бы включить главу 5 по моделированию данных, главы 6 и 7 по нормализации и полностью часть II книги. Эта часть описывает все методы реализации БД. Программист БД Если вы прежде всего пишете код для SQL Server, то при первом чтении вам будет интересна весьма небольшая часть этой книги. Хорошо в этом случае включить в чтение главу 5 по моделированию данных, главы 6 и 7 по нормализации, далее главы 9 и 10 по реализации БД, совместно с главами 11 и 12 по обращению к данным.
Что вам нужно, чтобы использовать эту книгу В первой половине книги мы будем обсуждать логическое моделирование данных. Нет никаких требований к программному обеспечению для работы с этой частью книги.
Введение Во второй части, связанной с физическим проектированием, единственное требование для работы с примерами — установленная копия пакета SQL Server 2000 и Query Analyzer (анализатор запроса), который поставляется вместе с пакетом. Это может быть любое издание SQL Server (версия Personal Edition — версия, которую можно поставить на вашем персональном компьютере), если вы сможете соединить ее с Query Analyzer. Вам потребуется база данных, которую вы можете создать с пользователем, выступающим в роли собственника БД, поскольку мы будем создавать все объекты с точки зрения собственника БД. Если вы не имеете копии SQL Server, оценку примера можно выполнить на SQL Server фирмы Microsoft, находящемся на Web-сайте www.microsoft.com/sql.
Используемые соглашения по шрифтам При пролистывании книги вы сталкиваетесь с различными стилями. Это было сделано, чтобы помочь вам легко идентифицировать различные типы информации и не пропустить каких-либо ключевых моментов. Эти стили следующие:
Важная информация, ключевые моменты и дополнительные объяснения показаны выделением подобно этому абзацу. Убедитесь, что вы обратили внимание на них, когда их встречаете.
Общие примечания, вспомогательная информация и краткие замечания выглядят так, как этот абзац. •
Кнопки, которые вы нажимаете на клавиатуре, подобно Ctrl и Delete, показаны курсивом.
•
Если вы видите что-то вроде BackupDB, это будет обозначать имя файла, название объекта или заголовок функции.
•
Первый раз, когда вы сталкиваетесь с важным термином, он изображается полужирным шрифтом.
•
Слова, которые появляются на экране, типа выбора меню, изображаются шрифтом, подобным тому, который используется на экране, например, меню File.
Когда образцы кода изображаются первый раз, они оформляются следующим образом:
I
Private Sub Command_Click MsgBox "Don't touch me" End Sub
С другой стороны, если вы уже видели код, или он не относится непосредственно к сути вопроса, то он изображается следующим образом: Private Sub Command_Click MsgBox "Don't touch me" End Sub
Введение
Поддержка клиента Мы хотим знать, что вы думаете об этой книге: что вам понравилось, что не понравилось, и что, по вашему мнению, мы можем сделать лучше в следующий раз. Вы можете послать ваши комментарии по электронной почте (по адресу feedback@wrox. com). Пожалуйста, не забудьте упомянуть название книги в вашем сообщении. Wrox имеет группу поддержки, так что если у вас появятся любые вопросы, пожалуйста, пошлите их также по адресу вышеупомянутой электронной почты.
Исходный код Полный исходный код для примеров, используемых в этой книге, может быть получен с Web-сайта фирмы по адресу: http://www.wrox.com.
Опечатки Мы приложили все усилия, чтобы удостовериться, что нет никаких ошибок в тексте или коде. Однако человеку присуще ошибаться, и поэтому мы считаем нужным информировать вас относительно любых ошибок: как их определить и исправить. Списки опечаток для всех наших книг находятся по адресу www.wrox.com. Если вы найдете ошибку, которая не была еще выявлена, пожалуйста, сообщите нам. p 2 p . w r o x . c o m Для поддержки авторов и подобных им лиц служит список адресатов SQL Server. Наша уникальная система обеспечивает взаимопомощь программистов (торговая марка programmer to programmer) с помощью списка адресатов, форумов и сетевых конференций в дополнение к системе нашей непосредственной электронной почты. Можете быть уверены, что ваш вопрос исследуется не только профессионалом поддержки, но многими Wrox-авторами и другими экспертами промышленности из наших списков адресатов. В p2p.wrox.com вы найдете список, специально нацеленный на разработчиков, использующих-SQL Server, которые поддержат вас не только в то время, когда вы читаете эту книгу, но также и тогда, когда начнете разрабатывать ваши собственные приложения. Зарегистрироваться на такую поддержку следует с помощью четырехступенчатой системы: 1.
Выйти на p2p.wrox.com.
2.
Щелкнуть на sql_server как на тип списка адресатов, к которому вы желаете присоединиться, затем щелкнуть Subscribe (присоединиться).
3.
Заполнить ваши данные и снова щелкнуть Subscribe.
4.
Дать подтверждение электронной почты.
Почему эта система дает лучшую поддержку Вы можете пожелать присоединиться к спискам адресатов или можете получать их как еженедельный обзор. Если вы не имеете времени или средств, чтобы получить список адресатов, то можете поискать наш диалоговый архив. Барахло и бесполезные сообщения удаляются, и адрес вашей электронной почты защищен уникальной системой Lyris. Любые вопросы относительно присоединения или отключения списков или любые другие вопросы относительно списка следует посылать по адресу
[email protected].
Часть 1 — Логическое проектирование Логическое проектирование БД — одна из наиболее важных задач в любом проектировании БД, и все же, вероятно, также и наименее понятная. Данная часть книги определенно нацелена изменить это, объединяя небольшую теорию БД с некоторыми практическими советами, рассматривая также ряд общих методов, которые часто пропускаются. В этой части мы закладываем основы для физической реализации нашего проекта, которой посвящена вторая половина книги, и при этом охватываем следующий материал: •
Глава 1 Введение в методологию БД — Как следует из названия, приведено краткое представление различных методологий БД, которые обычно используются для реализации полномасштабных систем БД (СУБД — систем управления БД), типа OLTP-БД (On-Line Transaction Processing — обработка транзакций в реальном масштабе времени), хранилищ данных, оперативных хранилищ данных и витрин данных.
•
Глава 2 Сбор информации для проекта БД — В этой главе мы даем краткий обзор процесса определения требований, которые нужны пользователям СУБД, рассматривая некоторые неясные моменты, где скрываются важные данные.
•
Глава 3 Фундаментальные концепции БД — Основы понимания концепций реляционной теории фундаментальны для процесса проектирования БД и рассматриваются здесь. Это обеспечит основу для разработки нашего проекта.
•
Глава 4 Сущности, атрибуты, отношения и бизнес-правила — В этой главе мы начнем процесс превращения информации, собранной в главе 2, в логический проект нашей реляционной БД, в частности, продумывая сущности, которые нам потребуются.
Часть 1 — Логическое проектирование •
Глава 5 Моделирование данных — Как только мы выделили объекты, нам нужен способ показать и распределить информацию между программистами и пользователями. Модель данных — наиболее эффективное средство для изображения информации проектируемой БД.
•
Глава 6 Методы нормализации — Нормализация — процесс использования информации, которую мы собрали в главе 2 и разработали в главе 4 и главе 5, и превращения ее в хорошо структурированный проект модели данных. В этой главе мы рассматриваем правила нормализации, которым должны следовать при проектировании хорошо структурированной модели нашей системы.
•
Глава 7 Расширенные темы нормализации — Эта глава пользуется предыдущей главой, расширяя основные методы нормализации за пределы приемов, знакомых большинству программистов. Таким образом, мы сможем точно отрегулировать наш логический проект, чтобы избежать, насколько возможно, любых аномалий данных.
•
Глава 8 Завершение фазы логического проектирования — Как только мы разработали "совершенную" БД, нам нужно вернуться к первоначальным описаниям, чтобы гарантировать, что данные, которые мы собираемся хранить, будут обеспечивать потребности пользователей. В этот момент многие программисты готовы приступить к кодированию, но важно завершить логическую фазу проектирования двойной проверкой нашей модели и ее документации, чтобы попробовать минимизировать уровень изменений, которые потребуются, когда мы перейдем к ее физической реализации.
Введение в методологию БД Введение Проектирование БД можно рассматривать как интересную смесь искусства и науки. Наука проектирования БД хорошо проработана, с рядом зрелых методов проектирования структур БД (типа нормализации), которые использовались в течение долгого времени. Фактически они почти древние в смысле компьютерной терминологии, и при проектировании вашей БД этим правилам не так уж трудно следовать. Как мы увидим, сравнительно просто перевести такое проектирование на создание таблиц SQL Server. Однако, если вы, конечно, не разрабатываете очень маленькую СУБД, это не завершение задачи. Искусство создания структуры БД проявляется тогда, когда мы должны обеспечить в наших решениях распределение, кластеризацию, избыточность, поддержку круглосуточного и ежедневного функционирования, использование процедур, триггеров, ограничений, целостности и т. д. Для физической реализации БД не существует специальных методов, и порой мудрено определить, какой метод является лучшим. В этой книге будут представлены хорошие методы, которые, наверное, помогут вам проектировать лучшие БД. Я стараюсь представить информацию таким образом, чтобы быть достаточно ясным для новичков, но в то же самое время полезным даже для наиболее закаленных профессионалов. Одну вещь нужно твердо понять, прежде чем мы пойдем далее. Проектирование и структура БД по-разному выглядят для тех, кто занимается настройкой и администрированием БД. Например, как системный архитектор, я редко формирую учетные записи пользователей, обеспечиваю резервирование, или даже формирую средства копирования или кластеризации и т. д. Это — работа администратора БД. Когда я работал для маленьких магазинчиков, эти типы задач были в моей области, но я никогда не чувствовал, что делал эту работу хорошо.
Глава 1 В данной книге мы глубоко рассмотрим вопросы, как проектировать, разрабатывать и реализовывать таблицы БД на SQL Server и сопутствующие им методы доступа; но мы не будем рассматривать детали физических аппаратных средств, системного программного обеспечения и другие аспекты СУБД.
История структур БД Структура БД значительно изменилась за последние несколько десятилетий. Первоначально не было никакого выбора структуры БД способами оптимизации для ограниченных аппаратных средств ЭВМ, на которых они размещались. В начале 1970-х гг. Е. Ф. Кодд, преподаватель математики, в то же время являясь исследователем фирмы IBM, представил новую концепцию, которая была предназначена навсегда изменить путь, которым будут размещаться данные. Его принципы, от которых пошла реляционная модель, опередили свое время. Многим программистам понравились его идеи, но они не могли реализовать их из-за ограничений аппаратных средств ЭВМ. Исследование Кодда реализуется в его трех правилах нормализации. Эти правила развивались, начиная с первоначальной работы Кодда, и мы объясним все общепринятые правила нормализации позже в главах 6 и 7. Хотя я и не буду непосредственно ссылаться на его работы, почти каждая частица теории реляционных БД построена на его классической статье "A Relational Model of Data for Large Shared Data Banks" (Реляционная модель данных для больших распределенных банков данных). Если вы могли прочитать эту статью или даже книгу К. Дж. Дейта "An Introduction to Database Systems" (Введение в СУБД), широко известную как техническая "библия", то, наверное, заметили, что они довольно сухие и академические. Я собираюсь представить важные идеи обычным способом, который является уместным для проектировщика БД и легким для его понимания. "A Relational Model of Data for Large Shared Data Banks" может быть найдена no адресу http://www1 .acm.org/classics/nov95/toc.html. Реляционная теория БД продолжала развиваться, и Е. Ф. Коддом и др. формулировались все более и более строгие правила относительно структуры БД. Эти правила очень ценны и сегодня широко используются, но реляционная модель практически не использовалась, пока не появилась теория. Мы подробно обсудим модель и ее преимущества позже, а пока только учтем, что нормализация требует увеличения числа таблиц БД, и любые совершенствования теории Кодда приводят к дальнейшему увеличению числа необходимых таблиц. Из-за того, что это предъявляет дополнительные требования к аппаратным средствам ЭВМ, обычным программистам БД было нелегко прочувствовать выгоды от более глубокой нормализации. Если добавить к этому факт, что большое число таблиц с большим числом связанных с этим соединений вызывают трудности с аппаратными средствами серверов и программным обеспечением 1980-х и 1990-х гг., то нетрудно понять, почему разработчики БД не могли должным образом структурировать свои БД как из-за технологических ограничений, так и от недостатка понимания. К счастью, последние достижения в технологии позволяют это пересмотреть. За последние десять лет аппаратные средства ЭВМ стремительно улучшались, улучшено и программное обеспечение серверов БД, используя алгоритмы, которые фактически были осязаемы в течение двадцати лет, но не использовались из-за ранее рассмотренных ограничений аппаратных средств. Оптимизация серверов БД для конкретного использования типа OLTP и OLAP (on-line analytical processing — аналитическая обработка в реальном масштабе времени), плюс тот факт, что Microsoft полностью переписал SQL Server, чтобы работать
8
Введение в методологию БД оптимально как с платформой Windows, так и с платформой Intel, в последние несколько лет вызвали исключительное улучшение характеристик и увеличение надежности. Эти факторы, наряду с усовершенствованием операционных систем — ОС (Operating System — OS) и развитием концепций типа организации хранилищ данных (рассмотренных позже в этой главе), позволили создать серверы БД, которые могут работать со структурированными данными надлежащим образом, как было определено тридцать лет назад. БД, построенные сегодня, разработаны с использованием лучших структур, но мы все еще плохо разрабатываем БД предыдущих лет. Даже с хорошим основным проектом, программирование БД может поставить под вопрос базу традиционного программирования типа языков С, C++ или Visual Basic. Это потому, что язык реляционного программирования SQL требует, чтобы программист заново продумал всю систему понятий: множества и отношения вместо операторов IF и циклов. Один оператор SQL при традиционном программировании может расшириться до сотен, если не тысяч, строк процедурного кода, и в некоторых случаях это так и есть. Этого не скажешь о Transact-SQL (T-SQL), языке SQL Server; он не поддерживает циклы и всякие другие основные конструкции языка программирования, но даже в T-SQL, чем большее количество работы отдается реляционному механизму, тем лучше. Другая особенность SQL, которую стоит упомянуть, состоит в том, что он является относительно легким при изучении основ, и как следствие этого, обычно не воспринимается так серьезно, как другие "настоящие" языки программирования. Однако программировать сложные вещи на SQL нелегко, и к нему не следует подходить несерьезно. В этой главе читателю представлены концепции, связанные с основными понятиями БД, в частности, различия между OLTP- и OLAP-БД; здесь также рассмотрены различия между SQL и T-SQL с одной стороны и процедурными языками программирования с другой, потому что в дальнейшем мы будем заниматься только разработкой БД.
OLTP и организация хранилища данных Об обработке транзакций в реальном масштабе времени (Online Transaction Processing — OLTP) в БД большинство людей, вероятно, думает, когда говорят о БД, что она имеет дело с текущими данными, необходимыми для бизнес-операций. Организация хранилища данных — интересное, относительно новое общее понятие БД, которое позволяет для чрезвычайно сложных отчетов строить систему исторических данных, выделенных из транзакций. Позволяя нам хранить большие количества исторических данных, оно дает возможность пользователям исследовать долговременные тенденции в истории бизнеса, в то же время избегая любых воздействий на текущую деятельность. Имеется огромное различие между этими двумя понятиями, которые могут быть получены в итоге следующим образом: •
Системы обработки транзакций в реальном масштабе времени (OLTP) работают с данными, которые используются в транзакциях. БД оптимизируется, чтобы быстро реагировать на добавление новых данных или корректировку/удаление существующих данных. Это делается так, чтобы реагировать даже тогда, когда количество данных очень большое, и имеется большое количество транзакций.
•
Хранилище данных получает результаты, которые OLTP использует при формировании отчетов. Системы OLTP всегда были вялы при формировании отчетов, так как они оптимизированы, чтобы иметь дело с постоянной корректировкой размещенных данных. Когда при формировании отчета мы хотим проверить данные
Глава 1 и произвести вычисления большого количества данных, это может серьезно ухудшить реакцию нашей БД. Кроме того, корректировка данных во время формирования отчета может привести к различным последствиям. Хранилища данных устраняют это, храня данные в форме, которая является эффективной для сложных запросов, — данные оптимизированы для определенного набора вопросов. Кроме того (это недавняя история размещения данных), вы используете для запроса не фактическую, текущую версию данных, а копию, используемую только для чтения. Это немного упрощено, но должно позволить вам эффективно представить различия между OLTP и организацией хранилища данных. В следующем разделе мы проанализируем различные модули, которые составляют каждую из этих технологий.
Четыре модуля современной СУБД За прошлые десять лет (или около того) было много обсуждений относительно проблемы, как общие данные должны быть структурированы. Мы рассмотрим один из основных вариантов, который был согласован со многими экспертами БД. Подход состоит в том, чтобы разбить всю систему на функциональные модули, которые обслуживают различные потребности, вместо использования единой технологии, чтобы реализовать все задачи. Эти различные модули следующие: •
OLTP — OLTP-БД хранит текущие данные — то есть, данные, с помощью которых БД должна управлять функционированием; здесь необходимо держать лишь небольшое количество предыстории.
•
Оперативное хранилище данных (Operational Data Store — ODS) — объединенные данные, используемые для ежедневных отчетов. Такие данные часто объединяются из нескольких несоизмеримых источников с некоторой степенью предварительной обработки, чтобы сэкономить время запроса.
•
Хранилище данных — хранилище большого объема данных для размещения почти всех структур данных и их предыстории.
•
Витрина данных (Data Mart) — специализированное хранилище данных, оптимизированное для объединений данных, используемых для определенных ситуаций, и представляющее собой подмножество хранилища данных. Витрины данных обычно обрабатываются, используя технологию, известную как аналитическая обработка в реальном масштабе времени (Online Analytical Processing — OLAP).
Рассмотрение их как модулей может показаться некорректным, но термин "модуль" используется здесь, чтобы указать, что они — отдельные части объединенной СУБД. Каждый модуль играет свою специфическую роль. По той или иной причине не всякая СУБД будет требовать всех модулей. Два основных фактора выбора необходимых модулей — количество данных, которые должны быть размещены, и параллелизм (который определяет, сколько пользователей будет обращаться к данным в любое конкретное время). Меньшие системы могут управлять формированием отчетов без создания дорогостоящих дополнительных модулей. Единственная проблема состоит в том, чтобы определить, что составляет меньшую систему? Глава 9 будет детально разбираться с этими проблемами. Посмотрим теперь несколько более пристально на эти четыре модуля, которые мы ввели выше.
10
Введение в методологию БД OLTP OLTP-БД содержит данные, используемые в процессе функционирования повседневных транзакций. Она описывается временными характеристиками, в которых отражается обработка потока, и служит источником, в котором находятся данные относительно предметной области. БД характеризуется наличием любого числа параллельных пользователей, создающих, обновляющих и удаляющих данные. Все общие данные должны быть размещены или уже были размещены (в случае исторических данных) в OLTP-БД. Структура размещения данных в OLTP строится, используя нормализацию, — специальный метод структурирования, упомянутый ранее. Нормализация уменьшает количество избыточных данных, помогая предотвратить неприятности при корректировке — наподобие того, что произошло бы, если бы вы хранили адрес заказчика в двух местах БД, а изменили бы его только в одном месте. Нормализация подробно рассмотрена в главах 6 и 7. Главная цель OLTP-БД — целостность текущих общих данных. Это достигается следующими двумя важными принципами: •
Хранить каждую текущую часть данных в единственном месте, где ее можно отредактировать так, чтобы любое изменение отражалось всюду, где она используется.
•
Обеспечение транзакционной поддержки так, чтобы многочисленные изменения БД происходили совместно. Если одно из изменений в транзакции не удается, ни одно из других изменений не должно произойти. Оставшаяся часть транзакции после этой ситуации должна реализовать откат.
Побочный эффект этого, однако, тот, что получение полезной информации может быть затруднено из-за строгой структуры данных. Поскольку OLTP-БД разработана с учетом характеристик транзакций и целостности, данные размещаются так, чтобы можно было их эффективно записывать, но не обязательно эффективно читать. Пользователь будет часто запрашивать многие таблицы, чтобы получить нужный набор информации. Эта книга нацелена на структуру и реализацию OLTP-БД. Для меньших (снова тот же термин!) систем вы можете использовать только этот тип БД. Как пример использования БД типа OLTP, рассмотрим банк, который имеет клиентов с различным количеством денег, размещенных на их счетах. Банк будет обычно иметь БД, которая содержит фамилии и контактную информацию всех клиентов. Банк также, вероятно, будет иметь много распределенных БД, чтобы обращаться ко всем различным типам счетов и денежно-кредитным сделкам (транзакциям), которые совершают его клиенты. Кажется, что лучше все эти данные разместить в той же самой БД, но так как банк имеет огромные количества данных относительно денежно-кредитных сделок, охватывающих различные географические пункты, штаты и даже различные страны наряду с большим количеством контактной информации относительно клиентов, более вероятно, что эти части будут размещены в отдельных БД. Банк может также иметь БД (или ту же самую БД, если она так организована) для будущей контактной информации о клиенте и т. д. Каждая из этих БД будет типа OLTP. Банковские системы находятся среди наиболее сложных OLTP-БД в сегодняшней глобальной экономике. Чтобы вам было легко использовать кредитную карточку где-нибудь в мире, без (по всей вероятности) возможности забрать большее количество денег, чем вы фактически имеете на счете, требуется массивная распределенная OLTP-СУБД, чтобы это реализовать.
11
Глава 1 Я должен разъяснить то, что я подразумеваю под термином "транзакция", прежде чем двинусь дальше. OLTP-БД имеют дело не только с денежно-кредитными или числовыми сделками. Термин "транзакция" означает механизм, с помощью которого вы можете гарантировать ссылочную целостность (другими словами, сохраняя определенные отношения между таблицами при вводе и удалении записи) и атомарность (концепция, означающая, что какая-то информация должна выступать как отдельная единица). Для БД это означает, что вы имеете известный набор результатов в зависимости от того, удается ли или нет какое-либо действие (или группа действий). Мы рассмотрим в главе 12 транзакции и то, как вам их использовать в коде БД. Наиболее важная вещь, которую следует понять, состоит в том, что одной из главных характеристик OLTP-БД является использование механизмов, предохраняющих данные от разрушения при любых действиях пользователя.
Оперативное хранилище данных (ODS) Идея ODS состоит в том, чтобы иметь БД, где все данные, которые вы используете в вашей деятельности, размещаются ежедневно. В зависимости от ваших требований здесь может быть также размещено ограниченное количество исторических данных. ODS разрабатывается так, чтобы разобраться и заняться некоторыми из проблем, связанными с концепцией OLTP, и которые могут быть подытожены следующим образом: •
OLTP-БД обычно имеют сложную структуру со многими таблицами. Структуры данных могут быть весьма тяжелы для понимания, и запрос информации может потребовать творческого использования SQL-языка. Исходя из моего опыта, новичок при написании запросов может загубить систему OLTP, написав неэффективные запросы из-за недостатка понимания внутренней работы SQL Server.
•
Многие OLTP-БД имеют большое количество детализирующих записей. Ежедневные действия, вероятно, не потребуют доступа к каждой сделке, выполненной в течение дня, но будет нужно, вероятно, иметь возможность получить итоговые данные. Если бы вы использовали многократные запросы в системе OLTP, все сделки должны были бы быть заново рассчитаны каждый раз, когда будет сделан запрос.
•
Не все данные размещаются в единственном источнике. Типичная организация может иметь данные, необходимые для обеспечения деятельности, размещенные во многих источниках данных. Многие, как нам бы и хотелось видеть, уже изменены, многие из общих данных все еще размещаются на универсальных ЭВМ в нереляционных БД, написанных на языке КОБОЛ.
В ODS данные объединены из всех несоизмеримых источников в общую структуру и суммированы в соответствии с предъявляемыми требованиями. Они могут обновляться так часто или нечасто, как требуется. Данные характеризуются небольшим количеством, если таковые вообще имеются, допустимых корректировок пользователем, с умеренным количеством поддерживаемой предыстории, чтобы формировать ежедневные запросы и показывать тенденции за короткие периоды. В сценарии банка, ODS вероятно имела бы все сделки в течение прошлого дня и, возможно, за прошлую неделю или месяц, размещенные таким образом, когда простой запрос выдаст любые ответы, которые вам понадобятся. Некоторые объединенные данные, если они часто требуются, также могут быть размещены. Например, можно было бы сохранять список счетов клиентов, где можно просмотреть текущий баланс в течение предыдущего дня. Этот вид данных тогда мог бы быть запрошен клиентом, чтобы посмотреть баланс в течение предыдущего дня, а также любые удаленные сделки. Следовательно, итоги всего счета не нужно получать каждый раз, когда клиент хочет видеть информацию о счете.
12
Введение в методологию БД Дополнительно могли бы быть посланы уведомления, основанные на данных, полученных в результате прокручивания балансов счетов. Хранение полученных итоговых данных не является требованием для ODS. Необходимым может быть только формирование набора таблиц, которые являются более простыми для запросов, так, чтобы пользователи могли выполнять нерегламентированные запросы. Отличный пример ODS — БД, помещенная в переносные компьютеры коммерческого штата, который находится в пути, или в карманных компьютерах для людей, которые находятся/работают вне офиса и не имеют постоянного рабочего стола. Следовательно, цель ODS может быть выполнена, обеспечивая пользователей рабочими данными, в которых они нуждаются для принятия ежедневных решений, и храня их актуальными за короткий период.
Хранилище данных Основное использование хранилища данных (Data Warehouse — DW) — поддержать принятие решения, храня столько исторической информации о структуре, сколько необходимо. Поддержка решения — довольно общий термин, который означает возможность ответить на сложные вопросы относительно того, как функционирует предметная область. Лучшие решения могут быть сделаны, когда доступно большее количество данных, нужных пользователю, которое можно просмотреть, получить их итог и уточнить. Надлежащая система поддержки решения может формировать "центр сведений" для организации. Например, если коммерческая группа в вашей компании была способна видеть коммерческие тенденции в течение десятилетнего периода, коррелированные с использованием потока рекламы в любое конкретное время, это будет конечно лучше, чем наличие тех же самых данных за один год. Или даже за месяц. Другая цель хранилища данных, как и в ODS, состоит в том, чтобы отделить активную транзакционную обработку от аспектов формирования отчетов системы БД так, чтобы мы могли делать более интенсивные запросы, которые не будут затрагивать возможность наших пользователей создавать данные и иметь к ним доступ в наших системах OLTP. Более старая копия данных из OLTP-БД размещается в хранилище данных. Частота обновления информации определяется количеством данных, потребностью пользователей, и временем, необходимым для выполнения копирования. Эти данные размещаются в виде, удобном для организации запросов, такая структура принципиально отличается от той, которая является удобной для корректировок. Никакая корректировка данных не должна никогда выполняться в хранилище данных; любые изменения должны быть сделаны в оперативной БД. Только во время, когда хранилище данных меняет свои данные, оно содержит наиболее современный набор информации, получаемой от оперативной БД. Следовательно, вы никогда не должны использовать эти данные в запросах, которые требуют современного точного ответа. Хранилище данных используется исключительно для ретроспективного анализа данных. Полное хранилище данных могло бы содержать данные за несколько лет от всех неоднородных источников в пределах организации, таких как наследство систем универсальных ЭВМ, БД на SQL Server и Oracle, и преобразовывать их в единственную БД, использующую общие структуры. Таким образом, ценные данные от наследуемых БД в организации могут быть объединены со всеми более новыми, хорошо структурированными БД (так же, как БД третьей стороны, которые используются для различных задач) в один общий набор структур, которые могут использоваться для получения информации, необходимой для принятия решений. Например, данные о людских ресурсах могли бы быть получены от одной сторонней системы, главная бухгалтерская книга — от другой, и маршрутизатор IP-адресов — от третьей. Все эти БД могут дать часть задачи, которая обеспечит ваших пользователей полной картиной, по которой они должны принять решение. 13
Глава 1 Как будет обсуждено позже, одним из наиболее важных вопросов, который вы должны рассмотреть, когда разрабатываете полную СУБД, является потенциальный диапазон запросов, которые могут потребоваться. В то время как на многие запросы можно отвечать непосредственно из OLTP-БД ("Каков текущий баланс счета клиента X?"), ряд из них придется направить в хранилище данных ("Какова средняя сумма дебета и кредита клиентов по банкоматам в течение прошлого года в каждом регионе страны?"). Хранилище данных — идеальное место для объединения данных, которые трудно объединить таким образом, как нужно вашим пользователям. Хранилище данных — огромное достижение — формируется не просто. Его не нужно рассматривать лишь как место быстрого формирования ответов на запросы. Вы будете, вероятно, желать вводить данные из многих несоизмеримых источников, некоторые из них могут изменяться, поскольку проходит время, особенно, когда данные поступают от третьих лиц (новый продавец для кадровой БД, новая версия другой системы и, что я предпочитаю, системы от компаний, которые ваша собственная компания только что приобрела — вот всего лишь несколько примеров). Так как хранилище данных банка, по-видимому, будет огромно, его, вероятно, будет тяжело использовать персоналом, осуществляющим банковские реализации и маркетинг. Им понадобились бы данные, чтобы ответить на вопросы типа "Какие из их программ работают лучше всего, когда ..." и "Какой вид программ выбрать, когда...". Страховой штат, вероятно, смотрел бы на данные с точки зрения тенденций скоростей работы с заложенным имуществом. Наличие такого обширного набора информации в БД делает новые технологии, заложенные в SQL Server, очень важными для организации хранилищ данных.
Витрины данных Витрина данных — характерная часть хранилища данных и обычно предназначена для определенных потребностей подразделения или отдела, или для определенной цели бизнеса в пределах организации. Она строится, используя специальные структуры БД, известные как схемы "Звезда" или "Снежинка". Схемы "Звезда" — фактически простые БД, с единственной таблицей фактов (таблица, содержащая информацию, которая может быть подытожена, чтобы получить детали относительно истории действий организации), связанной с набором таблиц измерений, которые классифицируют факты в таблице фактов. Следует заметить, что данные в таблице фактов, прежде всего, числовые. Схемы "Снежинка" — просто расширение схем "Звезда", где таблицы фактов могут также быть и таблицами измерений. Другой важный термин, используемый для витрин данных, который мы должны ввести, — кубы. Куб — еще один довольно странный термин, но он описывает, как OLAP-технология организует таблицы измерений нашей схемы "Звезда" или "Снежинка". Измерения таблицы фактов описываются измерениями куба, в то время как каждый элемент данных в кубе представляет факт, содержащий конкретный уровень для различных измерений куба. Рассмотрим следующие три запроса: •
Сколько всего посетителей было на нашем Web-сайте в 2000 г.?
•
Сколько всего посетителей было на нашем Web-сайте в 2000 г. и с какими разделами они ознакомились?
•
Какие категории посетителей были на нашем Web-сайте в 2000 г. и с какими разделами они ознакомились?
14
и
Введение в методологию БД Каждый из этих запросов будет использовать различное число измерений, чтобы решить проблему. Наша таблица фактов тогда содержала бы числовые индексы пользователей на нижнем уровне, а таблицы измерений будут использоваться, чтобы группировать слой данных в соответствии с потребностями пользователя. Механизмы организации куба могут использоваться, чтобы предварительно объединить ответы на части или все запросы, которые может сформировать пользователь, в группы, чтобы сделать ответ на запрос пользователя более быстрым. Более глубокое объяснение этих структур данных выходит за границы возможностей этой книги. Подробнее все рассмотрено в "Professional SQL Server 2000 " (Wrox Press, ISBN 1861004486) и "Professional Data Warehousing with SQL-Server 7.0 and OLAP Services" (Wrox Press, ISBN 1861002815). Витрины данных могут использовать SQL Server (хотя и не обязаны), чтобы размещать свои данные, но к данным не обязательно обращаться, используя классические команды SQL (хотя, когда SQL Server используется, чтобы размещать многомерные данные, это можно использовать). Витрины данных обычно используют OLAP-серверы, чтобы выполнить большую часть агрегатирования до того, как пользователь запросит данные. Существует SQL-подобный язык доступа к данным в витрине данных (Multidimensional Expressions — многомерные выражения — или MDX), хотя технически он непосредственно и не работает с данными из витрины данных. Однако обычно к данным обращаются, используя средства без использования кодирования. Подходящими средствами являются Microsoft Excel, Microsoft Access, Cognos Impromptu, а также Lotus — обращение по имени, которое используется редко. Данные в витринах данных — только для чтения по определению (конечно, исключая процессы, которые периодически освежают их). Главная цель витрины данных состоит в том, чтобы показать тенденции, региональные или сгруппированные по какому-то другому принципу данные, которые пользователь может пожелать, выделяя содержание массивного хранилища данных в небольшие сегменты, к которым проще обращаться и управлять ими. Каждая витрина данных будет структурирована таким образом, чтобы решать довольно специфический ряд задач. Рассмотрим пример электронного продавца книг. Витрина данных, которую его отдел маркетинга без сомнения хотел бы иметь, будет содержать информацию о проданных произведениях. Главная таблица фактов содержала бы числовые коммерческие показатели, типа количества проданных изданий и их цены. Тогда измерения были бы смоделированы для дат продаж; каким образом пользователи на абонентском пункте получают информацию (или они ищут непосредственно издание, или осуществляют общий поиск автора, или мы побуждаем их сделать покупку, высвечивая информацию на экране); информацию о клиентах (откуда они, какими видами личной информации они обладают) и, наконец, произведений, которые были куплены. Из этой информации мы можем, например, увидеть, что около четверти продаж были сделаны людьми в Топека, или получить любую другую комбинацию измерений; таким образом строятся неопределенные или, наоборот, очень конкретные запросы, чтобы получить ответ. В случае наших OLTP-БД, это потребовало бы сотен строк кода SQL (и я могу подтвердить это по болезненным личным воспоминаниям). В банковском примере архитекторы данных (или, естественно, конечные пользователи) могут создавать определенные витрины данных для различных пользователей хранилища данных. Например, пользователь маркетинга может иметь витрину данных с банковскими клиентами, определяемыми уровнем доходов, регионом страны, количеством оплат, величиной капитала и текущей информацией. Исходя из этого могут быть сформулированы запросы: показать клиентов для каждого региона, с каждым уровнем дохода, кто выполнил свои платежи своевременно, с каким уровнем доходов. Это могла бы быть очень мощная информация при определении новых условий специальных тарифов в различных частях страны. 2 1868
15
Глава 1
Структура Полная структура окончательного выбора БД зависит от масштаба проблемы, которую вы пробуете решать. В следующей диаграмме мы берем типичную большую систему и делим ее на части, чтобы видеть поток данных через всю БД.
Все еще используемая наследуемая БД1
Все еще используемая наследуемая ^ БД2
Витрина данных
Все еще используемая наследуемая БДЗ
/
/ Оперативное хранилище данных
Витрина данных
Витрина данных
Вы можете заметить, что имеется много витрин данных, рассеянных по всей диаграмме. Витрины данных проще всего строить на основе хранилища данных, так как хранилище данных уже имеет историческую информацию, но они могут быть построены в любом месте по пути следования информации. Обратите внимание, что мы берем наши исходные данные в наследуемых БД и OLTP-БД и преобразовываем их для использования в ODS. На самом деле, мы делаем примерно ту же самую вещь на каждой ступеньке процесса, от ODS до хранилища данных, и на витринах данных. Вообще-то, как только вы преобразовали данные в ODS-подобный формат, вам не нужно будет делать слишком много изменений, чтобы получить данные в формате хранилища данных. Фактические сценарии могут изменяться от ситуации к ситуации.
Учебный пример На протяжении книги мы будем приводить пример процесса разработки БД. Он охватит все аспекты проектирования, от начальных его идей, через интервьюирование клиентов, моделирование БД, создание ее, используя SQL Server, выбор аппаратных средств и организацию доступа к ней. Темой примера будет персональный реестр текущего счета. В то время как фактическое количество данных, которое будет размещено в окончательных БД (которые расположены на Web-сайте фирмы Wrox по адресу www.wrox.com), будет намного меньше, чем могло бы обычно требоваться для развитого хранилища данных, это — просто пример с легко управляемым размером.
16
Введение в методологию БД Выбор текущего реестра дает достаточно свободы действий, чтобы показать многие из различных типов отношений, структурных проблем, и т. д., с которыми приходится сталкиваться при создании реальной БД. В проект может быть включен некоторый уровень многопользовательского функционирования при наличии интерфейса для многих пользователей текущего счета, чтобы войти в дебеты и кредиты в одно и то же время. Это позволит нам рассмотреть проектирование и реализацию ODS БД, использование утилит SQL Server, чтобы выполнить задачи извлечения данных из OLTP-БД, преобразование данных в различные формы, и загрузку их в витрину данных (обычно называемые какЕТЪ — Extract, Transform, and Load — извлечение, преобразование и загрузка).
Реляционное программирование по сравнению с процедурным программированием Система понятий реляционного программирования настолько отличается от процедурного программирования, что может вызвать много огорчений, если эти различия будут неправильно истолкованы. Реляционное программирование — это все, что связано с определением того, что вы хотите, и затем запуск реляционного механизма для выполнения всей работы, а процедурное программирование — все, что связано с эффективным выполнением циклов. Первичный реляционный язык (и единственный, который мы будем рассматривать с этого времени) — SQL. Предполагается, что вы, читатель, имеете опыт в использовании SQL, поскольку мы будем представлять все наши примеры в T-SQL — языке Microsoft SQL. Пока существуют различные мнения относительно глубины различий между этими двумя принципами, различные методологии, которые каждый из них влечет за собой, означают, что нужно быть осторожными при определении того, что наиболее соответствует имеющейся задаче. В главе 3 мы рассмотрим основы реляционной теории более глубоко и выясним, как она связана с SQL.
Организация циклов При процедурном программировании основной путь доступа к данным — организация циклов с массивами данных. Мы можем представить программу, написанную так, что программист выполняет оператор SELECT, чтобы заполнить массив данных из одной таблицы (назовем ее outerTable), и затем выполняет оператор SELECT, чтобы получить массив данных из другой таблицы (назовем ее innerTable). Тогда он может найти нужную информацию, используя два вложенных цикла следующим образом:
I
For Each row In outerTable For Each row In innerTable Next row In innerTable / Next row In outerTable
Этот код — то, что SQL Server делает за сценой, когда он запрашивает две таблицы. Но это чрезвычайно плохая практика программирования — писать программу подобно этому, поскольку следующий код SQL исполняет тот же самый запрос.
I
SELECT * FROM innerTable JOIN outerTable ON innerTable.key = outerTable.key
17
Глава 1 SQL Server оптимизирован на выполнение этого действия, и сервер имеет более высокую производительность, чем клиент. Пока это — простой пример кода, те же самые принципы используются при выполнении сложных запросов — всегда лучше передать серверу столько работы, сколько возможно. Это называется соединением таблиц. Результирующий набор данных, возвращенный запросом, может быть далее отфильтрован и очищен путем изменения параметров выборки. Важно учиться передавать в SQL Server работу в максимально возможной степени, так как он непосредственно предназначен для целей управления данными. Это особенно важно, потому что некоторые запросы в хорошо структурированной базе данных могут включать десять, двадцать, или даже большее количество таблиц. Чем больше количество используемых таблиц, тем труднее это осуществить с помощью SQL без использования соединений (поскольку соединения представляют отношение таблиц). В главах 10, 11 и 12 мы будем использовать SQL несколько глубже, поскольку рассмотрим, как формировать и организовывать доступ к нашим данным в БД, которые мы создадим. Эта книга, конечно, не руководство по SQL; она в первую очередь рассматривает, как использовать операторы SQL в проекте базы данных. Хорошее понимание SQL-языка существенно, чтобы правильно управлять данными в реляционной БД, и это позволит разработать программное обеспечение для доступа к таким данным. Процедурные программисты, которые игнорируют это, просто не смогут разрабатывать хорошо работающие приложения. Так же, как вы используете молоток, чтобы забить гвоздь, и пилу, чтобы распилить древесину, вы должны использовать все инструменты, имеющиеся в вашем распоряжении (включая и процедурные, и реляционные языки), чтобы хорошо выполнить работу. Для дальнейшего чтения на эту тему используйте следующие книги: •
Instant SQL Programming (Wrox Press ISBN
•
Professional SQL Server 2000 Programming (Wrox Press ISBN
•
The Essence of SQL: a Guide to Learning Most of SQL in the Least Amount of Time, (Peer to Peer Communications, ISBN 0964981211)
1874416508) 1861004486)
Доступ к сети Одна из первопричин, по которой вы должны создать надлежащий код SQL, — доступ к сети. Приложения SQL БД, как правило, распределены между двумя или большим числом компьютеров. В настоящее время довольно редко бывает единственный пользователь SQL БД, который работает на том же самом компьютере, на котором расположены и данные. Хотя это не всегда так (с SQL Server 2000, обладающим Microsoft Data Engine и версией СЕ, позволяющей создавать системы с единственным пользователем), наша книга будет сконцентрирована на многопользовательских системах, которые включают два или больше компьютеров. Однако все примеры в книге могут быть реализованы и проверены на любой версии SQL Server. Чтобы реализовать пример организации цикла из предыдущего раздела, каждая строка в i n n e r T a b l e и o u t e r T a b l e должна быть восстановлена через сетевую связь с клиентом. Если таблица большая, это было бы* вероятно, очень медленное действие, даже на относительно быстрой сети. Эффективность, полученная за счет использования ресурсов процессора сервера, позволяет многим пользователям осуществлять доступ к данным более быстро, чем в случае, если работа выполнялась бы на машинах клиента, не говоря уж о том, что количество данных, которые должны быть переданы по сети, будет меньше, минимизируя величину скорости доступа сети. Хотя хорошо разработанные системы централизуют обработку, вы часто видите, что плохо реализованные приложения пытаются делать работу, которую должен делать механизм БД. 18
Введение в методологию БД
Краткий обзор процесса проектирования БД на основе SQL Server Процесс проектирования надлежащей СУБД имеет несколько моментов, общих с проектированием эффективных компьютерных систем любого назначения. • • •
Это — относительно прямая задача. Она может потребовать очень больших временных затрат. Она редко получается той мощности, которая требуется.
Как мы будем много раз повторять на протяжении всей книги, БД занимают центральное место среди компьютерных систем, которые создаются сегодня. Даже в системах, которые не предназначены для БД, обычно требуется какая-либо форма хранения данных. В этой книге мы концентрируемся прежде всего на больших системах, которые имеют БД в качестве центральной системы. Примеры таких БД — вокруг нас: в Интернете, в наших банках, правительстве, компаниях, магазинах бакалеи, аптеках, и т. д. Процесс проектирования таких БД можно разбить на несколько шагов, которым мы будем следовать в этой книге: •
•
•
•
•
Определение цели — не смейтесь над этим, считая все слишком очевидным: большинство проектов портятся разработчиками, не имеющими никакой реальной идеи того, что пользователь фактически хочет или в чем нуждается, потому что они перескакивают с одного заключения на другое или не в состоянии слушать правильных доводов пользователей, или в некоторых случаях любых пользователей вообще. В течение этой стадии мы будем определять функциональные требования, требования к рабочим характеристикам и отчетам для окончательной системы, которую мы будем создавать. Логический проект — процесс проектирования логического пути достижения целей, которые формируют в реализации независимый путь. Одна из главных причин того, что не придется часто ссылаться на SQL Server в первой половине этой книги, связана с тем, что логический проект БД независим от физического проекта БД и ее реализации. Физический проект — на этом этапе берется логический проект и приспосабливается к конкретной реализации. Эта стадия проекта связана с определением того, как СУБД будет физически реализована с использованием любых доступных аппаратных средств и программного обеспечения ЭВМ. Физическая реализация — стадия реализации проекта связана с фактическим размещением физических данных на серверах БД и разработкой кода для доступа к данным. Обзор — процесс оценки, были ли достигнуты цели. Печально, но это наиболее часто пропускаемая стадия проектирования, потому что она требует так досадно много времени, и это никакая не забава вообще: тестирование, документация и все другие вещи, которые вызывают в нас крайне неприятные чувства при их выполнении, но мы должны это делать. Это должно включать механизм использования обратной связи от пользователя и рассмотрение плана обслуживания, связанного с исправлением любых идентифицированных проблем.
В этой книге первые два шага рассмотрены в части "Логическое проектирование", а остальные — в части "Физическое проектирование и реализация". 19
Глава 1
Резюме В этой главе мы представили основы современных СУБД, особенно OLTP и методологию организации хранилища данных. Мы установили, что имеются четыре основных части в СУБД предприятия. Они могут быть представлены как: •
OLTP (Система обработки транзакций в реальном масштабе времени) для размещения наших текущих данных лишь с ограниченным количеством исторических данных, сохраненных, чтобы принимать самые последние решения.
•
ODS (Оперативное хранилище данных) для создания механизма хранения данных, необходимых для принятия ежедневных бизнес-решений, не прерывая пользователей в OLTP-БД.
Q
DW (Хранилище данных) — не следует путать с общим термином организации хранилища данных; DW используется, чтобы хранить массивные исторические величины, что позволит нам обслуживать объединенную БД, включающую многие корпоративные БД и видеть тенденции за длительные промежутки времени.
•
Витрины данных — часто путаются с OLAP или кубами (которые являются технологиями, используемыми в запросах на поиск данных в витрине данных); витрина данных используется, чтобы получить срез хранилища данных или, в его отсутствии, OLTP или ODS данных, и позволяет пользователю рассматривать объединенные данные в гибкой форме.
Наша книга будет нацелена на рассмотрение данных, особенно данных в OLTP-БД. Данные и, следовательно, OLTP-БД, являются единственной, наиболее важной частью любого проекта. Некоторые могли бы аргументировать другое: что, например, более важен надлежащий проект или даже логическая природа информации (потому что она моделирует сферу деятельности предприятия). Однако очень немного пользователей интересуется природой данных, или же интерфейсом, который они используют для доступа к ним, если они могут просматривать и управлять данными, как им нужно. Имеется так много устаревших систем, неуклюжих в работе, плохо продуманных, и все же они продолжают и продолжают использоваться. Процессы изменяются, так же, как и идеи, но запасенные данные будут жить намного дольше, чем любой интерфейс или даже процесс. Одна из главных задач администраторов БД — всюду, где это требуется, преобразовывать данные из одной системы в другую. Независимо от того, как меняется структура БД от версии к версии, данные будут все же использоваться снова и снова. Если вы когда-либо что-нибудь программировали, вы, несомненно, не согласитесь с некоторыми из мнений/идей в этой книге. Я полностью принимаю, что эта книга — не евангелие от святого Луки для БД. Мои идеи и мнения сформировались в течение десяти лет работы и изучения БД в дополнение к точкам зрения многих совершенно разных людей, книг, курсов колледжа, и семинаров. Методология проектирования, представленная в этой книге — сумма этих идей, и я очень благодарен другим людям, которых я могу вспомнить. Я надеюсь, что это доказывает пользу обучения, и что через чтение работ других людей и испытания ваших собственных идей вы сформируете собственную методологию, которая удовлетворит вас и сделает вас успешным проектировщиком БД.
20
Сбор информации для проекта БД В этой главе мы вернемся к исходным принципам и обсудим самые первые шаги, которые нужно предпринять, начиная проектирование БД. Получение предварительной информации для нового проекта БД — типа решения, что мы собираемся хранить, — является одной из наиболее важных задач, которую вы должны будете решить, хотя могут быть ограничения во времени и материальных ресурсах, которые подразумевают, что этот процесс не будет выполнен настолько полно, насколько это нужно. Построение вашей БД без адекватного предварительного анализа может быть уподоблено строительству дома на песке, где после того, как будет построен нулевой цикл, пески сдвинутся, и вы должны будете все начать снова. Такие ситуации часто возникают, когда после того, как БД создана и развернута, оказывается, что необходимо включить в БД пропущенную особенность, что потребует перепроектирования ее с самого начала. Надлежащий анализ — твердая основа того, что будет создан удачный проект без шероховатостей. При сборе информации для проекта БД вы должны избежать искушения начать выбирать в это время какую-либо структуру. Не определяйте на этой стадии таблицы, поля и т. д., даже если вы искушенный проектировщик БД. Наивно пытаться моделировать процесс, выбирая единственный путь, пока вы не проконсультировались со всеми сторонами, вовлеченными в проект, выслушали их идеи и потребности. Слишком часто мы начинаем формировать структуру БД прежде, чем получим достаточные знания о задаче, и это не помогает ни нашим заказчикам, ни нам самим. Эта глава помещена до рассмотрения моделирования данных, чтобы подчеркнуть данный момент.
Глава 2 В процессе проектирования БД обрабатывается много важной информации. Эта информация будет полезна для других членов команды проектировщиков теперь и в будущем, чтобы понимать и поддерживать систему. При этом должна быть рассмотрена формальная стратегия ведения документации.
Команда проектировщиков БД Чтобы начать процесс сбора информации, должна быть организована команда проектировщиков БД. Хотя команда и может состоять только из единственного человека (что, вероятно, будет в маленьких организациях или проектах), имеются, по крайней мере, четыре роли, которые должны быть заполнены, потому что каждая предназначена для достижения специфичной, существенной цели: •
Деловой аналитик излагает в деталях требования бизнеса пользователей и обеспечивает команду проектировщиков функциональным описанием, которое они перерабатывают в техническое описание. Он также действует как защитник интересов пользователя, убеждаясь, что окончательное проектное решение удовлетворяет указанным деловым потребностям клиента. Деловой аналитик также убеждается, что все контракты, соглашения, и т. д. выполнены.
•
Архитектор данных берет функциональное описание и разрабатывает техническую реализацию описания. (Системный архитектор также помогает в этой задаче.) Архитектор данных проектирует все размещение данных и структуру доступа к ним, а также выбирает надлежащие технологии хранения, основанные на том, что необходимо пользователю. В книге рассматриваются только обязанности этой роли. Обратите внимание, что архитектор данных — не то же самое, что администратор БД (Database administrator — DBA), который связан с реализацией и имеет дело с аппаратными средствами ЭВМ, программным обеспечением и моментами, обеспечивающими гладкую работу. Архитектор данных занимается вопросом, каким образом СУБД, реляционные или другие, объединить вместе и структурировать.
•
Системный архитектор отвечает за проектирование полного интерфейса пользователя и промежуточных объектов бизнеса, которые требуются в проекте, и отвечает за выбор внешнего интерфейса и промежуточных технологий. Есть определенное различие между этой ролью и архитектором данных — последний является ответственным исключительно за БД проекта, в то время как системный архитектор имеет дело со всем остальным, хотя, вероятно, могут быть и некоторые накладки между этими ролями.
•
Менеджер проекта — "босс", кто прежде всего отвечает за то, как каждый другой член команды делает свою работу, и занимается планированием.
Каждая из этих ролей будет существовать и в процессе проектирования, и в процессе реализации. Имеются другие важные роли, которые также внесут свой вклад, например, спонсор проекта или представитель клиента, кто заинтересован в окончательном проекте и обеспечивает финансы для развития, испытаний, документации и т. д. Однако четыре роли, внесенные в список выше, — основная группа, а определять другие или рассматривать большее количество деталей — вне возможностей этой книги. Причина определения этих ролей вообще состоит в том, чтобы показать, что если различные люди исполняют каждый свою работу, то архитектор данных может сосредоточиться почти исключительно на том, как размещать данные.
22
Сбор информации для проекта БД
Документация и связь Во время процесса анализа имеется одна хорошая привычка, которую вы должны принять — документировать всю информацию, которую вы получаете. Предположим, что вы можете завтра попасть под автобус. Мягче эту мысль можно выразить так: "Если со мной что-то случится, кто-то другой будет должен заняться моей работой". Другой пример— в нашей промышленности становится все более трудно ориентироваться только на лучших служащих. Если кто-то оставляет проект, потому что конкурент предлагает ему необоснованно огромное жалованье, замена будет связана с крутой кривой обучения, чтобы набрать темп, и единственный способ помочь этому — документировать всю информацию. Таким образом, вы должны документировать, документировать, документировать! Ни в коем случае нельзя все держать в вашей голове. Поскольку вы начинаете собирать замечания относительно потребностей пользователей, полезны следующие руководящие принципы: •
Поддерживать набор документов, включающих информацию о проекте системы и спецификации. Важные документы, которые следует иметь, включают: выявленные замечания к проекту, документы, описывающие устные запросы по изменениям, и записи по всем спецификациям, таким как функциональная, техническая, испытаний и т. д.
•
Помимо формальной документации, важно сообщать членам вашего коллектива проектировщиков новейшую и полную информацию. Разработайте и поддерживайте общее хранилище для всей информации.
•
Используйте минуты встреч и храните записи каждого предложения, запроса, или идеи, о которых говорили ваши заказчики.
•
Записывайте все, что пользователям не нравится.
•
Составьте перечень исходных границ проекта и постоянно помните о них. Это предотвратит от создания чрезмерно большого проекта или не того, который нужен.
Одной из первых работ команды проектировщиков должно быть определение потребностей (формулировка цели или характеристики цели), которые описывают параметры проекта. Они будут учитываться и с ними будет происходить сравнение в течение проектирования и реализации, а также после завершения работы. Если, однако, характеристики проекта и цели четко не заданы на стадии определения потребностей, или ничто не записано, то имеется большой шанс, что будут конфликты между вами и вашими клиентами, когда ваши идеи их не устраивают. Такая неопределенность или нерешительность могла бы вызвать в процессе проектирования ненужные обсуждения, стычки, или даже судебные процессы. Так удостоверьтесь, что ваши клиенты понимают то, что вы собираетесь сделать для них, и используйте язык, который будет правильно понят, но достаточно определенный, чтобы описать то, что вы изучите в процессе сбора информации.
Одобрение клиента Поскольку вы выполняете полный процесс проектирования БД, клиент, без сомнения, изменит свое мнение об именах полей, определениях полей, бизнес-правилах, интерфейсе пользователя, цветах — почти обо всем, о чем сможет, — и вы должны быть готовы к этому. Независимо от того, что клиент захочет или потребует сделать, вы будете должны пытаться это выполнить. Клиент является окончательным контролером проекта, и вы должны быть достаточно гибки при обращении с любыми предложенными изменениями, как незначительными, так и существенными. 23
Глава 2 После каждой встречи суммируйте ваши замечания на языке, который все участники могут понимать, и посылайте им копии. Держите папку со всеми ответами, которые вы получаете, и пересылайте их менеджеру проекта. В любой момент клиент может сказать вам "Я никогда не говорил это". Если нет никакого документа, подтверждающего то, что вы говорите, то это может привести к неприятностям. Так что я снова повторю — храните документацию, и если вы должны принять решение, которое не понравится клиенту, то должны будете иметь документ, дублирующий ваше решение. Лучший способ избежать конфликта, если клиенты будут изменять свое мнение и "характеристики поползут", — удостовериться, что вы получаете одобрение вашего клиента на всех стадиях процесса проектирования.
Минимальные информационные требования Независимо от того, являетесь ли вы командой из одного человека или винтиком в ударной силе проектировщиков из пятидесяти человек, имеется набор основной информации, которую команда проектировщиков должна собрать в течение ранних стадий, если они собираются продолжить процесс проектирования. Для начала вам потребуется груда записей, распечаток, изображений экрана, CD-ROM, загруженных крупноформатными таблицами, копий БД, документов в редакторе Word, документов электронной почты, рукописных материалов и т. д. Никакая структура пока не нужна для этой начальной информации, и было бы, вероятно, лучше в этот момент вообще не иметь никакой структуры, хотя это, конечно, вопрос вкуса. Некоторые утверждают, что вы не должны только собирать информацию, чтобы идти дальше, а лучше быть организованным с самого начала, чтобы после завершения информационного поиска вы могли бы задавать все более и более интеллектуальные вопросы. Причина того, что я не верю в эффективность слишком большого количества структур, заключается в том, что в этом случае я могу придавать вес собранной информации. Реальное упорядочивание должно произойти, когда вы начнете анализировать собранную информацию, что мы обсудим позже. Взглянем на начальные стадии процесса проектирования и источники этой исходной информации.
Опытные образцы БД Опытные образцы полезны при разработке крупномасштабных систем, которые оправдывают этот начальный шаг. Их целью должно быть "доказательство концепции" — возможность команды проектировщиков и пользователей конкретизировать критические элементы проекта, от которых будет зависеть успех или неудача. Иногда в качестве архитектора данных вы будете пытаться использовать опытный образец БД, который был торопливо разработан, и "заставить его работать" или, еще хуже, "отполировать БД". Действительно, вы можете унаследовать неструктурированный, неорганизованный опытный образец, и ваша задача будет состоять в том, чтобы на его основе создать промышленную БД. Действительно, ужасные слова!
24 иi
Сбор информации для проекта БД Имейте в виду, что опытные образцы должны рассматриваться лишь как диалоговые картинки, чтобы заставить заказчика подписать контракт с вашей компанией. Снова и снова нанимаются консультанты, чтобы разработать опытный образец, который выглядит настолько хорошим, что кажется, что он готов для развертывания на предприятии. Многие скажут вам "Это напоминает то, что нужно, так почему его выбрасывать?" Главная причина в том, что при создании опытного образца вы комбинировали, быстро стряпали и слепили код, разрабатывая идеи неорганизованным образом. Надлежащая структура и предусмотрительность при этом проигнорированы. Как только молниеносная разработка опытного образца выполнена, вы имеете пользовательский интерфейс и функциональную БД, но очень мало мысли, или просто никакой, которая была вложена в его структуру, хотя все и выглядит симпатично. Лучше, как только заказчик распишется, начать на пустом месте; разрабатывая окончательное приложение, используя структурированные и поддерживаемые стандарты кодирования. Очень важно, чтобы как архитектор данных вы работали настолько твердо, насколько возможно, чтобы использовать код опытного образца только как рабочую модель, как часть документации, которую вы используете, чтобы улучшить ваш собственный проект. Это поможет убедиться, что вы не пропустите какую-либо критическую часть информации, которая нужна пользователям, типа названия поля, операции поиска или даже кнопки (которая вызывает элемент данных), но не говорит вам ничего относительно структурных проблем. Работайте с этими проблемами сами. При проектировании БД предприятия не может быть никаких коротких путей.
Интервью с клиентами В большом бизнесе, корпоративном мире маловероятно, что архитектор данных будет встречаться с пользователем, не говоря уж о взятии у него интервью. Менеджер проекта, деловой аналитик и системный архитектор могли бы дать всю информацию, которая ему/ей потребуется. Однако бывают времена, когда архитектор данных фактически вовлекается в процесс интервью в зависимости от структуры команды проектировщиков. В моем опыте консультанта я был в роли фактического участника интервью с клиентом (совещания по проекту). При случае, я был должен брать интервью у интервьюера, чтобы выяснить противоречия! Клиента интервьюируют, когда проектирование БД действительно начинается. Однако многие клиенты обычно мыслят визуально; они думают, в частности, в терминах форм, Web-страниц и простых пользовательских интерфейсов. Во многих случаях ваши клиенты не будут иметь абсолютно никакого понимания относительно того, как система создана. Ваша работа в качестве архитектора данных, связанная с интервьюированием клиентов, должна сводиться к сопоставлению их действительных потребностей с тем, как они сами их трактуют - это касается должным образом структурированной БД, скрытой за пользовательским интерфейсом. Изменение всех форм при включении нового текстового окна, метки или чего-нибудь еще является относительно простой задачей, давая пользователю ложное впечатление, что создание приложения БД является легким процессом. Если вы хотите подтверждения, покажите пользователю почти законченный опытный образец приложения без поддержки БД. Клиенты могут быть поражены, что вы собрали кое-что так быстро, но когда запустят его и будут наблюдать, то споткнутся на этом. Редко кому будет понятно, что за фасадом существует что-то еще, а именно, БД и объекты бизнеса промежуточного уровня — то, где вся главная работа и выполняется. Исчерпывающее рассмотрение методов интервью — вне возможностей этой книги, но имеется несколько ключевых моментов, которые должны быть упомянуты. Во-первых, слово "интервью" используется вместо слова "опрос". Первое предполагает обмен идеями один на 25
Глава 2
один, в то время как второе подразумевает навязывание ваших идей оплачивающему заказчику. Если вы произведете впечатление властного человека и попытаетесь внушить заказчику, что он/она хочет, вы испортите ваши отношения прямо с самого начала. Будьте тактичны и не создавайте представление, что вы знаете больше, чем заказчик. Пробуйте установить основную тему для интервью так, чтобы были охвачены важные области, по которым вы имеете открытое мнение. В качестве затравки вам будет полезен один простой вопрос: "Что вы хотите от этого проекта?" Это позволит вашим интервьюируемым рассказать, что они хотят от проекта. Попросите их объяснить свои идеи относительно того, как они достигли бы этого. Это даст вам ясное представление того, что они хотят и почему, предотвращая ваши исходные идеи от изменения, когда начнете заниматься проблемами, которые будете решать для клиента. Будьте готовы слушать, делать записи, задавать вопросы, получать разъяснения — и делайте как можно больше записей. Удостоверьтесь, что вы обращаетесь с каждым человеком, у которого берете интервью относительно проекта, как с индивидуумом. Каждый человек будет, вероятно, иметь свою, отличную от других точку зрения. Не думайте, что первый человек, с которым вы говорите, может сказать за остальных, даже если все они занимаются одним и тем же, или если этот индивидуум — менеджер. Разговоры "один на один" позволяют клиентам высказывать мнение без неуклюжих прерываний со стороны коллег. Помните тот факт, что самые громкие и самые смелые люди могут и не иметь лучших идей, чем у тихого человека, который сидит сзади и ничего не говорит такого, что может быть ключом ко всему проекту. Удостоверьтесь, что вы получаете общие мнения. Полезна техника, когда вы говорите клиенту, что понимаете то, что он сообщил, повторяя во время встречи наиболее важные пункты. Это также полезно для разъяснения неясных моментов и помогает в процессе формирования заметок. Аудио- или видеозаписи встреч иногда используются, но это может показаться агрессивным и создать дискомфорт клиентам, так что они должны использоваться только в ситуациях, где это действительно необходимо из-за предыдущих проблем обмена информацией. Никогда не делайте запись беседы без согласия интервьюируемых. Если можно, возьмите на встречи человека, который не имеет никаких обязанностей, кроме ведения записей. Как архитектор данных, вы должны будете вспомнить многое из того, что было сказано в течение этих начальных встреч, и это жизненно важно в представлении того, для чего вы делаете свою работу. Это объясняет важность документации. Если все записывается и регистрируется, а не только запоминается, клиенты могут регулярно просматривать записи. Это означает, что вы не только можете улучшить отношения с вашими клиентами, но и увеличите ваши возможности в идентификации данных, которые они хотят видеть, а также обеспечит команду проектировщиков информацией, требуемой при проектировании конечного продукта. Эта книга возникла по итогам восьми лет создания ошибок в процессе проектирования БД, и интервью клиента — одна из наиболее критических частей процесса, с которым я сталкивался. Это может показаться неподходящей темой для опытных программистов, но даже лучшим из нас следует напомнить, что бряцание оружием, запугивание клиентов, сообщение им, что они хотят, до того, как они сами сообщат вам об этом, и даже попытки управлять ожиданиями пользователя, могут привести к крушению даже хорошо развитой системы. Хорошая техника интервьюирования клиента необходима нам, чтобы получить твердую основу для процесса проектирования. Если вы имеете шаткую основу, конечный продукт будет, вероятно, также шатким.
26
Сбор информации для проекта БД
О чем следует спрашивать Очень важно быть уверенным, что, независимо от того, кто бы ни интервьюировался, были выяснены ответы на следующие вопросы:
Кто будет использовать данные? Ответ на этот вопрос может указать на другой персонал, нежели тот, у которого вы хотели бы взять интервью, и будет, вероятно, иметь значение, когда вы будете определять безопасность системы. •
Как данные будут использоваться? Представьте, что вас просят создать БД учета партнеров. Вам нужно будет знать: •
Будут ли использоваться имена партнеров лишь для обращений по телефону, подобно быстрой телефонной книге?
•
Будем ли мы делать массовую рассылку электронной почты или отправлять по почте сообщения персонально членам списков партнеров? Должны ли для этой цели имена быть подразделены на группы?
•
Будем ли мы использовать имена, чтобы запрашивать ответ по почте, вроде пожертвований, просроченных векселей или новых инвесторов?
•
Насколько важно иметь правильные титулы для каждого партнера (например, г-н, а не гр.)? Нужна нам любая фонетическая информация — потеряем ли мы продажу, если пользователь неправильно произносит имя заказчика?
Знание, для чего ваш клиент планирует использовать данные в системе, действительно, очень важная часть информации. Вы не только поймете процесс, но и можете также получить хорошее представление о типах данных, которые следует разместить.
Что вы хотите видеть в отчетах? Отчеты — часто одна из наиболее забываемых частей процесса проектирования. Многие разработчики-новички оставляют их разработку на самую последнюю минуту. Однако пользователи, вероятно, больше заинтересованы в отчетах, которые формируют данные, чем в чем-либо еще, что вы будете делать. Отчеты используются как основа для принятия жизненно важных решений, и могут либо поддержать, либо разрушить компанию. Возвращаясь к примеру с партнерами, следует задать вопрос, какое имя клиент хочет видеть в отчетах? •
Имя, фамилия
•
Имя, отчество, фамилия
•
Фамилия, имя
•
Прозвище
Очень важно "попробовать на зуб" эти проблемы пораньше, независимо от того, сколь малыми или глупыми они ни показались в этот момент. Мы рассмотрим формирование инфраструктуры отчетов позже в нашем проекте, и этот вид информации может быть необходим.
27
Глава 2 Одно замечание-предупреждение: в то время как очень важно получить некоторые идеи относительно того, какие данные необходимы в отчетах, вы должны быть осторожны, чтобы избежать любых обсуждений относительно их фактического представления. Это — не задача архитектора данных заниматься чем-либо, кроме требований к данным отчетов.
Где сейчас находятся ваши данные? Было бы замечательно иметь полностью новую БД, абсолютно не имеющую никаких данных, существовавших ранее. Это бы сделало жизнь такой легкой! К сожалению, такого почти никогда не бывает, кроме, возможно, новых компаний, но даже они будут иметь некоторые данные, которые они хранили, когда начали работать. Каждая организация своеобразна. Некоторые имеют данные в одном единственном месте, в то время как другие рассеивают их по многим местам. Редко, если вообще когда-либо, данные находятся уже в хорошо структурированных БД, так чтобы вы легко могли осуществить доступ. Если бы это имело место, вот бы была забава! Действительно, почему тогда клиент вообще к вам пришел? Клиенты обычно имеют данные в следующих разных местах: •
Универсальная ЭВМ или наследуемые данные Миллионы строк кода, написанного на КОБОЛЕ, все еще используются многими корпорациями.
LJ
Электронные таблицы Электронные таблицы — замечательный инструмент, чтобы рассматривать, вырезать и нарезать данные, но являются неподходящим местом, чтобы обслуживать сложные БД. Большинство пользователей знает, как использовать электронные таблицы в качестве БД, но, к сожалению, не так хорошо рассмотрено обеспечение целостности их данных.
•
Настольные БД типа Access Настольные БД — мощные инструменты, их легко развертывать и использовать. Однако это часто означает, что такие БД построены и поддерживаются не техническим персоналом и плохо разработаны, потенциально вызывая большое число проблем, когда они должны быть увеличены или изменены.
•
Картотека Да, все еще имеется много компаний, которые в настоящее время не имеют никаких компьютеров и поддерживают обширные запасы бумажных документов. Ваш проект мог бы просто заменить картотеку системой, основанной на компьютере, или поставить простую БД, которая регистрирует физическое местонахождение существующих бумажных документов.
Данные, которые вы должны включить в разрабатываемую вами БД на основе SQL Server, будут поступать из этих и других причудливых и замечательных источников, которые вы найдете у клиента.
Сколько эти данные стоят? Важно также иметь суждения о стоимости данных. То, что данные доступны, не обязательно подразумевает, что они должны быть включены в новую БД. Клиент должен быть информирован относительно всех данных, которые являются доступными, и обеспечен оценкой стоимости перевода их в новую БД. Стоимость передачи наследуемых данных может быть высока. Таким образом, клиенту дают возможность принять решение, что можно сохранить фонды для более важных целей.
28
Сбор информации для проекта БД Как будут данные в новой БД сочетаться с другими данными? Как только вы хорошо проработаете, где располагать все важные данные клиента, можно начать определять, как данные в вашем новом решении на SQL Server будут взаимодействовать с данными, которые останутся в своем первоначальном формате. Это может включать создание связей с универсальными ЭВМ с помощью сложных шлюзов, связи сервера с другими SQL Server и Oracle или даже соединение с электронными таблицами. Мы не можем делать слишком много предположений относительно этой темы в данный момент в нашем проекте. Только зная основную структуру, вы должны этим заняться, что может быть очень полезно позже.
Имеются ли какие-то правила, которые управляют использованием данных? Взяв наш предыдущий пример с партнерами, мы могли бы обнаружить что: •
Каждый партнер должен иметь соответствующий адрес электронной почты.
•
Каждый партнер должен иметь соответствующий почтовый адрес.
•
Клиент проверяет каждый адрес электронной почты, используя почтовую программу, и партнер не будет соответствующим партнером, пока эта проверка не будет успешно выполнена.
•
Партнеры должны быть подразделены на типы.
Будьте осторожны при использовании каких-либо правил, подобных этим. Согласуйте их с клиентом. Ваш конечный продукт может стать недопустимым из-за того, что вы ввели в данные правило, которое клиент не хочет иметь.
Другие источники для определения правил данных Кроме интервью, имеются и другие источники, которые вы можете использовать, чтобы найти правила для данных и другие виды информации, важные для проекта. Часто менеджер проекта получает эти документы.
Запрос о цене или запрос предложений Имеются два первичных документа: •
Запрос о цене (The Request for Quote — RFQ) — документ с должным образом сформированной спецификацией, который организация высылает фирмам, чтобы определить, сколько что-то будет стоить.
U
Запрос предложений (The Request for Proposal — RFP) — для меньшего количества сформированных идей, которые организация желает распространить, используя свободные консультационные услуги.
29
Глава 2 Копии этих документов должны быть добавлены к груде информации, которая вам понадобится позже в процессе проектирования. В то время как эти документы вообще состоят из отрывочной информации относительно проблемы и желательного решения, вы можете использовать их, чтобы подтвердить первоначальную причину необходимости создания СУБД, и для получения более твердого выбора, какие типы данных должны быть помещены в ней.
Контракты или наряды на работу клиента Получение копий контрактов — довольно радикальный подход к сбору информации для проекта. Откровенно говоря, в корпоративной структуре вы, вероятно, должны будете бороться с руководством, чтобы заставить его понять, почему вы вообще должны видеть контракт. Контракты часто трудно читать из-за языка, на котором они написаны. Однако будьте прилежными в фильтровании информации, и вы раскроете основной набор требований к СУБД — требования, которые должны точно выполнить, или в противном случае вам могут не заплатить за работу. Обратите внимание, что важен не только контракт на создание системы, но должны быть также учтены любые-контракты, которые создаваемая вами система должна обеспечивать.
Соглашения об уровне обслуживания Серьезной частью контрактов, которая является очень важной для процесса проектирования — требуемый уровень обслуживания. Она может определять число формируемых страниц в минуту, число записей в БД и т. д.
Не забудьте о ревизиях Когда вы строите систему, то должны учесть, будет ли система ревизоваться в будущем и кем. Правительство, клиенты ISO-9000, и другие клиенты, которые проверяются организациями по стандартизации, вероятно, будут иметь строгие контрольные требования. У других клиентов также будут финансовые контрольные процессы. Эти контрольные планы могут содержать ценную информацию, которая может использоваться в процессе проектирования.
Старые системы Если вы пишете новую версию работающей в настоящее время СУБД, то доступ к существующей системе может быть и благословением, и проклятием. Очевидно, что большая часть информации, которую вы можете собрать об исходной системе, очень важна. Все экраны, модели данных, модели объектов, документы пользователя и т. д. являются чрезвычайно важными для процесса проектирования. Однако если вы не просто пересматриваете существующую систему, очень важно использовать старую систему БД только как отправную точку. Очень легко думать в терминах доводки существующего кода, использующего все особенности и данные существующей системы как основание для обновления системы. Иногда это могло бы быть правильным направлением работы, но, вообще говоря, это не так. В большинстве случаев, существующая система, которую вы заменяете, будет иметь много проблем, которые должны быть установлены, но не повторены.
30
Сбор информации для проекта БД Отчеты, формы, электронные таблицы Весьма большой процент компьютерных систем построен для заполнения форм государственных форм, форм компании, всех иных видов форм. Вы должны обеспечить сбор всех этих данных, которые рассеяны по всей компании, и обязательно найти их все. Фактически ясно, что эти источники будут содержать данные, которые потребуются для вашего проекта, так что удостоверьтесь, что клиент дает вам все такие данные.
Схема учебного примера Главная цель примера в этой главе не в том, чтобы проиллюстрировать часть процесса сбора информации, — маловероятно, что архитектор данных будет делать это в большинстве команд проектировщиков — а скорее легко управляемый пример, которому мы можем следовать на протяжении книги. Для удобства вообразим, что мы — команда проектировщиков из одного человека (за исключением некоторой информации, поступающей от делового аналитика) и мы также полностью игнорируем любой вид пользовательского интерфейса. Вы получаете электронную почту от менеджера по программированию, заявляющего, что отдел бухгалтерского учета нуждается в БД для управления обслуживанием текущих счетов. После того, как вы встретились со своим менеджером по информационным технологиям, вы обращаетесь к менеджеру бухгалтерского учета Сэму Смиту. Необходимо организовать встречу (интервью) с Сэмом, чтобы понять, что требуется.
31
Глава 2
Интервью клиента Вот тип записей, которые бы вы делали при этой встрече:
(Bcmpeia с Сэмом Смитом, бухгалтерское дела, 24 ноября 2000, W 1асов утра, ЪотшЖ конференц-Зал 'Постишвян: Луис Юэвидсон, архитектор данных; Сэм Смит, менедэ/сер бухгалтерском yiema; Юфо Юфонс, экономист ЧПервая всмреЫ (ИспольЗуемме дополнительные документы: регистр банка, типовой iek, типовой лицевой film, а такфе электронный формат лицевого dema. 33 настоящее время, испольЗуется (м/мафни4 регистр iekoS, а такфе основной внутренний регистр. (Рассматривали возможность использования подготовленных материалов, но не нашли ни одного, который бы ойспеАивал все, imo xoiem 3aka3iuk. M. у большинства предложенных путей слишком много особенностей. ^lip/mo иметь многопользовательские воЗмофности (ввод и просмотр). (Распределение интЬант, Щоцесс: иметь последнюю информацию относительно dema. (В настоящее время баланс dema проводится один раЗ в месяц. МспольЗуется omiem иЗ банка. Юля nmyienus баланса жтлъЗуетсл по крайней мере полный день. Хотелось бы, полукть баланс по demy еженедельно, испольЗуя данные нЗ 'Интернета ^Интересует только ведение clemoS, но не сберефения, акции, обяЛаттьства, и, т. д. Юля этого уо/се имеется система. Ъудет ревиЗоватъсл ефегодно главным аудитором. Однако контрольною плана нет. (Когда Сэм Закошил,, был Задан вопрос об отслефивании проо Ъыло бы хорошо иметь информацию о полуЫтет тмттфа для большинства выписываемых нами 'кков. УЛакфе относительно категориЗации 1вков Сэм скаЗал, imo это было бы хорошо, но не является необходимым в данный момент времени.
32
Сбор информации для проекта БД Просматривая записи, можно заметить, что имеется несколько документов, которые мы должны собрать в отделе бухгалтерского учета, если это доступно: регистр чеков, отчет банка, план ревизий и пустой чек.
Предварительная документация Типовой регистр чеков
Number
Date
{Description
Category
12390
112/15/00
{Pizza Hut
Employee Appreciation
12391
U2/15/00
{Allied Mortgage
Building payment
12392
112/16/00
iTN Electric
Utilities
12393
j 12/16/00
{Deposit
N/A
Amount
Balance
Account.Running Total Здесь Number — номер; Date — дата; Description — описание; Category — категория; Amount — сумма; Balance — баланс; Account — счет; Running Total — текущий итог. Здесь и далее даты будут приводиться в американском формате: сначала идет номер месяца, затем число и, наконец, две последние цифры года. Прим. перев.
33
Глава 2
Типовой лицевой счет банка
Bank of the Account Statement Account Statement Previous Balance Current Balance Total Debits Total Credits 11/5/00
—I Account. Account Number
Account number
Last Balanced: Statement Date:
11/5/00 12/5/00
Check
12200
11/6/00
12201
Check
11/8/00
X48393
Deposit
11/28/00
X99778
Direct Withdrawal
Строки удалены
Строки удалены
Строки удалены
12/01/00
Check
12/02/00
' Check
12/03/00
; Check
Строки удалены
Строки удалены :
Checks: 12200
|
\
| 12205
| 12212
Строки удалены . Строки удалены
| Строки удалены | Строки удалены | Строки удалены | Строки удалены
12204*
\ 12211
•
•
\
12217;
Означает разрыв в последовательности чеков
Statement. Balancing Items (the s t a t e m e n t contains copies of all t h e i t e m s t h e register s h o u l d have)
Deposit Direct Withdrawal p
Statement. Previous B a l a n c e Current Balance Total D e b i t s Total C r e d i t s Previous Balance Date Statement Date
Здесь Check — чек; Deposit — депозит; Direct Withdrawal — прямое изъятие. Заметьте, что я вычеркнул все числа в вышеупомянутых отчетах. Если не очень важно иметь точные числа по документам, удаляйте любую важную информацию, которая могла бы вызвать проблемы, если бы попала не в те руки. 34
Сбор информации для проекта БД Формат потока данных типового регистра банка Столбец
Тип данных
Требуется
Дата сделки
Date только
Да
Номер сделки
String(20)
Да
Описание
String(lOO)
Да
Количество
Money
Да
(String
(N) означает строку в N символов, Date — тип-дату, Money — денежный тип.)
Обратите внимание, что этот поток данных не из нашего анализа. Скорее всего, это — бумажный документ, объясняющий, что банк будет давать клиенту, чтобы обеспечить электронный баланс счетов. ТИПОВОЙ
чек
Junel2,2000
13403 WROX Subscriptions dollars
Magazine Subscription
Это — то, что мы взяли из примера в данной главе. Мы сделали хорошее начало — возможности нашего проекта чрезвычайно маленькие, а мы собрали так много информации, насколько могли в данный момент, но придет время, когда нам потребуется большее количество информации. В последующих главах мы вернемся к этим записям и документации, когда будем формировать непосредственно саму БД.
35
Глава 2
Резюме В этой главе мы сконцентрировали внимание на существенном предварительном шаге проектирования БД — процессе сбора информации. Практический вывод из всего — получите настолько много данных, насколько можете, включая и документы. Посмотрите везде — не оставляйте ни одного уголка, который бы вы не обшарили, чтобы получить столько информации, сколько клиент готов и в состоянии вам сообщить относительно решаемой задачи. Иногда вы будете знать точно, как решить задачу даже после первого сообщения по электронной почте. В другой раз потребуется месяц копания вокруг, чтобы получить достаточно информации, чтобы начать заниматься этой задачей. Проектирование хорошо структурированных систем и БД — трудная работа, которая занимает относительно долгое время, чтобы выполнить все правильно. Первый шаг — понять задачу прежде, чем ее решать. Всегда имейте в виду, что требуется большее количество времени, чтобы переделать задачу, чем в случае, когда все делается правильно в первый раз. Мы посмотрим, что означает "правильно" на стадии физического моделирования данных при проектировании БД. Это будет намного позже.
36
Фундаментальные концепции БД Введение В этой главе я стремлюсь снять завесу мистики над теорией и терминологией, используемой многими академиками БД. Группа, которой я принадлежу — наиболее активная группа "бойцов-фронтовиков", кто фактически проектирует хорошие пользовательские БД, чтобы заработать на жизнь, и вся эта теория кажется очень удаленной от мира, в котором мы живем и работаем. Это может вызвать у вас вопрос, является ли теория проектирования БД заслуживающей внимания. Конечно, понимание принципов и теории, на которых построена технология, несомненно, очень важно. Например, предположим, что вы отдаете свой автомобиль механику, который не понимает, как работает зажигание, или летите в самолете с пилотом, который не понимает теорию полета? Имея это в виду, почему вы должны ожидать, что заказчики придут к вам для экспертизы БД, если вы не понимаете теорию, которая поддерживает проектирование БД? Вышеупомянутое означает, что некоторые из терминов и концепций, используемые академиками, могли бы запутать и расстроить лучших из нас, частично из-за используемой математики. По этой причине имеются некоторые, известные только посвященным термины, которые, казалось бы, должны для большинства программистов означать одну и ту же вещь, но фактически означают кое-что совершенно разное. Однако важно получить некоторое понимание реляционной теории БД, чтобы можно было разрабатывать соответствующие проекты для таких систем.
Глава 3 Итак, чтобы понять, о чем бредит академическое сообщество, мы хотим посмотреть на некоторые простые аспекты реляционной теории, сравнивая их с SQL-концепциями и методами, которые нам уже известны. Поскольку имеется много других книг по данному предмету, мы не собираемся зарываться слишком глубоко на территорию академиков; скорее мы собираемся попробовать представить основу полезных концепций БД. Эта глава представит реляционные концепции в значительной степени с SQL-ориентированной точки зрения. Мы будем часто касаться реализации вещей (хотя эта часть книги не связана с техническими вопросами), которых нельзя избежать при предоставлении нашего теоретического объяснения в терминах концепций, с которыми вы будете знакомиться. Однако мы не увязнем в вопросах реализации; это будет только краткий обзор. Для большей информированности не стесняйтесь рыться в других источниках, предложенных в главе 1. На протяжении этой главы мы рассмотрим: •
Реляционную модель — исследование терминологии, используемой для наиболее значимых блоков; мы будем их строить в более поздних главах.
•
Определения дополнительных концепций БД.
Реляционная модель Как упоминалось в главе 1, Е. Ф. Кодд изобрел реляционную модель в начале 1970-х гг. В этом разделе мы рассмотрим ее наиболее важные части с точки зрения Microsoft SQL Server. Как мы увидим, реализация с помощью SQL Server имеет много общего с этой моделью, но не совсем то же самое. Многие реляционные пуристы съеживаются, когда реализуются все разновидности SQL (не только Microsoft SQL Server). Эта книга — не место для обсуждения их возражений, и мы намереваемся избежать любого противоречия в отношении этого, поскольку рассматриваем физический проект и части реализации на SQL Server.
База данных Первый термин, который мы должны определить, — база данных (БД).
Просто говоря, база данных — собрание данных, организованных для легкого и быстрого поиска и восстановления.
Это может быть карточный каталог в библиотеке, БД на SQL Server или текстовый файл. Технически, нет никакой соответствующей концепции в реляционной теории, но поскольку мы используем этот термин часто, важно, чтобы мы представляли его.
В SQL Server база данных — собрание объектов и данных, которые логически сгруппированы вместе.
38
Фундаментальные концепции БД
Таблица Одно из наиболее частых неправильных представлений относительно реляционной модели — то, что термин реляционный связан с отношениями между таблицами. Фактически он связан с термином relation (математическое название таблицы), который оказывается (почти) синонимом термину "таблица". Как мы увидим, реляционная модель использует различные имена не только для того, чтобы большинство программистов знало о таблицах, но также и об элементах, содержащихся в них. Центральный, наиболее важный элемент, с которым мы имеем дело — сама таблица. Таблица — физическое представление некоторого объекта, или реального, или мнимого. Когда вы проектируете таблицы, то будете рассматривать людей, места, вещи (существительные), о которых вы хотите хранить информацию. Понимание концепции рассмотрения таблиц как существительных, а существительных как таблиц, является первым шагом к проектированию соответствующих таблиц. Позже мы исследуем более подробно, что мы должны хранить в таблицах и других структурах, рассмотренных в этом же разделе. Затем в следующей главе мы начнем рассмотрение, как идентифицировать таблицы и другие объекты. Термин таблица — существенно ориентируемый на реализацию термин и имеет следующий смысл:
Упорядоченное расположение данных. Особенно такое, в котором данные размещены в столбцах и строках в существенно прямоугольной форме.
Две общих версии таблиц: электронная таблица Excel и результат простого SQL-запроса в БД Northwind (русская локализация этой БД называется Борей). Последняя таблица показана ниже: Щ SQL Queiy Analyzer - (Quew - NASH200.Noithwind.£NA\ldavidson - Untitledi'] File Editfluerytools Wn i dow Help • * 10 Northwind Щ$ ^•еИП Ш t i (ft «о П pelect
B
from region
-J-if RegionID
RegionDescription Eastern Western Northern Southern
(4 rou(s) affected)
Query bach completed
NASH200130) ENAMdavidson 1511 NuilUw.d 0 00 00
4 fows Lni.CoH
39
Глава 3 Даже до компьютерных электронных таблиц люди работали на бумаге с таблицами, содержащими строки и столбцы (хотя они и не назывались электронными таблицами). Мы взглянем глубже на строки и столбцы позже в этом разделе. Как мы упомянули ранее, таблица известна как отношение, или, более определенно, именованное отношение. Как мы рассмотрим позже, таблицы и представления — оба рассматриваются как именованные отношения, а результирующие наборы — неименованные отношения. Понимание этого поможет вам лучше писать SQL-запросы. Следующая таблица — очень характерная таблица, составленная для ресторанов с несколькими определенными столбцами. Каждый из терминов таблицы будет определен полностью в этой главе. Вторичные ключи
Первичный ключ
Table ID • (табличный • идентификатор)
Таблица Отношение
:
Name (имя)
Code (код)
Date (дата)
1
Big Momma's V ittles
BMV
10/22/00
2
Next Step Eatery
NSE
10/22/00
3
Bob's Dog Hut
BDH
11/13/00
4
Earl's Restaurant
ER
8/12/00
5
Roger's Restaurant
RR
7/4/00
Строки
Число строк
Тип данных, определенный пользователем тип данных и/или проверкиограничения
Это не означает, что таблица и отношение — одно и то же. Определение отношения следующее:
Отношение: структура, составленная из атрибутов (индивидуальные характеристики, типа имени или адреса, соответствующие столбцам в таблице) и кортежей (наборы значений атрибутов, описывающих индивидуальные характеристики отдельных экземпляров, например, заказчиков, соответствующих строкам таблицы). В пределах отношения экземпляры не могут повторяться; каждый должен быть уникален. Кроме того, экземпляры в пределах отношения считаются неупорядоченными; если два экземпляра поменять местами, отношение не изменяется.
Это определение может сначала показаться немного запутанным. Не волнуйтесь; все станет более ясным, когда мы пройдем через всю эту главу.
40
Фундаментальные концепции БД Отношение — очень строгая математическая концепция, основанная на теории множеств, в то время как таблица — физическая реализация отношения со специальными свойствами. То есть, можно сказать, что отношение может использоваться, чтобы определить все таблицы, но не все отношения могут быть представлены как таблицы. Однако разумно предположить, что различия между этими двумя понятиями являются столь тонкими, что могут показаться среднему программисту БД почти бессмысленными. Однако мы будем использовать другой термин для обозначения таблицы —сущность (entity). Термин "сущность" часто используется в логическом моделировании, чтобы обозначить концептуальную версию таблицы. Я лично люблю этот термин, поскольку он действительно подчеркивает, что таблица — фактическое представление чего-то. "Таблица" — очень уж абсолютный термин, который имеет совершенно другое значение вне области информатики. Первый раз, когда я сказал моей жене, что построил таблицу, она подумала, что я ничего не делаю по своей профессии, "А я думала, что ты был с компьютерами" Термин "сущность" имеет меньшее количество издержек и меньше привязан к реализации. В следующей главе, когда мы начнем идентифицировать таблицы, то сначала идентифицируем их концептуально как сущности, чтобы избежать втискивания в структуру, которую таблицы непременно имеют.
Строки и столбцы Строка (или запись) задает экземпляр какого-то объекта, который физически представляется таблицей. Концепция экземпляра связана с объектно-ориентированным программированием, но интерпретируется по-другому в реляционном программировании. В объектно-ориентированном программировании (ООП) (и даже в объектно-ориентированных базах данных) класс — общее определение, содержащее атрибуты и методы, которые могут работать с конкретными экземплярами класса, обычно упоминаемыми как объекты. В SQL Server мы имеем дело с таблицами, содержащими столбцы и строки. Определение таблицы во многом подобно классу в ООП, а конкретный экземпляр таблицы — строка, которая аналогична объекту. Поскольку мы не имеем никаких методов для управления данными, специально введенных в определение таблицы, мы должны иметь определенные SQL-операции, которые можно использовать для взаимодействия с данными в таблицах. Главная причина различий, вообще говоря, — проблема реализации. Поскольку SQL в большей степени создан для организации доступа к данным, очень легко в любой момент видеть многочисленные экземпляры SQL-"объектов", так что многие программисты не замечают параллели между объектом ООП и строкой SQL Server. При моделировании данных и объектов в последующих главах мы подчеркнем некоторые из параллелей между методологиями ООП и реляционным программированием БД. Как мы можем видеть на рисунке ниже, реляционная модель существенно отличается названиями строк и столбцов. Строки — кортежи (tuples — по-английски, произносится как "купле"), а столбцы — атрибуты. Термин "кортеж" — один из тех забавных технических терминов, который, кажется, или заставит вас сделать паузу и задуматься, или заставить вас хихикать (что и случилось со мной). Согласно своей легенде, слово "кортеж" не имеет никакого другого значения, и вы можете найти его не в любом словаре. Возможно, оно было выдумано в течение формирования реляционной модели Кодда, в частности, как слово без предвзятых понятий, приложенных к нему. Это устраняет беспорядок, возникающий при использовании терминов типа "таблица".
41
Глава 3
Вторичные ключи
Первичный ключ
Name (имя)
Code (код)-
Date (дата)
1
Big Momma's V ittles
BMV
10/22/00
2
Next Step Eatery
NSE
10/22/00
3
Bob's Dog Hut
BDH
11/13/00
4
Eari's Restaurant
ER
8/12/00
5
Roger's Restaurant
RR
7/4/00
Taole Ю
(тай.шчный иден!ифйквн1р)
Таблица (сущность) Отношение
Строки Число строк Кортежи
Тип данных, определенный пользователем тип данных и/или проверкиограничения
В то время как термин "кортеж", кажется, довольно неочевиден многим программистам, атрибут — название намного лучшее для представления столбца. Каждый столбец должен содержать характеристику или атрибут экземпляра строки или кортежа, или более просто сказать — каждый столбец содержит атрибут, который описывает строку. Если думать в такой манере, использование столбца становится более осмысленным. Аналогия со свойствами ООП должна быть разумно очевидна для любого, кто использовал ООП. Столбец — свойство экземпляра (или строки) таблицы (или отношения/сущности). Один из принципов реляционной теории — то, что атрибуты не имеют никакого свойственного им упорядочения. Вы, вероятно, поняли из опыта, что столбцы в таблице имеют свойственное им упорядочение, поскольку SQL-пользователи хотят иметь постоянный набор столбцов, которые будут возвращены независимо от того, как столбцы были эффективно размещены в физической среде. Главный момент здесь заключается в том, что вы никогда не должны задумываться о физическом упорядочении столбцов. Имеется несколько общих правил сортировки столбцов, которым архитекторы данных обычно следуют при проектировании таблиц, типа того, что первичные ключи размещаются слева в таблице, за которыми следуют более важные столбцы, а менее читаемые столбцы размещаются в правой части, но это — только конкретно для удобства программиста или пользователя. Мы рассмотрим это в дальнейшем, когда фактически начнем моделировать таблицы в главе 5. Каждая строка в таблице должна содержать один и тот же набор столбцов. Каждый столбец должен иметь название, которое является уникальным среди названий столбцов в той же конкретной таблице. Это может казаться очевидным, но все же стоит упомянуть. Последний момент, который мы должны обсудить, — степень отношения, которая соответствует числу столбцов в таблице. Этот термин не используется так часто, но я видел, что он упоминался в некоторых документах, и его стоит отметить для завершенности.
42
Фундаментальные концепции БД Атрибуты атрибута Я только что дал такое название этому разделу, потому что оно заставило меня улыбнуться. Однако важно понять, что каждый столбец в таблице имеет собственные атрибуты. Наиболее важными характеристиками (помимо названия столбца, которое уже рассмотрено) являются следующие: •
допустимые значения столбца;
•
является ли столбец частью идентификатора строки;
•
обязательны ли значения столбцов.
В ряде следующих разделов мы будем говорить об этом более подробно.
Ограничение значений столбца только допустимыми значениями Таблицы SQL Server имеют несколько средств для ограничения значений, которые могут быть введены в столбец: •
типы данных (основные);
•
определенные пользователем типы данных;
•
ограничения;
Q
триггеры и хранимые процедуры.
Каждое из них служит четко своей цели и должно обычно использоваться в перечисленном порядке. В реляционной терминологии, как показано на рисунке ниже, все они рассматриваются под заголовком домены. Домен определяется как множество допустимых значений для данного атрибута. Мы кратко рассмотрим эти механизмы в следующих разделах. Первичный ключ
Вторичные ключи
Число строк
Возможные значения
Тип данных, определенный пользователем ] 4 — т и п данных / и/или проверки-ограничения домены
4 3
Глава 3 Основные типы данных Основной тип данных — самая простая форма домена в SQL Server. Тип значения определен так же, как характерный диапазон входной величины. Одна дополнительная деталь, которую следует упомянуть, — то, что каждый тип данных также поддерживает значение NULL, если мы не определяем иначе. Например: •
Переменная типа i n t e g e r требует ввода целой числовой величины и может быть между - 2 3 1 (-2 147 483 648) и +2 3 1 (2 147 483 647).
•
Тип данных t i n y i n t также требует ввода целой числовой величины, но имеет диапазон неотрицательных целых чисел между 0 и 255.
•
Тип данных v a r c h a r (20) требует данные в виде ASCII-символов в диапазоне от 0 до 20 символов.
Базовые типы данных — набор "примитивных" типов данных, которые определены в SQL Server. SQL Server имеет следующие базовые типы данных, которые могут использоваться как типы данных столбцов: binary big int bit char datetime decimal float image int money nchar ntext nvarchar
numeric real smalldatetime smallint smallmoney sql variant sysname text timestamp tinyint uniqueidentifier varbinary varchar
Есть еще два типа данных: c u r s o r и t a b l e , хотя они никогда не используются в качестве типов данных столбцов. Выбор точного типа данных столбца — один из наиболее важных выборов, которые вы будете выполнять на стадии физического проектирования. Во время логической стадии проектирования, как минимум, нужно определить общий тип данных, которые должны быть размещены. Общие классы типов данных могут включать: Binary (двоичный) Blob (BLOB-тип) Character (символ) Date (дата) Logical (логический) Numeric (числовой) Time (время) Мы обсудим типы данных более детально в главе 10.
44
Фундаментальные концепции БД Определяемые пользователем типы данных (с правилами) SQL Server позволяет создать настраиваемые типы данных, основанные на существующих типах. Такой тип определяется только в терминах единственного базового типа данных. Далее можно создать правило и связать его с новым типом данных, которое ограничивает размещаемые значения, допустимые в столбце, по своему желанию. Это позволяет вам создать много столбцов из одного и того же шаблона и обеспечить контроль вводимых значений. Если вы не знакомы с определяемыми пользователем типами данных, для получения дополнительной информации проконсультируйтесь в SQL Server Books Online (электронное руководство по SQL Server). В качестве простого примера вы могли бы пожелать задать следующие определяемые пользователем типы: •
IntegerFromOneToTen — определяемый как целое число, с правилом, которое ограничивает вводимые значения в диапазоне от единицы до десяти включительно.
•
SocialSecurityNumber — определяемый как строка из девяти символов или строка из одиннадцати символов, которая ограничена наличием черточек в четвертой и седьмой позициях, а все другие символы — цифровые.
Как вы видите, многие общие типы данных могут быть созданы, чтобы помочь в реализации домена значений, допустимых для столбца. Нужно также отметить, что определяемые пользователем типы данных не должны быть простыми основными типами данных, как определено в реляционной теории. Допустимо иметь более сложные типы данных, пока в них нет повторяющихся групп. Например, вы могли бы иметь следующий тип данных: •
2DGraphPoint — определенный как X — целое число, Y — целое числа
Но вы не могли бы иметь тип данных таблицы с неограниченным числом атрибутов. Это допустимо только в случаях типа графической точки, где две величины X и Y фактически представляют единственный атрибут при таком расположении. Обратите внимание, что Microsoft SQL Server в настоящее время не поддерживает это.
Ограничения Ограничение — другой механизм, который может использоваться, чтобы ограничить значения, вводимые в столбец. •
•
•
NULL-ограничения — NULL-ограничение определяет, обязательно ли данные должны быть введены в столбец. Значение NULL в реляционной теории указывает, что атрибут имеет неизвестное значение, обычно представляется как может быть. Важно понимать, что когда сравниваются два значения NULL, они никогда не равны. Проверки-ограничения — механизмы, которые позволяют нам определять допустимый диапазон значений для столбца. Например, мы можем создать ограничение на столбец, который хранит месяц года и допускает только значения от 1 до 12. Ограничение внешнего ключа — средства, которые используются, чтобы быть уверенным, что значение в одной таблице соответствует ключу другой таблицы. Они используются, чтобы обеспечить целостность данных и поддерживать отношения между таблицами. Хотя мы и не будем их в данной главе рассматривать более глубоко, важно просто упомянуть их существование в роли домена для столбца. 45
Глава 3 Триггеры и хранимые процедуры Вы можете использовать триггеры и хранимые процедуры, чтобы реализовать домен столбца, хотя они вообще-то используются для намного более мощных целей. В этом разделе мы рассмотрим только использование каждого из них в качестве домена. Триггеры — механизмы, которые позволяют коду запускаться всякий раз, когда данные в таблице вводятся или изменяются. В то время как они позволяют нам закодировать сложные правила домена, которые не могут быть записаны в ограничения, они не могут регулировать значения, вводимые в отдельный столбец. Последнее имело бы место только в том случае, если бы правила были слишком сложные для простого ограничения, например при требовании автоматической зачистки для ввода новых значений от удаленного супервизора. Мы исследуем триггеры более подробно в последующих главах. Другой механизм в SQL Server, который формирует домен столбца — хранимая процедура. Хранимые процедуры — не лучший способ делать это, но, опять-таки, могут иметься серьезные основания для их использования (например, запрещение прямого доступа к определенным строкам и/или столбцам данных). Основная задача состоит в том, что для этого случая мы должны учесть все корректировки для столбца, чтобы фильтровать с помощью единственной хранимой процедуры или повторяемого для каждого случая количества кода, когда пользователь должен обновлять данные. Единственный случай, когда мы должны обратиться к использованию хранимых процедур для формирования домена — это когда выполнение требований необязательно или они изменяются. Мы должны также кратко упомянуть, что в то время как хранимые процедуры могут использоваться, чтобы реализовать домены, то же самое можно сказать относительно любого кода, который написан, чтобы вставить данные в столбец. В главе 11 мы рассмотрим триггеры и хранимые процедуры как механизм обеспечения целостности данных более детально.
Замечание относительно терминологии В этой книге мы обычно будем использовать термин "домен" на протяжении логического проектирования, чтобы указать описание типов данных, таких как номер (number), целое число (integer), строка (string) и т. д. вместе с общим описанием допустимых значений для атрибута. Мы будем стараться использовать тот же самый домен для всех столбцов, которые имеют одни и те же атрибуты. Например, мы могли бы определить следующие домены в типичной БД: •
Amount (сумма) — денежная величина, с доменом, который всегда требует значения;
•
String (строка) — характерное значение строки, которая содержит алфавитно-цифровые величины и которая всегда требует значения; Description (описание) — значение строки, которая используется для дополнительного описания экземпляра таблицы и которая всегда требует значения;
•
• •
46
FutureDate (будущая дата) — значение даты, которая при вводе должна быть больше, чем текущая дата, и которая всегда требует значения; Social Security Number (номер социального страхования) — строка из одиннадцати символов формата ###-##-####, которая всегда требует значения.
Фундаментальные концепции БД Когда мы идентифицируем новые атрибуты, то будем смотреть, подходит ли новый атрибут какому-либо существующему домену, и если это так, то мы назначаем соответственно этот домен. Если же это не так, то мы создаем новый домен. Это даст нам атрибуты, которые позволяют строить БД более быстро, более легко, и более последовательно. Мы будем использовать термин бизнес-правила, чтобы указать предикат для таблицы или БД в целом. Предикат — просто правило, которое управляет значениями в БД. Как пример этого, рассмотрим домен SocialSecurityNumber. Этот домен пришел с предикатом, размещающим значение в атрибут, который использует его всегда в формате "###-##-####". В то время как мне не очень-то нравится термин "бизнес-правила", это довольно распространенный сегодня термин. Заметим здесь, что мы продолжим далее определять бизнес-правила на протяжении всей книги, так как имеется много различных проблем, связанных с этим очень общим термином. Очень часто мы не будем идентифицировать наши бизнес-правила как часть определений таблицы (или сущности); скорее, они будут помещаться в нашу документацию и реализацию.
Идентификаторы строк В реляционной теории отношение не может иметь повторяющиеся кортежи. Те, кто имеет опыт работы с SQL Server, конечно, хотели бы перейти к построению индекса, чтобы иметь дело с этой ситуацией. Однако в реляционной теории не имеется также и никакой концепции индекса, так как индекс — физическое устройство, а, строго говоря, реляционная теория не имеет дело с проблемами реализации. В физической таблице нет никакого ограничения, которое говорит, что не должны быть повторяющиеся строки. Однако, в практическом смысле, никакие две строки не могут быть идентичными, потому что в деталях реализации скрыты атрибуты, которые предотвращают появление этой ситуации (типа номера строки или точного местоположения в физическом носителе данных, которое вы не можете фактически видеть). Однако это всегда плохо, когда Вы можете иметь повторяющиеся строки по ряду причин: •
Невозможно определить, какая строка является какой; это означает, что мы не имеем никакого логического метода изменения или удаления отдельной строки.
•
Если больше чем один объект имеет точно те же самые атрибуты, это, вероятно, опишет тот же самый объект так, что если мы пробуем изменить одну строку, то другая строка тоже должна измениться; это означает, что мы имеем зависимые строки.
Чтобы бороться с этим, мы используем концепцию потенциального ключа. Ключ используется, чтобы обеспечить уникальность атрибута или совокупности атрибутов. Сущность может иметь столько потенциальных ключей, сколько потребуется, чтобы обеспечить уникальность ее столбцов. Так же, как в SQL Server, мы имеем концепцию первичных ключей и вторичных ключей. Первичный ключ используется как первичный идентификатор для таблицы, а вторичные ключи (реализуемые как уникальные ограничения) — другие поля, по которым должна поддерживаться уникальность, как видно из следующей таблицы:
3 1868
47
Глава 3
Первичный ключ Потенциальные Вторичные ключи
Число строк
Тип данных, определенный V\ пользователем Возможные j 4 — тип данных значения / и/или ^ проверки-ограничения домены
Выбор потенциальных ключей — очень важный вопрос. Большой процент ошибок БД возникает оттого, что не определены все возможные потенциальные ключи. Когда мы будем получать правила нормализации в главе 6, то обсудим специфические особенности выбора потенциальных ключей. Составной ключ Ключи, по определению, могут быть составлены из любого числа столбцов, хотя лучше стараться ограничивать число используемых столбцов настолько, насколько возможно. Старайтесь иметь определенное число столбцов, насколько это возможно, но если необходимо, может быть столько столбцов, сколько вы пожелаете. Например, вы можете иметь таблицу Book (книга) со столбцами Publisher_Name (название издателя), P u b l i s h e r _ C i t y (город издания), ISBN_Number (ISBN-номер), и Book_Name (название книги). Для тех, кто не знает, ISBN — уникальный идентификационный номер, назначаемый книге при ее издании. Из этих атрибутов мы решаем, что можем определить три ключа: •
Publisher_Name, Book_Name — очевидно, что издатель, вероятно, выпустит больше чем одну книгу. Также можно осторожно предположить, что названия книг не уникальны в множестве всех книг. Однако, вероятно, истинно то, что один и тот же издатель не будет издавать две книги с одним и тем же названием (по крайней мере, мы предположим, что это так).
• •
ISBN_Number — мы уже говорили, что ISBN_Number уникален. P u b l i s h e r _ C i t y , ISBN_Number — так как ISBN_Number уникален, то комбинация P u b l i s h e r _ C i t y и ISBN_Number также уникальна.
Первый и третий ключи, которые мы определили, — сложные ключи. О третьем следует сказать несколько больше. Значение этого ключа — то, что в каждом городе вы можете повторно использовать ISBN_Number; факт, который является, очевидно, не истинным. Это — очень частая проблема со сложными ключами, когда они не обдуманы должным образом. 48
Фундаментальные концепции БД Хотя это немного в стороне от темы, важно не путать уникальные индексы с ключами. При работе с индексами могут быть причины выбрать ключ P u b l i s h e r _ C i t y , ISBN_Number в нашей БД на SQL Server. Однако мы не должны идентифицировать его как первичный ключ таблицы. В главе 10 мы обсудим реализацию ключей, а в главе 14 рассмотрим реализацию индексов для улучшения доступа к данным. Первичный Ключ Первичный ключ — это ключ, который используется, чтобы дать определенный интерфейс доступа к отдельной строке таблицы. Обычно, это — просто указатель (термин, заимствованный из функционального программирования), используемый для доступа к строке, и он, вероятно, не должен изменяться или, по крайней мере, должен изменяться очень нечасто. В следующем разделе будет кратко введено понятие типа ключа, называемого искусственным ключом, который мы будем использовать на протяжении этой книги, и который будет спасать вас от многих неприятностей во время реализации. Мы обсудим точную логику, определяющую этот выбор, когда будем рассматривать физическое проектирование, но пока достаточно рассмотреть только смысл терминов. Искусственные ключи Искусственный ключ — изобретение, единственной причиной существования которого является возможность идентифицировать строку. Это обычно автоматически формируемый номер (столбец идентификации), или GUID (Globally Unique IDentifier — глобальный уникальный идентификатор), который является очень большим идентификатором, уникальным на всех машинах в мире. Реализация GUID — вне возможностей этой книги; однако полная документация доступна в www.microsoft.com. Главная причина использования искусственного ключа состоит в том, чтобы обеспечить ключ, который конечный пользователь никогда не должен видеть и никогда не должен с ним взаимодействовать. Он служит просто как указатель, и ничего больше. Для тех, кто не понимает, что такое указатели, они являются общими конструкциями, пришедшими из языка С или любого языка программирования низкого уровня, используемыми для указания на местоположение в памяти. Искусственные ключи указывают на положение в таблице. Для таблицы должны быть также определены и другие ключи, иначе это некорректная таблица. Концепция искусственного ключа — своего рода беспокойство для тех, кто в перспективе является пуристом. Так как он не описывает запись вообще, то может ли он на самом деле быть атрибутом записи? Искусственные ключи, вероятно, даже не должны быть упомянуты в части, посвященной логическому проектированию, но важно знать об их существовании, так как они будут, несомненно, все еще неожиданно возникать в некоторых логических проектах. В главе 6 мы обсудим все "за" и "против" такого подхода. Вторичные ключи Любой потенциальный ключ, который не выбран как первичный ключ, упоминается как вторичный ключ. Вторичные ключи очень важны для успешной работы БД. По той или иной причине, большинство таблиц имеет больше чем один способ идентификации. Это особенно важно, когда используются искусственные ключи в качестве первичных ключей.
Необязательные значения столбца Это — куча проблем, которые я предпочел бы не раскрывать, но должен. Таблицы могут иметь столбцы, для которых содержание еще неизвестно или ненужно в данном контексте. К сожалению, в SQL имеется только один механизм, чтобы обозначить, что это факт. Это — значение NULL. Из перечня различных целей использования NULL очевидно, что это — не оптимальный путь обработки таких ситуаций. Помещение неправильных данных в столбец 49
Глава 3 (типа даты 31 декабря 9999) даже более сомнительно, чем NULL, по двум причинам. Во-первых, вы можете впоследствии обнаружить, что хотите хранить данные с точно тем же самым значением, которое вы только что ввели. Во-вторых, вы можете увидеть, что ввод значения, подобного этому, может оказаться непонятным пользователю. Закон независимости целостности (номер 10 из 12 законов Кодда — см. приложение А) требует, чтобы никакой столбец в первичном ключе не был необязательным столбцом. Сведенный к единственному предложению он означает: NULL — плохо. Многие теоретики предпочли бы, чтобы с NULL было покончено раз и навсегда. Имеется много причин для этого, как мы увидим в более поздних главах, но в этот момент мы будем придерживаться очень простого взгляда. NULL определен как неизвестное значение так, что никакие два значения NULL не эквивалентны. Аналогично при реализации NULL рассмотрение значения NULL может иметь несколько возможностей: •
Я не знаю значение.
•
Я никогда не буду получать значение.
•
Значение в этом столбце не имеет смысла в данной ситуации.
Я предпочел бы видеть набор реализованных значений типа NULL, чтобы знать, почему столбец имеет значение NULL. Имеется ряд методов работы с необязательными данными без NULL, но реализация может требовать использования весьма небольшого числа таблиц. Мы обсудим, как работать со значениями NULL, несколько глубже в главе 12.
Число строк Число строк в таблице имеет специальное название в реляционной теории. Как показано на рисунке, приведенном ниже, это называется мощностью (cardinality). Этот термин редко используется, чтобы указать число строк в таблице; однако он используется, чтобы указать число строк, которые удовлетворяют некоторому условию. Мы рассмотрим другую форму мощности позже в этой главе, когда будем обсуждать отношения. Первичный ключ Потенциальные Вторичные ключи ключи
Строки Число строк Кортежи
Мощность
Возможные значения
5 0
Тип данных, определенный пользователем ] 4 — тип данных / и/или проверки-ограничения домены
Фундаментальные концепции БД Дополнительные соображения насчет таблиц В этом разделе мы рассмотрим некоторые дополнительные соображения, относящиеся к таблицам в реляционной теории. Внешние ключи Когда ключ — не ключ? Когда это — внешний ключ. Не шутка, но истина. Внешний ключ — фактически столбец (или комбинация столбцов), чьи значения соответствуют первичному ключу или уникальному ключу в той же самой или другой таблице. Существование внешнего ключа в таблице представляет реализацию отношений между таблицами. Мы обсудим отношения более подробно позже в этой главе. Внешний ключ не обязательно должен иметь уникальные значения. Закон независимости целостности (номер 10 из 12 законов Кодда — см. приложение А) требует, чтобы для всех величин, не являющихся необязательным внешним ключом БД, имелось соответствующее значение первичного ключа в связанной таблице. Представления Представления — это таблицы, которые не существуют как базовые (фактические) таблицы; фактически, они — виртуальные. Представление — реализация именованного отношения, как мы обсуждали ранее в разделе Таблица этой главы. Представления появляются как таблицы для пользователя и должны осторожно рассматриваться в этом качестве. Основанные на законах Кодда (см. приложение А), представления, как предполагается, являются корректируемыми, что сильно увеличивает их способности выполнять свои задачи. Фактически, в SQL Server 2000 мы можем реализовать любое представление как корректируемое, используя несколько новых особенностей. Это будут рассмотрено в деталях в части Физическое проектирование книги, когда мы будем выяснять, какие проблемы можно, а какие нельзя решить, используя представления. Представления имеют ряд полезных применений и должны рассматриваться как часть логического проекта, поскольку они могут использоваться, чтобы решать некоторые очень важные проблемы. Они могут использоваться для следующих целей: Q
Обеспечение безопасности. Включением или исключением определенных столбцов из определения представления, конкретной отменой прав доступа к базовым таблицам и выдачей прав на представление можно предусмотреть безопасность исключенных столбцов.
•
Возможность рассматривать различными пользователями одни и те же данные различными способами. Строя различные представления данных, пользователи могут получить представление, которое удовлетворяет их потребностям. Таким образом, данные, которые являются несущественными для пользователя, могут быть скрыты от него, чтобы улучшить читаемость. В дополнение к этому, могут быть установлены отношения с помощью внешних ключей для отображения пользователю данных из связанных таблиц.
•
Обеспечение логической независимости данных. Один из основных принципов реляционной теории заключается в том, что реализация БД не должна давать никаких различий для пользователей. Если что-то изменяется в реализации БД, пользователь не должен это заметить. Представления 51
Глава 3 могут использоваться почти таким же образом, чтобы изолировать пользователя от изменений базовой таблицы. Очевидно, что представление зависит от структуры основной таблицы, но если вы добавите столбец в основную таблицу, представление не должно добавлять его, если в этом нет необходимости. Если вы удаляете столбец или перемещаете его в другую таблицу, представление можно так изменить, чтобы вернуть отсутствующее значение или получить значение из новой таблицы. Представления — очень важная часть любой реализации реляционной БД. Однако они рассматриваются среди физических проблем реализации, так что следующий раз мы обсудим представления именно в этой части книги. Отношения Законы Кодда (см. приложение А) заявляют, что каждая БД должна быть описана на логическом уровне таким же образом, как и данные пользователя, чтобы пользователи с надлежащей безопасностью могли использовать один и тот же реляционный язык для рассмотрения пользовательских данных. Реляционная теория рассматривает структуру отношения таким образом, чтобы обеспечить два момента: •
Заголовок — набор пар — имя столбца и имя типа данных, — которые определяют столбцы таблицы. Это служит прослойкой между определениями, которые видит SQL-программист, и фактической реализацией, которую программист СУРБД (системы управления реляционной базы данных, Relational DataBase Management System — RDBMS) обычно реализует в структуре сервера БД.
•
Тело — строки, которые составляют таблицу.
В SQL Server и большинстве БД мы чаще рассматриваем каталог или, в терминах SQL Server, системные таблицы как групповое описание таблиц и других структур в БД. SQL Server представляет заголовок или каталог набора таблиц, называемый информационной схемой (Information Schema). Мы рассмотрим информационную схему в главе 10. Ограничение значений, которые помещаются в таблицы До сих пор мы имели дело со значениями ограничений, накладываемых на столбцы, и которые технически накладываются на них. Однако поскольку мы уже рассмотрели концепцию домена столбца, нам теперь просто нужно кратко перечислить ситуации, когда нужно ограничить данные в зависимости от других данных. Мы можем разделить их на три типичных сценария: •
зависимости между столбцами;
•
зависимости между строками;
•
зависимости между таблицами.
Каждый из них налагает на реализацию весьма специфические проблемы: •
Зависимости между столбцами. Зависимости между столбцами имеют дело с ситуацией, когда значение, размещенное в одном столбце, требует некоторого домена значений в другом столбце.
52
Фундаментальные концепции БД Например, пусть имеются два поля: hasDriversLicence (наличие водительских прав) и driversLicenseNumber (номер водительских прав). Если значение hasDriversLicense равно False, то не нужно вводить значение DriversLicenseNumber. Обычно вы решаете эту задачу для таблицы с помощью простого ограничения CHECK (проверка). •
Зависимости между строками. Зависимости между строками описывают ситуацию, где каждая строка той же самой таблицы диктует значения в других строках. Примером этого могла бы быть банковская система. Когда пользователь собирается произвести изъятие со своего счета, предварительно должны быть выполнены все бизнес-операции, чтобы увидеть, имеется ли достаточное количество денег, чтобы сделать изъятие. Фактически это могло бы быть выполнено и иначе, но логически оно должно выглядеть именно так. В общем случае эта ситуация должна быть решена, используя триггер, или, что менее желательно, хранимую процедуру. Одной из реальных проблем, связанных с зависимостями между строками в SQL Server, является то, что достаточно легко предотвратить чрезмерные величины (слишком много записей со слишком большими значениями), но очень трудно предотвратить слишком малые вводимые значения. Мы вернемся к этой теме в главе 12, когда начнем реализовывать этот вид проблем.
U
Зависимость между таблицами. Другая ситуация могла бы состоять в том, что если в одной таблице находится какое-то конкретное значение, то вы не хотите помещать соответствующие данные в связанную таблицу. Примером такого ограничения могла бы быть персональная информация о пользователе. Одна таблица будет содержать запись имени человека и его возраста, а другая таблица может быть предназначена для хранения его адреса и телефонного номера. Если человек находится в определенном возрасте, может быть фактически незаконным хранить его адрес и телефонный номер. Как и с зависимостью между строками, это также должно быть решено, используя триггер или, что менее желательно, хранимую процедуру. В зависимостях между таблицами существуют те же самые трудности, что и с зависимостями между строками.
Предикаты Мы обсудили предикаты для атрибутов, но теперь нам нужно рассмотреть предикаты, которые управляют использованием таблицы. Напомним, что термин предикат — причудливый путь задания правила, которое управляет значениями в БД. Предикат для каждой таблицы должен быть сформирован во время логического проектирования настолько полно, насколько возможно. Говоря прозаически, составление списка предикатов для таблицы нужно не только разработчику, но и пользователю для утверждения этих правил. Предикат должен содержать любое правило, управляющее вводом данных. Довольно трудно этому следовать, но овчинка стоит выделки. В следующей главе мы рассмотрим разработку предикатов для таблиц, с которыми будем иметь дело. 53
Глава 3
SQL-операции Важный момент, который нужно отметить при обсуждении реляционной технологии — реляционные операторы в T-SQL. Мы не собираемся слишком углубляться в это обсуждение, но некоторое упоминание об этих операторах должно быть включено в любое обсуждение реляционной модели просто для законченности. В этом разделе мы кратко рассмотрим операторы отношения, составляющие реляционную алгебру, которая является основой для всех SQL-реализаций. Как мы намекнули ранее, все наборы результатов, которые возвращаются запросом SELECT (запросом на выборку) языка SQL, можно рассматривать как таблицы (хотя они физически и не размещены соответствующим образом). Обратите внимание, что таблица должна содержать уникальные названия для каждого возвращаемого столбца, но это не всегда выполняется для набора результатов SQL (даже при условии, что должно быть именно так). Важно помнить этот основной принцип, поскольку вы пишете ваши собственные операторы SELECT, и это причина того, что использование SELECT * является особенно неприятным, что будет подтверждено в главе 12. Таблицы, которые сформированы в результате любого SQL-действия, считаются неименованными таблицами в противоположность именованным таблицам и представлениям. Первоначально Кодд сформулировал восемь реляционных операторов. Большинство их имеет форму одного отношения в виде оператора с одним или двумя отношениями. Только один из них не соответствовал этой форме и не реализуется в SQL. Следующие реляционные операторы — список операторов, которые первоначально были описаны Коддом в 1972 г. в Relational Completeness of Data Base Sublanguages (Реляционная реализация диалектов базы данных). Они приведены в списке в том порядке, в котором представлены в тексте, поскольку каждый логически добавляется к предыдущему. •
Ограничение (Restrict)
•
Набор (Project)
•
Соединение (Join)
•
Произведение (Product)
•
Объединение (Union)
•
Пересечение (Intersect)
•
Разность (Difference)
•
Деление (Divide)
Есть весьма много исследований в этой области, и совсем немного операторов было добавлено за эти годы. Исходные восемь используются, чтобы дать краткое представление реляционных операторов. Мы кратко рассмотрим каждый из исходных операторов и оставляем читателю право выбора их чтения по своему усмотрению. Важно не путать эти операторы с любыми ключевыми словами, используемыми в конкретном SQL-диалекте. Конкретный набор SQL-операторов является результатом двадцатилетних попыток создать реляционный язык, который конечные пользователи могли бы легко использовать, в то же время реализуя громадное число операций.
54
Фундаментальные концепции БД Ограничение Оператор ограничения используется, чтобы ограничить строки таблицы в соответствии с данным критерием. Это прямо аналогично простому выражению WHERE запроса SELECT. Например: SELECT FROM Щ WHERE
fieldname, fieldname2, tablename fieldname > value
...
,
fieldnameN
Выражение WHERE — реализация оператора ограничения, и можно рассматривать результат этой операции как неименованную таблицу с единственным полем fieldname и содержащую все строки, где fieldname больше, чем заданное значение v a l u e . В выражении WHERE мы можем использовать любой из обычных операторов сравнения (=, >, =, LIKE и т. д.).
Набор Оператор набора используется, чтобы ограничить число столбцов в результирующей выходной таблице. В следующем примере мы предполагаем, что имеется более одного столбца в таблице: Щ SELECT FROM
DISTINCT fieldname, tablename
fieldname2
Таким образом вы можете последовательно подразделить результирующую таблицу на отдельные столбцы или комбинации столбцов. В реляционной теории любые повторяющиеся значения могут быть удалены, если мы добавим ключевое слово DISTINCT языка SQL. Обычно возникает вопрос: "Почему в реляционной теории нет никакого оператора DISTINCT?". Каждый оператор в нашем обсуждении здесь состоит из одного или двух отношений и возвращает отношение. По определению, отношение не имеет никаких двойных строк. Так как результатом нашего запроса должно быть отношение, он не будет иметь никаких дубликатов, и оператор DISTINCT принципиально избыточен. В сущности, поэтому оператор DISTINCT является встроенным. Опять-таки, это — одно из наиболее важных различий между определениями таблицы и отношения.
Соединение Оператор соединения используется, чтобы связать одну таблицу с другой на основе соответствия значений столбца, обычно первичного ключа одной таблицы с внешним ключом другой. SELECT FROM
fieldname tablenamel JOIN tablename2 ON tablenamel.keyl
= tablename2.keyl
Мне кажется, что эта операция также должна быть объявлена, используя более реляционный алгебраический стиль. Реляционный алгебраический стиль показывает, как ограничены возможности реляционных операторов по сравнению с гораздо более функциональным SQL-языком.
55
Глава 3
I
JOIN WITH OVER
tablenamel tablename2 keyl
Это обозначение на самом деле намного более ясное, но делает точно ту же самую вещь. Оно, конечно, работает только тогда, когда названия ключей одинаковы. Причина, по которой я показываю это обозначение, заключается в том, что я хочу ясно дать понять, что JOIN в первом отрывке кода можно представить как отдельную таблицу. Далее рассмотрим влияние объединения двух операций JOIN, как в следующем запросе: SELECT FROM
fieldname table1 JOIN table2
ON tablel.keyl = table2.keyl JOIN table3 ON table2.key3 = table3.key2 Первую операцию соединения можно представить следующим образом: (SELECT * FROM Tablel JOIN table2 ON tablel.keyl = table2.keyl) = derivedTablel
Тогда мы можем переписать наш запрос следующим образом: SELECT FROM
I
fieldname derivedTablel JOIN table3 ON derivedTablel.key2 = table3.key2
Это все, насколько глубоко мы будем вникать в операторы соединения в этом разделе. Цель этого раздела просто ввести в курс работы оператора соединения и связи его с технологией соединения в SQL Server. В главах 11 и 12 мы снова рассмотрим эту концепцию.
Произведение Одной из наименее полезных и наиболее опасных операций является операция произведения. Оператор произведения берет две таблицы и строит таблицу, состоящую из всех возможных комбинаций строк по одной из каждой таблицы. В SQL-языке он обозначается выражением CROSS JOIN. SELECT FROM
I
fieldname tablenamel CROSS JOIN tablename2
Для лучшего объяснения этой операции рассмотрим следующее:
56
Фундаментальные концепции БД
Tablel:
Fred
Table2:
Sam
_>
Bugs
Wilbur
Tablel product Table2
Fred
Sam
(умноженная на)
Fred
Bugs
Ed
Fred
Ed
Wilbur
Sam
Wilbur
Bugs
Wilbur
Ed
Вы можете видеть, что число строк, которое будет возвращено оператором произведения — число строк в Tablel, умноженное на число строк в ТаЫе2. Это число может стать весьма большим, содержать двойные и бесполезные строки и может быстро задушить ваш сервер, так что оператор произведения используется не так часто.
Объединение Оператор объединения берет в качестве входных две таблицы одной и той же структуры и строит другую таблицу со всеми строками из обеих таблиц, но удаляя дубликаты: SELECT FROM Щ UNION SELECT
FROM Tablel:
fieldname tablenamel fieldname
tablename2 Fred
Table2:
Wilbur
Fred
-3
Wilbur
Tablel product Table2
Fred
(объединение с)
Wilbur Babe
Babe
Самое время напомнить вам, что две входные таблицы оператора объединения должны иметь один и тот же тип, но не обязательно они должны быть базисными таблицами. Таблица может также рассматриваться и как вторичная таблица, сформированная операцией набора, рассмотренного ранее. •
Пересечение Оператор пересечения очень близок к операторам объединения и присоединения. Оператор пересечения в значительной степени противоположен оператору объединения. Он использует две таблицы одной и той же структуры, удаляет значения, которые не присутствуют в обеих таблицах одновременно, и возвращает результирующую таблицу. Tablel:
Jeb
Table2:
Jeb
Silver
Howie
Carney
Billy
_»
Tablel intersect Table2
Jeb Carney
Carney Bob
57
Глава 3 Пересечение непосредственно не реализовано в SQL-синтаксисе. Следующий фрагмент кода показывает основной способ реализации пересечения в SQL. SELECT FROM I
fieldname tablenamel JOIN tablename2 ON tablenamel.fieldname = tablename2.fieldname
Разность Разность — операция, которая используется весьма часто при реализации системы. Точно так же, как объединение и пересечение, разность снова требует таблиц одной и той же структуры. Суть оператора разности заключается в том, что мы хотим получить каждое значение из одной таблицы, которого нет в другой таблице. Это — в противоположность оператору пересечения, который удаляет значение, если оно не находится в обеих таблицах. Разность снова не имеет соответствующего ключевого слова в SQL. Она может быть реализована наиболее простым способом, используя оператор NOT IN, как показано ниже: SELECT FROM
fieldname tablenamel
WHERE
f i e l d n a m e NOT IN (SELECT f i e l d n a m e FROM t a b l e n a m e 2 )
I
Если вы программировали на SQL, то, вероятно, заметили, что эта реализация, возможно, не оптимальна, хотя в логическом смысле это — лучшая реализация. Хотя мы обычно не должны формулировать запросы на стадии логического моделирования проекта БД, однако, если это потребуется, мы должны всегда использовать наиболее прямое представление запроса, который мы хотим сформировать.
Деление Оператор деления — один из странных операторов, который вы, вероятно, никогда не будете использовать. Он использует два отношения, одно — с единственным столбцом, другое — с двумя столбцами, и возвращает таблицу значений, которые соответствуют (в столбце, который определен) всем значениями в отношении с единственным столбцом. Нет никакого определенного способа осуществить это способом, который будет ясен в SQL, так что мы опустим это здесь из нашего обсуждения, поскольку оператор не является жизненно важным для большинства архитекторов БД и SQL-программистов.
Другие операторы Дополнительные операторы могут быть разделены на две категории — типы корректировки (INSERT, UPDATE, DELETE) и дополнительные поисковые типы. Мы не будем в этой главе обсуждать операторы корректировки, поскольку они очень близки к тому, как SQL реализует их, а также имеют очень небольшую возможность логического варьирования. Мы будем обсуждать их в некоторых деталях в главе 12. Так же как и поисковые операторы, есть другие реляционные операторы, определенные в течение почти тридцати последних лет, начиная с формулировки рассмотренных выше восьми основных операторов. Фактически все команды DML-языка (Data Manipulation
Language — язык манипулирования данными — ЯМД) в SQL Server являются или 58
i
I!
Ill
II
I
II! I
Фундаментальные концепции БД результатом, или расширением этих основных операторов. Мы должны также обратить внимание, что DDL-язык (Data Definition Language — язык определения данных — ЯОД) в основном используется для определения наших объектов, и в то время как может и не быть никакого эквивалента реляционного оператора, любой реляционный язык должен осуществить некоторую форму DDL, чтобы строить объекты для DML. Я ограничил пределы возможностей книги этими восемью исходными операторами. Цель данного раздела, посвященного реляционным операторам, состояла не в том, чтобы заменить или даже дополнить различные учебники по этой теме, а, скорее, дать представление, которое поможет нам разобраться с основами того, откуда произошли SQL-операции. Понимание основных принципов SQL поможет нам при логическом проектировании наших БД и написании кода.
Отношения Мы уже упомянули концепцию первичного ключа и внешнего ключа. Теперь поместим их рядом и посмотрим на объект, который они формируют, —отношение. Отношения — это то, что делают таблицы, которые вы успешно создали, заменяя повторяющиеся группы связью таблиц. Однако имеется один момент, который мы должны учесть при этом. Типы отношений, которые мы будем обсуждать в этом разделе, не сводятся только к форме, когда на первичный ключ одной таблицы ссылается другая таблица через внешний ключ. Если это так, то независимо от того, как все отношения будут в конечном счете физически выполнены при реализации БД, логически может потребоваться несколько отношений "первичный ключ — внешний ключ", чтобы обеспечить простые логические отношения. В этом разделе мы обсудим типы логических отношений и оставим обсуждение физической реализации на более позднее время. Отношения на этом этапе могут быть разделены на два основных типа, смысл которых нам нужно понять: Q
бинарные отношения;
Q
небинарные отношения.
Различие между этими двумя типами заключается в числе таблиц, обеспечивающих отношение. Бинарные отношения включают две таблицы, в то время как небинарные отношения включают более чем две таблицы. Это может показаться маленьким различием, но на самом деле это не так. SQL и связанные с этим отношения ограничены бинарными отношениями, в то время как в реальном мире нет никаких таких ограничений. Когда вы проектируете БД, то должны помнить и уметь представить каждую из возможных ситуаций. Когда мы будем рассматривать моделирование данных в главе 5, то обсудим, как представить каждую из них в модели данных.
Бинарные отношения Бинарные отношения — отношения между двумя таблицами. Большинство отношений, с которыми мы обычно имеем дело, подойдет под эту категорию. Число порожденных строк, которые могут участвовать с каждой из сторон отношения, называется мощностью. В этом отношении мы рассмотрим различное число элементов бинарных отношений.
59
Глава 3 Отношения "один к п" Отношения "один к п" представляют собой класс отношений, где одна таблица передает свой первичный ключ другой таблице в качестве внешнего ключа. Таблица, которая содержит первичный ключ, известна как таблица-предок, а таблица, которая получает и использует его как внешний ключ, — как таблица-потомок. Эта терминология предков-потомков рассматривается только в отношении между двумя конкретными таблицами. Потомок может иметь много предков, а предок может иметь много потомков. Отношение "один к п" — наиболее общий тип отношения, с которым вы будете иметь дело. Однако имеется несколько различных возможностей для мощности этого отношения. Можно выделить следующие варианты: •
Отношения "один к одному": мощность отношения "один к одному" означает, что для любого конкретного предка может существовать только единственный потомок. Примером этого типа отношений могло бы быть отношение House (дом) — Location (расположение), как изображено на рисунке ниже. Очевидно, что дом может находиться только в одном месте. Обсуждение того, почему все это не расположено в одной таблице, отложено на более позднее время.
Houseld (идентификатор дома) House Type (тип дома) 1001
1
1002
2
1003
1
1004
3
1005
4
Houseld (идентификатор дома) Location (расположение)
•
60
1001
Database Drive
1002
Administration Avenue
1003
SQL Street
1004
Primary Key Place
1005
Business Rules Boulevard
Отношения "один ко многим": отношение "один ко многим" является наиболее важным типом отношения. Для каждого предка может быть неограниченное число записей-потомков. Примером отношения "один ко многим" могло бы быть отношение "State (государство) — Address (адрес)", как показано на рисунке ниже. Очевидно, имеется много адресов для одного государства, хотя конкретный адрес может быть только в одном государстве.
Фундаментальные концепции БД
State Id No.(идентификатор штата)
Amabala
2
Aksala
3
Sasnakra
State Id Number
(идентификатор штата)
в.
State (штат)
1
Number of bedrooms
Address (адрес)
(число спален)
Number of bathrooms (число санных комнат)
1
402 Database Drive
1
1
1222 Administrator Avenue
2
1
2
23 Login Lane
2
1
2
246 Cardinality Close
1
1
2
893 Server Street
3
2
3
107 Query Quay
2
1
3
293 Primary Key Place
4
2
1
Отношения "один точно к п": фактически отношение "один к одному" — просто частный случай этого типа отношений. Это редко используемый тип отношений, но в подходящих случаях он может использоваться. Например, он мог бы быть использован, если пользователь может иметь только два адреса электронной почты. Следующий рисунок показывает, как отношение мощности "один к двум" могло бы использоваться, чтобы обеспечить отношение "User (пользователь) — E-mail (электронная почта)".
Employee Reference Number (номер сотрудника)
5001
•
Bob
5002
Red
5003
Jean
Employee Reference Number (номер сотрудника)
—• —•
Name (имя)
EMail Address (адрес электронной почты)
5001
[email protected] 5001
[email protected] 5002
[email protected] 5002
[email protected] 5003
[email protected] 5003
[email protected] Рекурсивные отношения: рекурсивные отношения — это те отношения, когда предок и потомок — одна и та же таблица. Этот вид отношений — то, как можно реализовать дерево, используя SQL-конструкции. Если вы реализуете рекурсивное отношение мощности "один к двум", то можете создать древоподобную структуру данных. Классический пример рекурсивных отношений — спецификация материалов. Возьмем автомобиль. Составные части и он сам могут рассматриваться как части стоимости для изготовителя, и каждая из его компонент, которая также имеет свои части, представляет часть, которая входит в целое. Некоторые из этих компонент также составлены из частей. В рассматриваемом примере автомобиль можно представить составленным рекурсивно из частей автомобиля и всех их составных частей. 61
Глава 3 Отношения "многие ко многим" Другой тип бинарных отношений (фактически второй наиболее общий тип отношений) — отношение "многие ко многим". В отличие от случая, когда имеется единственный предок и один или большее количество потомков, здесь может быть больше чем один предок и потомок. Это невозможно осуществить, используя только две таблицы, но мы оставим реализацию отношений "многие ко многим" до части книги, связанной с физическим моделированием, и главы 10. Примером отношения "многие ко многим" может быть автомобильный дилер. Возьмем любую конкретную модель автомобиля, которая продается многими различными автомобильными дилерами. Далее возьмем одного конкретного автомобильного дилера. Он, в свою очередь, продает много различных моделей автомобилей.
Небинарные отношения Небинарные отношения включают в отношение больше двух таблиц. Это бывает гораздо чаще, чем можно было бы ожидать. Например: Wrox снабжает книгами продавцов книг, которые обеспечивают их продажу. Оно называется тройным отношением, потому что включает три таблицы. Когда мы имеем дело с такими отношениями, то разбиваем их на три таблицы с двумя или большим количеством отношений. •
Wrox снабжает книгами.
•
Продавцы книг продают книги.
•
Продавцы книг снабжаются издательством Wrox.
Однако из этих трех отношений вы не можете точно вывести исходное тройное отношение. Все, что вы можете вывести, это: •
Wrox снабжает книгами кого-то.
•
Продавцы книг продают книги, которыми их снабжает какое-то издательство.
•
Wrox обеспечивает чем-то продавцов книг.
Из этих отношений вы не можете точно вывести исходное утверждение, что Wrox снабжает книгами продавцов книг. Это — обычная ошибка, которую делают многие архитекторы данных. Исходное отношение будет, вероятно, идентифицировано на стадии проектирования, а затем не будет реализовано должным образом, когда вы начнете разрабатывать отношения. Надлежащее решение этого вида задачи состояло бы в том, чтобы иметь таблицу для издателей (Wrox), таблицу для изделий (книги) и таблицу для магазинов (продавцы книг). Тогда вы имели бы таблицу с тремя бинарными отношениями, по одному на каждую из этих таблиц. Это решает проблему (пока). В главе 7 на основе расширенных правил нормализации мы будем иметь дело с проблемами, которые возникают, когда вы должны хранить отношения с более чем двумя таблицами. 62
Фундаментальные концепции БД
Последнее замечание относительно реляционной/SQL терминологии В этой книге мы будем придерживаться терминологии SQL-стиля кроме случаев, где это не имеет смысла. Важно понять основные принципы реляционной теории, однако не нужно смущаться и тому, кто не прочитал эту главу или серьезно не изучал БД. SQL-терминология может и не быть совершенной (и действительно, это так), и так думает большинство программистов, даже те, кто являются приверженцами реляционной теории.
Определения существенных понятий Прежде чем завершить эту главу, описывающую фундаментальные понятия БД, следует рассмотреть пару дополнительных понятий. Они будут очень важны позже в процессе проектирования (и, возможно, более ясными), но представление о них важно дать в этой главе.
Функциональная зависимость Термин функциональная зависимость является одним из тех терминов, которые выглядят более сложными, чем есть на самом деле. Это — фактически очень простое понятие. Если мы определяем функцию для какого-то одного значения (назовем eroValuel), а результат этой функции всегда одно и то же значение (скажем, Value2), то Value2 функционально зависит от Value 1. Это довольно простое, но все же важное понятие, которое нужно представлять. Правильно реализованные функциональные зависимости — очень полезные вещи, в то время как неподходящие функциональные зависимости являются основой многих проблем БД. Существует один дополнительный запутывающий термин, который связан с функциональной зависимостью. Этот термин —детерминант, который может быть определен как "любой атрибут или комбинация атрибутов, от которых функционально зависит любой другой атрибут или комбинация атрибутов". Так, в нашем предыдущем примере, Value 1 можно было бы рассматривать как детерминант. На ум приходят два примера: •
Рассмотрим математическую функцию типа 2 * X. Для каждого значения X будет получено конкретное значение. Для 2 мы получим 4, для 4 мы получим 8. Каждый раз, когда мы помещаем в функцию значение 2, мы всегда будем получать 4, так что в этом случае 2 функционально определяет 4 для функции 2 * X.
•
В более связанном с БД примере рассмотрим серийный номер изделия. Из серийного номера может быть получена дополнительная информация типа номера модели и других конкретных характеристик изделия. В этом случае серийный номер функционально определяет ряд конкретных характеристик.
Если это вам кажется знакомым, то потому, что любой ключ таблицы функционально определяет другие ее атрибуты, и если вы имеете два ключа, один из которых — первичный ключ, а другой — вторичный ключ, то первичный ключ функционально определяет вторичный ключ и наоборот. Это понятие будет самым важным при обсуждении нормализации в главе 6. 63
Глава 3
Зависимость с несколькими значениями Интуитивно менее понятной является зависимость с несколькими значениями. В нашем предыдущем разделе мы обсуждали функциональную зависимость в случае единственного значения. В качестве иллюстрации зависимости с несколькими значениями рассмотрим факт, что эта книга была написана одним автором, так что, если бы мы определяли функцию автора этой книги, мы каждый раз должны были бы иметь единственное значение. Однако если бы существовала функция технического редактора, она возвратила бы много значений, так как у этой книги несколько технических редакторов. Следовательно, технический редактор для книги имеет несколько значений. Мы снова столкнемся с понятием функциональной зависимости позже, в главе 6.
Резюме В этой главе мы проскочили тридцать лет исследований всего с помощью нескольких страниц. Мы определили многие из терминов и понятий, которые являются очень распространенными как в нашей нынешней корпоративной прагматической культуре, так и в кругах "чистых" исследователей. Мир нуждается в обеих из этих групп, чтобы работать вместе. Мы также посмотрели на некоторые основы построения реляционного языка программирования SQL. В следующей таблице мы установим соответствие между общими терминами БД SQL Server и их синонимами из реляционной теории и, в меньшей степени, терминологией логического проектирования: Общий термин
Синоним
Таблица
Столбец
Сущность (конкретно для логического проектирования), отношение (чаще в теоретических дискуссиях) Атрибут
Строка
Кортеж
Допустимые значения, которые могут быть размещены в столбце, учитывая тип данных, ограничения и т. д. Число строк
Домен
Число столбцов
Степень
Мощность
Моя искренняя надежда — что данная глава представит эти темы на уровне технической конференции, что, по крайней мере, даст вам достаточное теоретическое понимание, чтобы сделать проектирование БД понятным, или, что лучше, стимулирует вас пойти и прочитать больше о предмете, чтобы вы полностью поняли всю ту информацию, которую я пробежал слишком быстро. Конечно, мое реальное опасение — что сам Е. Ф. Кодд прочитает мою книгу и придет в мой дом, чтобы отругать меня за то, что, потратив годы и проштудировав большое количество страниц хорошо обдуманных записей и исследований, я превратил их в двадцать страниц или около этого. В этом русле я надеюсь, что вы будете эту тему изучать и далее, так как она позволит вам стать хорошим архитектором/программистом БД. Сделав краткую теоретическую остановку во время путешествия в страну БД, мы возвращаемся к изучению нашей темы, поскольку действительно начинаем сам процесс проектирования. 64
.4 ^ n
Сущности, атрибуты, отношения и бизнес-правила Введение Теперь, посмотрев на основные блоки здания БД, мы должны начать идентифицировать эти блоки здания в рамках предварительной информации, которую мы собрали в главе 2. Я допускаю, что такой подход выглядит излишне осторожным, но это — "лучшая практика", однако многие квалифицированные программисты, вероятно, будут иметь свой план. Я так же на это смотрю; однако результаты, которые я получаю, всегда гораздо лучше, когда я выполняю эти предварительные шаги перед началом проектирования новой БД. Чем больше мы будем следовать этому пути при проектировании, тем лучшее будет результат. Методы, описанные в этой главе, помогут вам избежать опасности увеличения затрат на обслуживание проекта, уменьшить частоту, с которой вы должны будете потом возвращаться, чтобы изменить структуры и код, и вообще уменьшать риск неудачи. В главе 2, где описывается стадия сбора информации, мы собрали массу информации, которая в настоящий момент загромождает ваш стол. В этой главе добавим к этой информации еще структуру и затем взглянем на все интегрально. Мы пройдем все — идентификацию и назначение начальных названий таблицам, отношениям, столбцам и бизнес-правилам — конечно же, при этом мы консультируемся с нашими пользователями для получения любой дополнительной информации. Закончив этот процесс, мы будем иметь основную документацию, которая представлена все еще в формате, когда можно делиться информацией с пользователем без бремени технических терминов и диаграмм (которые мы начнем разрабатывать позже).
Глава 4
Отказ от выбора окончательной структуры на ранних стадиях Для тех читателей, кто плохо знаком с проектированием БД, или кто даже не создал ни одной таблицы, принципы, рассмотренные в этой главе, должны выполняться, чтобы гарантировать лучший результат для полученного проекта. Если вы уже знаете, как создать таблицу, то могли бы подвергнуть сомнению упрощенный характер того, как мы рассуждаем в этой главе. Вы уже можете иметь некоторые идеи того, какие таблицы следует разбить и на какие составные части. Однако при выполнении таких действий на ранних этапах процесса проектирования, что может казаться выгодным для сокращения времени разработки, вы заплатите за это в полной мере, когда должны будете возвратиться для корректировки БД, вставляя, например, столбцы, потому что вы пренебрегли необходимостью потратить время для рассмотрения вашей стратегии с клиентом. Не забудьте также продолжать корректировать вашу документацию, так как лучшая в мире документация бесполезна, если она устаревшая. Как только вы создадите одну БД или две (или две сотни таких БД), то, вероятно, поймете, что очень трудно отказаться от досрочного выбора окончательной структуры. Когда вы слушаете клиента, объясняющего, что он хочет, то, вероятнее всего, представляете набросок структуры даже до того, как он выскажет все, что хочет; это будет почти вашей второй натурой. К сожалению, это — не лучшее, что можно сделать на этой стадии развития/организации процесса проектирования БД. Вы должны до поры, до времени игнорировать эти организационные поползновения. Независимо от того, с помощью каких средств вы фактически объединяете информацию (средств создания ER-диаграмм (ER-диаграммы — диаграммы сущностей и отношений), средств объектно-ориентированного представления структур, текстового процессора или даже карандаша и бумаги), — со стремлением построить БД на этом этапе нужно бороться. Почему же? Ответ очень прост. В вашем рвении собрать воедино создаваемую БД, вероятно, будет пропущена ценная информация, не будут выполнены, по-видимому, и некоторые важные шаги, которые мы обсудим в следующих главах. На этом этапе вы должны ограничить себя только рассмотрением собранной документации для определения следующих моментов: •
сущности и отношения;
•
атрибуты и домены;
•
бизнес-правила.
Делая это, вы, вероятно, завершите работу с намного меньшей грудой документации, возможно, представляющей десять процентов от первоначального объема, но будете все еще уверены, что не пропустили никакие важные данные. В этот момент вы можете гарантировать получение информации от пользователя до того, как начнете применять очень жесткую последовательность операций, рассмотренную в следующих трех главах, чтобы выполнить процесс разработки. Мы упомянули в главе 3, что с этой главы и далее будем использовать ориентируемые на реализацию термины, чтобы описать объект, когда это имеет смысл. В этой главе мы используем термины "Сущности" и "Атрибуты", поскольку будем искать объекты, которые никогда не могут появиться как физические структуры. Мы начнем использовать ориентируемые на реализацию термины, как только станем переходить к структурам, напоминающим то, что обычно называется таблицами и столбцами. 66
Сущности, атрибуты, отношения и бизнес-правила Простой пример Проиллюстрируем то, что вы только что прочитали, следующим примером, который мог легко оказаться фрагментом записей реального процесса проектирования.
'Имеются ЗакаЗШки, котфым они продают подфэ/санные иЗделия, обы1но офисное оборудование, но такэ/се и мебель. Они идмжифиуи^/ют ЗакаЗЬков номфом, ффми^/еммм cyijjecm6ymije4 системой. Они makojce, продают новые вепрь, но подффшние ~ это то, на^ км они Зарабатывают. Скта-фактфи iwiarmwmm в^се-тделънл, но поставки делаются немедленно, как только ЗакаЗЫк делает ЗакаЗ, если ему довфяют (нет никаких кредитных тфоблем). У1юбш иЗмтения скта-фактфм долфнм быть Задокумштм^юванм. Здсе изделия отправляются со склада в i/етфе города. 'Нуфно поместить инффмаз/ию о контактах для kaojakm ЗакаЗЫка — телефонные тмфа (факс и голосоЫь), adftec п адреса элёятфтнои* тюЬпм. ЯфтсоофаЗно иажъЗоватъ факс или электронную noimy для фшниЗауии nodmflefo/cdmuii. 'Желательно такфе иметь с£о(юдную область для контактов, т. к. они могут бести ффшш контактов с ЗакаЗЫкамн.
В каждом из последующих разделов, мы будем просматривать эту документацию, чтобы выявить нужную информацию. Звучит достаточно просто, а? Это, действительно, намного легче, чем могло бы показаться.
Определение сущностей Сущности, действительно, одни из наиболее простых для выявления объектов, когда вы просматриваете документацию. Они обычно представляют людей, места, объекты, идеи или вещи, которые грамматически выступают как существительные. Это не означает, что сущности отражают только реальные вещи. Ими могут быть также абстрактные концепции, например, "Новые изделия" является абстрактной концепцией, которая не является физической, но представляет удобный способ группировать диапазон изделий. Фактически, более разумным определением того, что такое сущность, может быть то, что она используется для размещения всей наглядной информации, необходимой для полного описания отдельного человека, места, объекта или идеи. Например: Q
Человек: может представлять студента, служащего, водителя и т. д.
•
Место: город, здание, дорога и т. д.
•
Объект: деталь, инструмент, электронный блок и т. д.
•
Идея: документ с требованиями, группа (подобно группе безопасности для приложения), журнал действий пользователя и т. д.
Обратите внимание, что имеется наложение нескольких из категорий (например, здание — "место" или "объект"). Вам редко будет требоваться дискретно идентифицировать, что 67
Глава 4 представляет собой сущность — человека, место, объект или идею. Однако если вы можете разместить ее в пределах конкретной группы, это может помочь назначить некоторые атрибуты, например "Имя" для человека или "Адрес" для здания. Для тех, кто имеет опыт работы с реляционными БД, будет ясно, что когда сущность реализуется в фактическую таблицу БД, она будет, вероятно, очень отличаться от начальных сущностей, которые вы определили. Важно не волноваться относительно этого на данной стадии процесса проектирования — мы должны постараться не заниматься серьезно в данный момент возможной реализацией БД. Причина, почему мы заинтересованы различать людей, места и объекты — то, что это поможет определить некоторую основную информацию, которая потребуется позже, когда начнем заниматься в данной книге реализацией. Это также поможет нам быть уверенными, что мы имеем всю документацию, необходимую для описания каждой сущности.
Люди Почти каждая БД должна будет хранить информацию относительно некоторого вида сущностей, представляющих людей. Большинство БД будет иметь, по крайней мере, какое-то упоминание пользователя. В то время как не все пользователи — люди, о пользователе почти всегда думают как о человеке, хотя пользователем могла бы быть корпорация или даже машина, наподобие сетевого принтера. Что же касается реальных людей, в отдельной БД может потребоваться хранить информацию о многих людях. Например, БД школы может иметь в качестве сущностей учащихся, учителей и администрацию. Может также потребоваться, чтобы только одна таблица представляла все три категории. Это — вопрос, который мы рассмотрим поподробнее в более поздних примерах. В нашем примере можно выделить две сущности людей — "Контакты" и "Заказчики":
нуфно поместить инффмаумю о контактах для kaojaim ЗакаМнка ... ... ojcyfmtu контактов ... Информация о партнерах и заказчиках, которую нам нужно хранить, должна, однако, быть описана, и мы на этой стадии процесса документирования не можем знать, действительно ли они будут рассматриваться как отдельные сущности. Мы рассмотрим различные атрибуты партнеров и заказчиков а разделе этой главы, касающемся атрибутов.
Места Существует много различных типов мест, относительно которых пользователи пожелают хранить информацию. В нашем примере записей имеется одна очевидная сущность, связанная с местом:
иЗдвлт со склада 6 уенпфе города. Обратите внимание, что партнеры и заказчики, очевидно, имеют почтовые адреса, определяющие их местоположение. 68
Сущности, атрибуты, отношения и бизнес-правила
Объекты Объекты относятся, прежде всего, к физическим предметам. В нашем примере мы имеем три различных объекта:
к)м(фым они продают подфо/сатыв нЗделил, otmluo офисное оборудование, но такфе и мебелг Изделия, офисное оборудование и мебель — объекты, но заметим, что мебель и офисное оборудование — также изделия. Однако вас не должно шокировать, что изделия, офисное оборудование и мебель будут иметь очень разные атрибуты. Можно ли объединить эти три объекта в одну абстрактную сущность, называемую изделиями, или будем рассматривать их как три отдельные сущности, будет полностью зависеть от того, с какой целью вы разрабатываете конкретную БД.
Идеи Нет никакого правила, требующего, чтобы сущности были реальными объектами или даже физически существовали. На этой стадии разработки мы должны будем рассмотреть информацию, которую пользователь хочет хранить об объектах, которые не соответствуют уже установленным категориям "людей", "мест" и "объектов" и которые могут быть или не быть физическими объектами.
Документы Для многих термин "документы" обычно ассоциируется с осязаемыми листами бумаги, содержащей информацию, которую клиенты должны отслеживать. Это может выглядеть как спор о мелочах, но это не то же самое. Что, если мы сделаем копии листов бумаги? Означает ли это, что имеются два документа, или оба они — тот же самый документ? В нашем примере мы действительно просто ссылаемся на большое число листов бумаги:
. Clem-cfia'kmyfm mlamawmcu ... Счета-фактуры — листы бумаги, которые отправляются заказчику после поставки изделий. Однако мы только знаем, что счета-фактуры напечатаны физически на бумаге, нокогда они отправлены или даже как они посланы, на этой стадии неизвестно. На этой стадии мы только идентифицируем сущности и двигаемся дальше; опять мы выдвигаем предположения относительно того, как данные будут использоваться позже в этом процессе.
электронную mimy для о^шннЗауш тдтв(фф)внн4 ... Здесь мы имеем тип документа, который не написан на бумаге — сообщение электронной почты.
Другие сущности На этой стадии разработки мы должны будем рассмотреть информацию относительно сущностей, которые пользователи хотят хранить и которые не соответствуют уже установленным категориям "людей", "мест", "объектов" и "документов". 69
Глава 4 Протоколы ревизий и журналы БД Типичная сущность, которую вам нужно будет определить — протокол ревизии или журнал БД. Это — не обычная сущность, в которой не хранятся никакие данные пользователя и, по существу, должна вообще рассматриваться на физической стадии проектирования. Единственные виды сущностей, которые мы рассматриваем на этой стадии — это те, о которых пользователь желает хранить данные. Аналогично и операторы, наподобие следующего, не нужно рассматривать на этой стадии, а следует оставить до реализации:
Любые иЗменения dema-фактфп долфны быть Задокументированы
р
События Сущности-события обычно представляют собой глаголы или действия.
... поставки делаются немедленно, как только ... Записываются ли поставки в какой-либо физический документ, на данном этапе неясно: выражение лишь указывает, что событие имеет место. Эти события важны, и пользователи захотят вести их список или формировать данные, связанные с возникновением событий. В нашем примере, может быть, мы будем документировать события после того, как они произойдут, так что это может вылиться в протокол сделанных поставок. Другим типом событий для вас могут быть показания счетчика, погодные данные, измерения приборов и т. д. Записи и журналы Последний из типов сущностей, которые мы исследуем на этой стадии — запись или журнал действий. Заметьте, что я имею в виду записи, не находящиеся в БД. Это может быть любой вид деятельности, при котором пользователь мог бы предварительно сделать записи на бумаге. В нашем примере пользователь хочет хранить записи о каждом сделанном контакте с заказчиком.
... могут 9Шт ффнал контактов о Зака31пкамн. Это другой тип сущностей, который подобен контрольному протоколу, но потенциально он будет содержать больше информации, — такой как замечания относительно контакта, а не только то, что контакт имел место.
Список сущностей Пока мы определили следующий список предварительных сущностей.
70
Сущности, атрибуты, отношения и бизнес-правила
Сущность
Описание
Контакт
Люди, выступающие в качестве заказчиков, и с которыми следует установить контакт
Заказчик
Организация или конкретное лицо, которым продаются изделия, офисное оборудование и/или мебель
Склад
Место, где размещаются товары для продажи
Поставки
События, когда проданные товары поставляются заказчику
Изделия
Товары для продажи
Офисное оборудование
Тип изделия
Мебель
Тип изделия
Счет-фактура
Физический документ, который отправлен заказчику, чтобы запросить оплату за проданные изделия
Подтверждение размещения изделия
Электронный документ (возможно, это единственный формат), который позволяет сообщить заказчику, что заказ размещен
Журнал
Распечатка всех контактов
Бухгалтерский журнал
Фактическая запись контакта с заказчиком
Замечание для физического моделирования: регистрировать любые изменения счетов-фактур Описания основаны на фактах, которые мы тщательно выбрали из исходной документации. Обратите внимание, что в описании контакта мы не стали делать никаких предположений относительно того, как используются данные, в то время как полностью описаны главные моменты, основанные на том, что было предварительно согласовано. Теперь мы имеем список всех сущностей, которые были определены в нашей предыдущей документации. На этой стадии мы постоянно просматриваем то, что сказал нам клиент. Когда же мы начинаем анализировать то, что было определено, то можем заняться деталями, которые клиент нам не дал. Вместо того чтобы заполнять бреши предположениями и догадками, нам нужно (в идеале) подготовить список вопросов и передать их деловому аналитику, который повторно посетит клиента, чтобы извлечь большее количество информации: •
У вас имеется только один склад?
•
Ваши заказчики — отдельные лица или большие сообщества?
•
Имеются другие типы изделий, которые вы продаете? Или мебель и офисное оборудование настолько важны, что вы должны хранить специальную информацию относительно них?
71
Глава 4
Определение атрибутов и доменов Основная причина использования здесь термина "атрибуты" вместо термина "столбцы" заключается в том, что мы будем искать элементы, которые идентифицируют, составляют, и описывают сущность, которую вы пытаетесь представить; либо, выражая это в компьютерных терминах — свойства сущности. Например, если сущность — "Человек", атрибуты могли бы включать "Номер водительских прав", "Номер социального страхования", "Цвет волос", "Цвет глаз", "Вес", "Супругу" ("Супруга"), "Детей", "Почтовый адрес" и "Адрес электронной почты". Каждая из этих характеристик служит, чтобы представить какую-то сторону сущности. Однако из нашего определения того, что должен хранить столбец, мы знаем, что это должна быть единая часть информации, хотя для атрибута такого требования нет. Идентификация, которая может связать атрибуты с сущностью, требует различного подхода, исходя из определений самих сущностей. Атрибуты часто находятся как прилагательные, используемые для описания сущности, которую вы предварительно нашли. Поскольку информация о домене данных обычно определяется в то же самое время, когда и атрибуты, мы также посмотрим и на них.
Идентификаторы Причина, почему мы начинаем с идентификаторов, заключается в том, что они являются наиболее важными. Каждая сущность должна иметь по крайней мере один идентифицирующий атрибут или набор атрибутов. Без атрибутов нет никакой возможности далее в процессе определять различные объекты. Этими идентификаторами является то, что мы определили как ключи в главе 3. В вышеупомянутом примере один такой идентификатор показан следующим образом:
Они идттифпурфуют ЗакаЖнков номером, формируемым существующей системой1. Почти для каждой сущности, которую вы разрабатываете на этой стадии проектирования, легко найти какой-либо идентификатор. Причина этого кроется в том, что на этой стадии вы имеете дело с сущностями, которые легко выбрать, потому что они имеют естественные идентификаторы. Важно также быть уверенным, что то, о чем вы думаете как об уникальном элементе, действительно является уникальным. Взгляните на имена людей. Для нас они почти уникальны, но на самом деле в Соединенных Штатах имеются сотни Луисов Дэвидсонов, а это не самое частое имя. Имеются тысячи, если не миллионы, Джонов Смитов! Вот некоторые общие примеры хороших идентификаторов:
72
•
Для людей — номера социального страхования (в США); полные имена (не всегда совершенный идентификатор); или другие числовые идентификаторы (например, номера заказчиков, табельные номера служащих и т. д.).
•
Для деловых документов (счета-фактуры, счета, формируемые компьютером уведомления) — они обычно определяются некоторым назначенным числом, когда посылаются другим организациям.
Сущности, атрибуты, отношения и бизнес-правила
Q
Для книг — ISBN-номера (сами названия не уникальны).
•
Для изделий на продажу — номера изделий (сами названия изделий не уникальны).
•
Для компаний, с которыми клиенты имеют дело — они обычно задаются номером заказчика/клиента для отслеживания.
•
Для зданий — полный адрес, включая индекс/почтовый код.
•
Для почтовых корреспонденции — имя и адрес адресата и дата отправки.
Имеется много других примеров, но в данный момент вы должны понять, что мы подразумеваем, когда говорим об идентификаторах. Вспомнив реляционную модель, которую мы обсуждали в главе 3, каждый экземпляр отношения (или строка, если вы желаете) должен быть уникальным. Определение уникальных естественных ключей для данных — самый первый шаг в реализации проекта. Некоторые из идентификаторов, перечисленных выше, не могут гарантировать, что будут уникальными; например, в случае номеров компаний и деловых документов идентификаторами могли бы быть последовательные числа, которые не являются естественными ключами. Однако во многих случаях эти значения — разумные ключи: это означает, что в них заключена и другая важная информация. В большинстве случаев разумные ключи могут быть разбиты на части. В некоторых случаях, однако, данные, вероятно, ничего не будут вам означать. Возьмем следующий пример серийного номера изделия: XJV 10229392: •
X — тип изделия — телевизионная трубка;
Q
JV — подтип изделия — 32-дюймовая консоль;
•
102 — номер партии, означающий, что изделие было произведено в 102-ой партии;
•
293 — день года;
•
9 — последняя цифра года;
CJ
2 — цвет.
Разумные ключи имеют свою цель, но при логическом проектировании мы должны найти все части информации, которые в них входят.
Описательная информация Описательная информация — один из самых легких типов для определения. Прилагательные, обычно описывающие предметы, которые были предварительно идентифицированы как сущности, довольно распространенны и обычно указывают непосредственно на атрибут. В нашем примере мы имеем различные типы изделий: новые и подержанные.
... nftofawm подерфанние иЗдвлпя ... Они mafa/ce продают новые вещи Главное здесь заключается в том, что мы теперь идентифицировали возможный домен атрибута. В этом случае атрибут — "Тип изделия", а домен, вероятно, будет включать "Новый" и "Подержанный". Конечно, возможно, впоследствии это может быть преобразовано в два атрибута — "Является новым" и "Возраст изделия" или, может быть, единственный атрибут — "Год изготовления".
73
Глава 4 В следующем примере мы видим, что должны будем иметь атрибут, который определяет кредитные способности заказчика:
... как только SakaSiuk делает ЗакйЗ, если ему доверяют (нет никаких кредитных проблем)^ Как только будет выяснено, есть ли у заказчика проблемы с кредитом, мы определенно будем хотеть в этот момент задать вопрос, что нам нужно знать еще относительно того, как БД может облегчить процесс управления кредитоспособностью. С другой стороны, следующий пример:
... телефонные номера (факс и голосовой), адрес ..^ дает впечатление, что мы имеем сущность "Контакт", которая имеет атрибут "Номер телефона" с доменом, состоящим из значений "факс" и "голосовой". Здесь уместно вставить небольшое напоминание, что одним из главных различий между столбцом и атрибутом является то, что столбец является физическим представлением единственного элемента информации, размещенной в той же самой физической сущности или таблице, в то время как атрибут может быть физически размещен и не в той же самой таблице.
Указатели на расположение Указатели на расположение используются как способ разместить что-либо, начиная от физического ресурса и кончая классификацией, а также чтобы дифференцировать и размещать значения в таблице. Пример указателя на расположение приведен ниже:
(Все изделия отправляются со склада в центре Здесь мы имеем местоположение склада. Склад может иметь только один адрес, так что это определенный указатель на расположение. Обратите внимание, что из этого фрагмента фразы не ясно, имеются ли другие склады. Одно очевидно, что мы имеем по крайней мере один склад, который находится в центре города. В следующем примере мы имеем четыре очень типичных указателя на расположение:
... поместить информацию о контактах для кафдого ЗакаЛ1ика — телефонные номера (факс и голосовой), адрес и адреса электронной noimu. Большинство сущностей, которые связаны с организациями, будет иметь много атрибутов телефонных номеров, адресов и адресов электронной почты. В нашем примере мы имеем четыре атрибута для сущности "Контакт". Дальнейший обзор позволит лучше определить все фактические потребности, когда дело дойдет до этой информации, но это — довольно хороший перечень того, что важно для пользователя.
74
Сущности, атрибуты, отношения и бизнес-правила
Вы можете задаваться вопросом, почему мы думаем об адресе как единственном атрибуте, когда вы хорошо знаете, что "адрес" состоит из номера квартиры, улицы, города, штата, почтового индекса — нескольких атрибутов. Ответом будет то, что все они составляют адрес, что мы мыслим обо всех них как об единственном атрибуте, и этого нам достаточно. В это будет вложено больше смысла позже, когда мы начнем процесс структуризации, но пока будет достаточно представлять, что когда пользователь видит слово "адрес" в контексте нашего примера, он думает о понятии адреса, используемого для задания физического местоположения. Таким образом вы можете избежать любого обсуждения того, как адрес фактически сформирован, не говоря уже обо всех различных форматах адреса, с которыми мы, вероятно, должны будем иметь дело, когда займемся физической реализаций атрибута "Адрес" позже в этой книге. Последний атрибут, который мы рассмотрим, вытекает из следующего предложения:
ЩелесообраЗно исполъЗоватъ факс нлн электронную noimy для оршниЗауш подтвфусдений^ Атрибут "Факс или электронная почта" относится к механизму поставки, для того чтобы определить, что мы будем использовать для передачи подтверждения заказа изделия. Мы будем, конечно, хотеть иметь записи того, как и когда послали изделия заказчику. Следовательно, нам будет нужен атрибут, описывающий механизм поставки, в таблице подтверждения заказа изделия.
Связанная информация В некоторых случаях (хотя и не в нашем примере) мы будем иметь связанную информацию, которая потребуется в виде атрибутов для наших сущностей. Некоторые примеры этого следующие: •
Дополнительные материалы — пусть где-нибудь вы имеете сущность, которая описывает книгу или какой-нибудь другой тип средств информации (предположим, Amazon.com); вы, вероятно, захотите внести в список дополнительные ресурсы, которые пользователь может также посмотреть.
•
Контакты — мы уже рассматривали таблицу контакта, но контакт также представляет собой технически и атрибут сущности. Независимо от тогб, захотите вы или нет, чтобы контакт был отдельной сущностью на данном или более поздних этапах, окончательная БД будет выглядеть одинаково, потому что процесс нормализации (главы 6 и 7) гарантирует, что атрибуты, которые на самом деле являются сущностями, будут соответственно реструктурированы.
•
Web-сайты, FTP-сайты (FTP (File Transfer Protocol) — протокол передачи файлов) или другие различные Web-ресурсы — вы должны будете часто идентифицировать сущности "Web-сайт" или "URL-ресурс" (URL (Uniform Resource Locator) — унифицированный указатель информационного ресурса, фактически адрес в Интернете), которые определяются как сущности; такая информация могла бы быть определена и как атрибуты.
Список сущностей, атрибутов и доменов Следующая таблица показывает сущности совместно с описаниями и доменами столбцов. Атрибуты сущности помещены с отступом в пределах столбца сущностей. 75
Глава 4
Сущность
Описание
Контакт
Люди, выступающие в качестве заказчиков, и с которыми следует установить контакт
Номер факса
Номер телефона, чтобы посылать факсимильные сообщения заказчику
Любой подходящий номер телефона
Номер голосового телефона
Номер телефона для голосовой связи
Любой подходящий номер телефона
Адрес
Почтовый адрес контактного лица
Любой подходящий адрес
Адрес электронной почты
Адрес электронной почты контактного лица
Любой подходящий адрес электронной почты
Заказчик
Организация или конкретное лицо, которым продаются изделия, офисное оборудование и/или мебель
Номер заказчика
Ключевое значение, которое используется для идентификации заказчика
Неизвестный
Надежность кредита?
Сообщает нам, можно ли отправить заказчику изделия немедленно
True, False
Склад
Место, где размещаются товары для продажи Нахождение •
Определяет, какой склад имеется в виду, — только один может быть в этот момент, так как определено только одно значение домена
Поставки
События, когда проданные товары поставляются заказчику
Изделия
Товары для продажи
Тип изделия
76
Домен столбца
Определяет различные типы изделий для продажи
Офисное оборудование
Специальный тип изделия на продажу
Мебель
Специальный тип изделия на продажу
"В центре города"
" Подержанное ", "Новое"
Сущности, атрибуты, отношения и бизнес-правила
Сущность
Описание
Счет-фактура
Физический документ, который отправлен заказчику, чтобы запросить оплату за проданные изделия
Подтверждение размещения изделия
Электронный документ (возможно, это единственный формат), который позволяет сообщить заказчику, что заказ размещен
Тип механизма передачи
Определяет, как подтверждение заказа изделия передается заказчику
Журнал
Распечатка всех заказчиков
Бухгалтерский журнал
Фактическая запись контакта с заказчиком
Домен столбца
"Факс", "Электронная почта"
Замечание для физического моделирования: регистрировать любые изменения счетов-фактур Обратите внимание на использование термина "Любой подходящий". Возможности этих формулировок должны быть уменьшены до разумной величины. Многие БД, которые хранят телефонные номера и адреса, не могут справляться со всеми возможными форматами, используемыми в разных регионах мира, главным образом, потому что это не такая простая задача. На этой стадии мы определили список сущностей и атрибутов, которые нам удалось выделить. Заметьте, что при этом процессе мы все еще не начали добавлять что-нибудь к проекту. Интересно было бы также обратить внимание, что мы имеем документ почти в страницу, а всего-навсего проанализировали два маленьких параграфа текста. Когда вы это сделаете в реальном проекте, окончательный документ будет намного больше, и в документации будет, вероятно, весьма немного избыточности. Вы будете без сомнения определять нужные данные лучше, чем мы делали на предыдущих немногих страницах!
Отношения между таблицами Наиболее важные решения, которые вы будете принимать относительно структуры БД, будут обычно связаны с отношениями.
Отношения "один к п" В любом отношении "один к п" (то есть, "один к одному" или "один ко многим") таблица, которая является таблицей "один", рассматривается как предок, а "п" является потомком или потомками.
77
Глава 4
Отношение "один к п" часто используется при реализации, но редко встречается на ранних этапах проектирования БД. Причина этого заключается в том, что большинство естественных отношений, о которых сообщит вам пользователь, оказывается отношением "многие ко многим". Имеются два известных типа отношений "один к п", которые мы должны обсудить. Они на самом деле весьма просты, и опытный проектировщик БД выявит их немедленно.
Отношение "имеет" Основной специальный тип отношения — отношение "имеет". Оно названо так, потому что таблица-предок в отношении имеет одну или большее количество сущностей-потомков, используемых как атрибуты родителя. Фактически, отношение "имеет" — способ задания атрибута, который часто используется неоднократно. Некоторые примеры: •
адреса;
•
номера телефонов;
G
дети.
В разделе с нашим примером мы имели:
... поместить информацию о контактах для кяа/сдж ЗакаЗЫка ... В этом случае мы имеем сущность "Заказчик", которая имеет одного или большее количество контактов. Вы будете встречать этот тип отношения неоднократно; обратите также внимание, что это является основой для решения, получаемого при использовании четвертой нормальной формы, которую мы рассмотрим в главе 6. Другой пример отношения "имеет" приведен в следующем примере:
... для контактов, т. к. они могут вести фурнал контактов с ЗакаЗШкамг В этом случае партнер имеет несколько вхождений в журнал. С другой стороны невероятно, что вход журнала будет связан со многими партнерами.
Отношение "является" Другой специальный случай отношения "один к п" — отношения "является". Суть отношения "является" заключается в том, что сущность-потомок в отношении расширяет сущность-предка. Например, автомобили, грузовики, железнодорожные средства — это все типы транспортных средств, так что автомобиль является транспортным средством. Мощность такого отношения всегда "один к одному", поскольку сущность-потомок просто содержит более определенную информацию, которая определяет это расширенное отношение. Причина для этого вида отношений концептуальна. Может быть некоторая информация, которая является общей для каждой сущности-потомка (размещенная как атрибуты сущности-предка), а другая информация является специфичной для каждой конкретной сущности-потомка (размещенная как атрибуты сущности-потомка).
78
Сущности, атрибуты, отношения и бизнес-правила
В нашем примере, мы имеем следующую ситуацию:
... продают подффмпш иЗделнл, ofhaiuo офисное околдование, но mfo/ce и мебель, В этом примере мы имеем сущность "Изделие", которая является достаточно общей. Сущность "Изделие" вряд ли будет содержать много информации относительно того, что представляет собой офисное оборудование, или из чего сделан предмет мебели. Это приближается к концепции наследования в объектно-ориентированной БД. Обратите внимание: отношение "является" несколько похоже на подкласс в объектно-ориентированном программировании. В реляционных БД нет никакого понятия наподобие подкласса, хотя это может измениться в следующих SQL-спецификациях. Мы обсудим эту тему дальше, когда приступим к моделированию БД.
Отношения "многие ко многим" Отношение "многие ко многим" используется гораздо более часто, чем вы могли бы представить. Фактически, поскольку мы начинаем детализировать наш проект, то будем использовать его для большей части наших отношений. Однако на данной стадии будет распознано только очень немного отношений "многие ко многим". В нашем примере имеется одно такое очевидное отношение:
, 'которым они продают подфо/саннш нЗделия ... Если мы используем простое отношение "один ко многим", то были бы привязаны либо к одному заказчику, который приобретает разные изделия, или к тому, что каждое изделие может быть приобретено единственным заказчиком. С другой стороны, при таком отношении одно изделие может быть продано многим заказчикам, но каждый заказчик может приобрести только единственное изделие. Очевидно, что ни один из этих вариантов не реалистичен. Многие заказчики могут приобрести многие изделия, и многие изделия могут быть проданы многим заказчикам.
Список отношений Давайте посмотрим на документ, с которым мы работаем, еще раз, на сей раз удалив атрибуты, добавленные в предыдущем разделе, чтобы сэкономить место и сделать более ясным то, что мы сделали.
4
1868
7 9
Глава 4
Сущность
Описание
Контакты
Люди, выступающие в качестве заказчиков, и с которыми следует установить контакт
Имеют бухгалтерский журнал Заказчики
Для определения, когда они контактировали по адресу заказчика Организация или конкретное лицо, которым продаются изделия, офисное оборудование и/или мебель
Приобретают изделия
Заказчики приобретают изделия у клиентов
Имеют контакты
Разместить имена, адреса, номера телефонов и т. д. людей по адресу заказчика
Склад
Место, где размещаются товары для продажи Размещают товары для отправки
Товары размещаются на складе для продажи заказчикам
Поставки
События, когда проданные товары поставляются заказчику
Изделия
Товары для продажи
Могут быть проданы заказчикам Офисное оборудование Является товаром
Мебель
Заказчики могут купить любые из продаваемых товаров Тип товара Офисное оборудование просто является расширением понятия изделия, возможно несущим большее количество информации Тип товара
Является товаром
Мебель просто является расширением понятия изделия, возможно несущим большее количество информации
Счет-фактура
Физический документ, который отправлен заказчику, чтобы запросить оплату за проданные изделия
Подтверждение размещения изделия
Электронный документ (возможно, это единственный формат), который позволяет сообщить заказчику, что заказ размещен
Журнал
Распечатка всех контактов
Бухгалтерский журнал
Фактическая запись контакта с заказчиком
Замечание для физического моделирования: регистрировать любые изменения счетов-фактур
80
Сущности, атрибуты, отношения и бизнес-правила
Определение бизнес-правил Бизнес-правила могут быть определены как совокупность операторов, которые управляют и формируют деловое поведение. В зависимости от методологии организации данных, эти правила могут быть в форме помеченных списков, простых текстовых диаграмм или других форматов. Для наших целей мы прежде всего будем рассматривать правила, которые являются в основном теми же самыми, что и утверждения, которые мы обсуждали в предыдущей главе, и охватим любые критерии, которые точно не укладываются в нашу конструкцию таблицы, столбца и домена. Реализация не подразумевает существования бизнес-правил на данной стадии процесса проектирования. Все, что мы хотим сделать — собрать бизнес-правила, связанные с данными, для последующего рассмотрения. При определении бизнес-правил вы можете получить некоторое дублирование правил и доменов для атрибутов, но в данный момент это не является проблемой. Важно задокументировать столько правил, сколько возможно, так как пропущенные бизнес-правила будут иметь для вас большие последствия, нежели пропущенные атрибуты, отношения или даже таблицы. Новые таблицы и атрибуты будут часто найдены, когда вы будете реализовывать систему, обычно по необходимости, но выяснение, что нужны новые бизнес-правила на последней стадии разработки, может разрушить весь проект, вызывая дорогое переосмысление или опрометчивое "залатывание дыр" в них. Разработка бизнес-правил — не очень трудный процесс, но это — потраченное время и довольно утомительно. В отличие от сущностей, атрибутов и отношений нет никакого прямого определенного алгоритма для выявления всех бизнес-правил. Однако моя общая практика, когда я рассматривал бизнес-правила, дает вывод, что нужно читать документы строка за строкой, отыскивая предложения, включающие слова, наподобие "как только ... происходит", "... надлежит ...", "... должен ...", "... будет ...", и т. д. Но документы не всегда включают каждое бизнес-правило. Вы могли бы просмотреть сотню или тысячу счетов, и не увидеть ни одного случая, где клиент отдает в кредит деньги, но это не означает, что это никогда не бывает. Во многих случаях бизнес-правила могут быть добыты из двух мест: • Q
старый код; клиенты.
Получение бизнес-правил с помощью любого из этих источников никогда не является приятным делом. Не каждый программист пишет удобочитаемый код. Если вам повезет, ваш деловой аналитик возьмет интервью у клиентов относительно этих правил. В нашем примере "фрагмента записей о встрече" есть несколько бизнес-правил, которые мы можем определить. Например, мы уже обсудили потребность в атрибуте, представляющем собой номер заказчика, но не могли определить домен для номера заказчика, поэтому возьмем следующее предложение:
Они идентифицируют SakaSlukoS номером, формируемым существующей системой. ф> и получим бизнес-правило, наподобие следующего: Номера заказчиков формируются на основе алгоритма работы существующей системы. (Проверить значения их текущих номеров.)
81
Глава 4 Другое предложение в нашем примере дает еще одно возможное бизнес-правило: пеЫтаютсп еженедельно Для него мы можем получить следующее правило:
Счета могут быть напечатаны только в определенный день недели. Однако действительно ли это фактическое бизнес-правило? Маловероятно, что клиент хотел бы связать себя системой, в которой можно печатать счета только в некоторый день недели. Однако вы могли бы заявить, что это точно то, что клиент указал в первоначальной документации. Это, вероятно, будет одной из ситуаций, где английский язык (и русский тоже. Прим. перев.) недостаточно технически точен, чтобы определить важные детали в такой короткой форме. Вышеупомянутое извлечение из примера документации могло бы подразумевать: клиент ожидает, что будет столько документов, которые достаточно печатать еженедельно, или что документы удобно печатать еженедельно. Мы должны будем выяснить, что предполагалось в этом утверждении, когда созданный нами документ будет рассматриваться клиентом. Последнее бизнес-правило мы можем получить из следующей части документа: ... но поставки делаются немедленно^ как только ЗйкаЗШ деллем ЗакаЗ, если ему довфлют (нет никаких кредитных пробл Мы имели с ней дело раньше, когда рассматривали атрибуты, но она заключает также очень важное бизнес-правило:
Поставки не должны выполняться немедленно, если заказчики некредитоспособны.
Конечно, мы должны определить: • •
Что здесь означает термин "немедленно"? Как мы оцениваем кредитоспособность клиента?
•
Когда мы в конце концов можем отправить изделия заказчикам, как будут осуществляться платежи и как будут сделаны поставки?
Эти проблемы также должны быть выяснены при дальнейших консультациях с клиентами. Данный раздел, посвященный бизнес-правилам, вряд ли полон. Легко привести примеры таблиц, атрибутов и отношений, но бизнес-правила имеют гораздо более неясную природу. Чтобы сделать наш пример таким, чтобы можно было получить большую часть 82
Сущности, атрибуты, отношения и бизнес-правила
бизнес-правил, мы должны будем существенно увеличить количество информации в документации примера, что сделает идентификацию сущностей и атрибутов излишне сложной. Просто очень важно отметить каждое возможное место, где может быть получено то или иное бизнес-правило, которое определяет, как используются данные и которое не должно конкретно управлять доменами столбцов. Установление бизнес-правил — потенциально протяженный процесс, который нельзя обойти или сократить в реальном проекте. Отсутствие или неполные бизнес-правила вызовут большие проблемы позже.
Идентификация основных процессов Процесс обычно представляет собой логически связанную последовательность шагов, предпринимаемых программой, которая использует выделенные нами данные, чтобы что-то с ними сделать. Как пример, рассмотрим процесс получения водительских прав (по крайней мере, здесь, в штате Теннесси): Q
Заполнить разрешающие формы ученика.
•
Получить разрешение на обучение.
•
Практика.
•
Заполнить форму водительских прав.
•
Пройти экзамен на вождение.
•
Получить фотографию.
•
Получить права.
Каждый из этих шагов должен быть завершен до перехода к следующему. Процессы могут включать все или не все шаги, перечисленные на логической стадии, и конечно будут иметь бизнес-правила, управляющие ими. Процесс получения водительских прав в штате Теннесси требует, чтобы вам было пятнадцать лет для получения разрешения на обучение, было шестнадцать лет для получения водительских прав, чтобы вы сдали экзамен, практика должна быть с лицензированным водителем и т. д. Если вы являетесь деловым аналитиком, помогающим проектировать систему получения водительских прав, то были бы должны в какой-то момент задокументировать этот процесс. Идентификация процессов очень важна для задачи моделирования данных. Многие процедуры в СУБД требуют работы с данными, а процессы являются решающими в этих задачах. Каждый процесс будет обычно переводиться в один или большее количество запросов или хранимых процедур, которые могут потребовать большего количества данных, чем мы определили. В нашем случае мы имеем несколько примеров таких процессов:
Ммеютм ЗакаМнки, кппафмм они продают подфэ/стнме нШякя Эта запись говорит нам, что есть средства создания и работы с заказами изделий, купленных клиентом. Внешне это не похоже на кардинальное открытие, но, вероятно, будут иметься записи в нескольких таблицах, чтобы облегчить надлежащее создание заказа. Мы еще не определили таблицу заказа или заказываемых изделий, или даже цены изделий, хотя нам, несомненно, нужно это сделать. 83
Глава 4
Clem-(f)afawyjm пеАатютоя ... ф> Мы сделали запись о счетах, но этот процесс "печатания счета" может потребовать дополнительные атрибуты для определения, что счет-фактура был напечатан или перепечатан, для кого он был напечатан и может ли он быть перепечатан. Управление документами — очень важная часть многих процессов, которая может помочь в модернизации бумажной системы. Обратите внимание, что печать счета-фактуры может походить на довольно глупую процедуру — нажимай на кнопку на экране, и принтер выдаст бумагу. Все, что мы должны сделать, — выбрать некоторые данные из таблицы, не так уж и много? Однако когда мы печатаем документ, нам, вероятно, придется делать запись факта, что документ был напечатан, кто напечатал его, и каково использование документа. Нам бы также могло потребоваться указать, что документы напечатаны в течение процесса, который включает операции включения и суммирования продаваемых единиц в счете-фактуре. Здесь наиболее важный момент заключается в том, что мы не можем делать любые предположения. Другие основные процессы, которые были выявлены, следующие: • • • Q •
Организация поставок — из "... поставки делаются немедленно, как только заказчик делает заказ, если ему доверяют ...". Изменение счета-фактуры — из "Любые изменения счета-фактуры должны быть задокументированы." Поставки изделий — из "Все изделия отправляются со склада в центре города." Организация подтверждения заказа изделия — из "... факс или электронную почту для организации подтверждений." Контакт с заказчиком — из "Желательно также иметь свободную область для контактов, т. к. они могут вести журнал контактов с заказчиками."
Каждый из этих процессов определяет единицу работы, которую нужно выполнить на стадии реализации проекта БД.
Где мы сейчас? Должно быть совершенно очевидно, что не все сущности, атрибуты, отношения, бизнес-правила и процессы даже для нашего простого примера были идентифицированы на данный момент. В этом разделе мы кратко рассмотрим шаги, которые нужно выполнить для завершения задачи формирования набора рабочей документации. Каждый из следующих шагов может показаться довольно очевидным, но их можно легко пропустить или проскочить в реальной жизненной ситуации. Реальная проблема состоит в том, что если вы не выполняете весь процесс разработки, то можете пропустить огромное количество информации, часть которой может быть жизненно важной для успешного проектирования. Часто, когда архитектор достигает этого конкретного момента, он/она будет пытаться перейти к реализации проекта. Однако имеются, по крайней мере, еще три шага, которые мы должны выполнить, прежде чем сможем начать следующую стадию проектирования.
84
•
Определение необходимых дополнительных данных.
• •
Рассмотрение проекта с клиентом. Повторение процесса, пока и мы не будем удовлетворены, и клиент не будет удовлетворен и не скажет, что можно остановиться на том, что мы разработали.
Сущности, атрибуты, отношения и бизнес-правила Обратите внимание, что эти процедуры должны выполняться в течение всего процесса проектирования, а не только в той части, где определяются данные.
Определение необходимых дополнительных данных Вплоть до этого момента мы старались не расширять информацию, которая была получена на стадии ее сбора. Целью было получение основы нашей документации, так что мы могли довольствоваться грудами документов, которые первоначально собрали. Добавление сюда наших новых мыслей до выяснения того, что было в исходной документации, может запутать как клиента, так и нас самих. Однако на данном этапе нам нужно изменить стратегию и начинать добавлять атрибуты, которые мы считаем нужными. Обычно имеется довольно большой набор очевидных атрибутов и, в меньшей степени, бизнес-правил, которые не были определены ни одним из пользователей или на основе начального анализа. Для каждой из идей и элементов, выделенных к настоящему моменту, нам нужно тщательно рассмотреть и определить дополнительные атрибуты, которые, как мы думаем, будут необходимы. Например, возьмем сущность "Контакт", которую мы определили ранее: Сущность
Описание
Контакт
Люди, выступающие в качестве заказчиков, и с которыми следует установить контакт
Атрибуты Номер факса
Номер телефона, чтобы посылать факсимильные сообщения заказчику
Любой подходящий номер телефона
Номер голосового телефона
Номер телефона для голосовой связи
Любой подходящий номер телефона
Адрес
Почтовый адрес контактного лица
Любой подходящий адрес
Адрес электронной почты
Адрес электронной почты контактного лица
Любой подходящий адрес электронной почты
Отношения Имеют бухгалтерский журнал
Для определения, когда они контактировали по адресу заказчика
Мы вероятно хотели бы отметить, что контактное лицо будет нуждаться в следующих дополнительных атрибутах: •
Имя — полное имя заказчика, вероятно, наиболее важный из всех атрибутов.
•
Имя супруга (супруги) — имя мужа или жены заказчика. Этот вид информации бесценен при создании контактов, если вы хотите персонифицировать ваше сообщение или спросить относительно семейства заказчика. 85
Глава 4 • •
Дети. Дата рождения — если день рождения человека известен, можно послать к этой дате открытку.
Имеется, конечно, большее количество атрибутов, которые мы могли бы добавить к сущности "Контакт", но этот набор должен сделать сущность достаточно ясной. Могут также иметься дополнительные таблицы, бизнес-правила и т. д., чтобы рекомендовать клиенту на этой стадии проектирования задокументировать их и добавить к вашим спискам. Одна из главных вещей — удостовериться, что ваши новые элементы данных отличаются от тех, которые уже согласованы с клиентом на предыдущих консультациях, и от предварительной документации.
Повторное рассмотрение с клиентом Как только вы закончили собирать эту первую подборку документов, пришло время встречаться с клиентом. Важно показать ему все, что у вас есть, и ничего не пропустить. Совершенно необходимо сделать так, чтобы клиент рассмотрел каждую часть этого документа и понял то решение, которое вы начинаете разрабатывать для него. Конечно, клиент может иметь очень маленький или никакой опыт в вопросах проектирования БД и может требовать помощи и объяснений от вас, чтобы лучше понять ваш проект. Стоило бы даже сделать два документа, один для клиента в терминах обывателя и другой внутренний, более детальный для использования вашей командой проектировщиков. Заслуживает внимания разработка некоторой формы документа отказа от претензий, который клиент подпишет до того, как вы продолжите свою работу. Это могло бы быть хорошим юридически обязательным документом.
Повторяйте ваши действия, пока не получите одобрение заказчика Конечно, маловероятно, что клиент немедленно согласится со всем, что вы ему говорите, даже если вы — величайший архитектор данных в мире. Обычно требуется несколько попыток, чтобы получить его согласие, и каждое повторение должно продвигать вас и клиента ближе к цели. Поскольку вы выполняете все больше повторений проекта, становится все более и более важным удостовериться, что на основании имеющейся у вас информации клиент каждый раз отказывается от своих претензий; вы сможете указать на эти документы, когда клиент изменит свое мнение позже. Плохо, когда вы не выполняете соответствующей работы по управлению повторным рассмотрением и процессом документирования. Я занимался консультированием проектов, когда проект был хорошо разработан и согласован, но документация о том, что было согласовано, была сделана не слишком хорошо (уйма подтверждений на высоком уровне, чтобы сэкономить деньги!). Когда прошло время, и много тысяч долларов было потрачено, клиент пересмотрел документ соглашения, и стало очевидно, что мы вообще о многом не договорились. Само собой разумеется, что работа была выполнена не слишком хорошо.
86
Сущности, атрибуты, отношения и бизнес-правила
Учебный пример книги Перейдем теперь к нашему реальному примеру проектирования БД. Вернувшись к учебному примеру, начатому в главе 2, мы должны заняться разработкой сущностей на основе принципов, уже представленных в этой главе.
Интервью клиента Всегда лучше начать с записей интервью с клиентом. Сначала мы просматриваем их и отмечаем все пункты, которые собираемся представить сущностями. В нашем примере мы будем помечать полужирным шрифтом эти пункты.
З&сифеЫ с Сэмом Смитом, бухштфскм дело, 2Л ноября 2006\ -W ia-сов утра, Ъольшой, конференц-Зал посетители: Луш Юэбидсщ архитектор данных; Сэм Смит,, менедфер бухгалтерского укта; Юфо Юфонс, экономист
91ервая встрек МспольЗуемые дополнительные документы: регистр банка, типовой iek, типовой лицевой clem, a такз/св электронный формат лицевого clem. $ настоящее время испольЗуется бумафный регистр iekoS (check register), а такфе основной вм/тренний регистр, Рассматривали воЗмоо/сность использования подготовлентлх материалов, но не нашян ни одного, который бы обетеАивал все, imo xoiem 3aka3(uk. 94 у большинства предлофенных путей слиткам много особенностей. 91р1сно иметь многопользовательские воЗмофности (ввод и просмотр). Распределение iepe3 интранет. Щоуесс: Муфио иметь последнюю информацию относительно ciema (account). (В настоящее время баланс ciema проводится один раЗ в месяц. МслолъЗуется omiem (statement) иЗ банка (hank). Юля полфеиил баланса испольЗуется по крайней мере полный день. Хотелось бы mлyiamь баланс по demy ефенеделъно, испольЗуя данные иЗ Мнтернетл. Мнтересует только ведение скмов, но не сберефения, акции, обязательства, и т. д. Юля этого уфе имеется система. Щцдет ревизоваться ефегодно главным аудитором. Однако контрольного плана нем. 'Когда Сэм Закончил, был Задан вопрос об отшфиванми продавцов: Ъыло бы хорошо иметь информацию о полу1ателе платефа (payee) для большинствавыписываемых нами iekoe. Шакфе относительно категориЗации iekoS (check) Сэм скаЗал, imo это было бы, хорошо,-но не яёляется необходимым в данный момент времени.
87
Глава 4 Мы нашли следующий исходный ряд сущностей, с которыми будем иметь дело:
88
Сущность
Описание
Check (чек)
Бумажный документ, который используется, чтобы передать фонды кому-нибудь
Account (счет)
Банковские отношения, установленные, чтобы обеспечить финансовые сделки
Check register (регистр чеков)
Место, где зарегистрировано использование счета
Bank (банк)
Организация, которая имеет текущий счет, вместо которого выписываются чеки
Statement (отчет)
Документ (бумажный или электронный), который поступает от банка раз в календарный месяц и который сообщает нам соображения банка о том, что мы потратили
Payee (получатель платежа)
Личность или компания, которой мы посылаем чеки
Сущности, атрибуты, отношения и бизнес-правила Следующим шагом мы просматриваем документ и ищем атрибуты (которые помечаем полужирным подчеркнутым шрифтом) и отношения, которые мы выделяем серым фоном.
3dcm,peia с Сэмом Смитом, бухгалтерское дело, 24 ноября 2000,
Ю ксов утра, большой
конферепц-Зал (Посетители: Луис Юэвидсон, архитектор данных; Сэм Смит, менедфер бухмлтерского yiema; Юфо Юфонс, экономист Первая встреЛа МспольЗуемые дополнительным документы,: регистр банка-, типовой iek, типовой лицевой dem, а такфе электронный формат лицевого dema. 3d настоящее время испольЗуется бумафный
(paper)
регистр lekoS, а такфе основной
внутренний регистр. Рассматривали воЗмофность использования подготовленных материалов, но не нашли ни одного, который бы обеспеАивал все, imo xoiem ЗакаЗЫк. My большинства предлофенных путей слишком много особенностей. HipjcHo иметь мнтопольЗовательские воЗмофности (ввод и просмотр). Распределение iepe3 интранет. Процесс: 'Нуфно иметь последнюю информацию относительно dema
(account).
3d настоящее время баланс dema проводимся один раЗ в месяц. МспольЗуется omiem и 3 банка. Юля полукнт баланса испольЗуется по крайней мере полный день. Хотелось бы полукть бала-ис по demy ео/сенеделъно (balanced weekly), испольЗуя данные нЗ с/
с/ — '
*——
J /
с/
Мнтернета (internet data). Мнтересует только ведение demoe, но не сберефения, акции, обязательства, и т. д. Юля этого уфе имеется система,. Ъудет ревиЗоват,ься ефегодно главным аудитором. Однако контрольного плана нет. Когда Сэм Закткщ
был Задан вопрос об отслефивании продавцов:
Ъьию бы хорошо иметь информацию о иолуЫтеле платефа для большинства выписываемых нами hhiC> . Шакфе относительно категорпЗашп
(categorizing)
tekoS Сэм скаЗал, imo это било бы
хорошо, но не .является необходимым в данный момент времени.
В настоящее время фактически нет полного набора информации, который нужно подобрать из этих заметок. Вы можете заметить, что интервью с клиентом раскрывает очень небольшую информацию; в этом случае мы можем захотеть понять, почему мы не получили информацию, которую искали. Говорили не с теми людьми в организации, или задавали неправильные вопросы? Неприятно отвечать на вопрос "Почему я потерпел неудачу? " — но это сделает вас более хорошим архитектором данных в будущем.
89
Глава 4
Документы Наша документация учебного примера справедливо хромая (чтобы пример был коротким и приятным), но, к счастью, мы имеем несколько документов, которые прольют немного света на наше решение. Мы сейчас рассмотрим эти документы и отметим атрибуты (и возможно, сущности), которые там найдем. Первый документ, который мы рассмотрим — типовой чек. Этот вид документа — мечта проектировщика. Так как мы уже определили сущность Check (чек), каждое из полей в этом документе, вероятно, будет атрибутом. Check Number June 12, 2000
13403 WROX Subscriptions
Magazine Subscription Memo
Amount
Здесь CheckNumber — номер чека; Payee — получатель платежа; Date Written — дата заполнения; Memo — служебная запись; Amount — сумма; Signature — подпись. В нашем чеке можно отметить два момента: • •
Термин Amount (сумма) определен на чеке дважды, обеспечивая меру избыточности и защиты от мошенничества, которые несущественны на этом уровне проектирования. Payee (получатель платежа) уже был определен как отношение типа "имеет", который, в сущности, является атрибутом таблицы, так что мы не должны его снова определять как атрибут.
Следующее, что мы рассмотрим, — типовой регистр чеков. Это — просто запись каждого чека, как только он будет введен.
Numb ег
Date
Description
iCategcry
12390
12/15/00
Pizia Hut
Employee Appreciation
12391
112/15/00
Allied Mortgage
Building payment
12392
12/16/00
TN Electric
Utilities
12393
12/16/00
Deposit
N/A
Amount
Balance
шшт шшш
| Accountflunnino Total
Здесь Number — номер; Date — дата; Description — описание; Category — категория; Amount — сумма; Balance — баланс; Account — счет; Running Total — текущий итог.
Сущности, атрибуты, отношения и бизнес-правила Заметим, что номера, даты, описания (получатели платежей), категории и суммы уже получены непосредственно из чеков, но баланс (Balance) — новый параметр. Атрибут Balance (баланс) не соответствует в полной мере сущности Check (чек), но имеет смысл для сущности Account (счет). Следовательно, мы добавим атрибут Balance сущности Account. Затем мы должны рассмотреть отчет банка. Отметим все атрибуты так же, как мы делали в чеке. Отчет банка обеспечит атрибуты для нескольких сущностей. BankBank . Name iI
Bank of the I Account Statement Account Statement
Account. Account Number
Account number I
Previous Balance Current Balance Total Debits Total Credits
Last Balanced: Statement Date:
11/5/00 12/5/00
11/5/00 11/6/00 11/8/00 11/28/00 ... строки удален
. строки удален
12/01/00 12/02/00 12/03/00
Checks: 12200 строки удалень
... строки удален 12204*
'Означает разрыв в последовательности чеков
Statement. Balancing Items (the statement contains copies of all the items the register should have)
Statement. Pre/ious Balance Current Balance Total Credi Previous Balance Date Statement Date
Здесь Bank — банк; Bank Name — название банка; Account — счет; Account Statement — отчет no счету; Account Number — номер счета; Previous Balance — предыдущий баланс; Current Balance — текущий баланс; Total Debits — общий дебет; Total Credits — общий кредит; Last Balanced — дата последнего баланса; Statement Date — дата отчета; Check — чек; Deposit — депозит; Direct Withdrawal — прямое изъятие; Balancing Items — элементы баланса; Previous Balance Date — дата предыдущего баланса. 91
Глава 4 Отчет банка дал нам несколько атрибутов для сущностей — Bank (банк), Account (счет) и Statement (отчет). Здесь мы определяем две новых сущности: Q
Deposit (депозит) — используется для добавления денег к счету;
•
Direct Withdrawal (прямое изъятие) — деньги, изъятые со счета без заполнения бумажного чека.
Снова мы имеем дату чека, номер чека, сумму чека и текущий итог (все вычеркнуто по причинам секретности и безопасности, хотя всегда хорошей идеей является изображение вида некоторых документов, содержащих конфиденциальную информацию, с помощью отображения диапазона цифр и их формата — например, номера чеков дополнить ведущими нулями). Мы также обращаем внимание, что сущности Deposit (депозит) и Direct Withdrawal (прямое изъятие) имеют, очевидно, одни и те же атрибуты, и они — все в той же самой таблице с текущим итогом. Так что сущности Deposit и Direct Withdrawal будут иметь атрибуты Data (дата), Number (номер) и Amount (сумма). Текущий итог снова является общим для счета. Заключительная часть документации, которую мы должны рассмотреть — электронный формат регистра банка. Электронная версия более редкая, чем бумажная версия. В электронной версии может быть и больше информации, но это — все, что нам в настоящее время дают. Она также может быть единственной, что нам дают, потому что это действительно все, что нам нужно для балансирования счета.
Column
Data type
Required
Transaction Date
Date Only
Yes
Transaction Number
String(20)
Yes
Description
String(lOO)
Yes
Item Amount
Money
Yes
Transaction. Transaction Date Transaction Number Description Item Account
Здесь Column — столбец; Data type — тип данных; Required — требуется; Transaction— сделка; Transaction Date — дата сделки; Transaction Number — номер сделки; Description — описание; Item Amount — сумма; Date Only — только дата; String (N) — строка из N символов; Money — денежный тип. В то время как здесь нет никаких новых атрибутов для уже имеющихся сущностей, имеется упоминание о новой сущности — Transaction (сделка). Интересно, что банк использует эту сущность с соответствующими атрибутами, чтобы сообщить о значениях некоторых других сущностей. Все наводит меня на мысль, что, вероятно, имеется единый объект, который одновременно представляет и чеки, и депозиты, и прямые изъятия. Это указывает на то, что мы будем иметь сущность Transaction с отношениями "является" к сущностям Deposit, Direct Withdrawal и Check. 92
Сущности, атрибуты, отношения и бизнес-правила Проработав все наши документы, мы должны закончить наш документ сущностей. Здесь мы отметим все дополнительные атрибуты, которые добавим в самый левый столбец.
Перечень объектов банковского счета Вот этот набросок документа, который мы формируем на основе нашего первоначального анализа: Сущность
Описание
Домен
Transaction (сделка)
Логическая операция, которая добавляет или изымает деньги из нашего счета
Атрибуты Transaction Date (дата сделки)
Дата, когда деньги добавлены или удалены со счета
Подходящие даты
Transaction Number (номер сделки)
Уникальный номер, используемый для идентификации сделки
Строка
Description (описание)
Строка описания сделки
Строка
Item Amount (сумма)
Количество денег, добавленных или удаленных со счета
Вещественное число с двумя десятичными цифрами
Check (чек)
Бумажный документ, который используется, чтобы передать фонды кому-нибудь
Атрибуты Check Usage Type (тип использования чека)
Определяет категорию использования чека
Неизвестный
Check Type (тип чека)
Определяет категорию чека
Неизвестный
Check Number (номер чека)
Номер для однозначного определения чека
Целое число
Date Written (дата заполнения)
Дата, когда чек был написан
Подходящие даты, возможно, не будущие даты
Amount (сумма)
Количество денег, снятых со счета и переданных получателю платежа
Вещественное положительное число с двумя десятичными цифрами
Продолжение таблицы, на следующей странице 93
Глава 4
Описание
Домен
Memo (служебная запись)
Краткое описание, для чего используется чек
Текст
Signature (подпись)
Подпись лица, выписавшего чек — мы, возможно, просто храним текстовую версию имени подписавшего
Текст
Сущность
Отношения Имеет получателя платежа
Используется для идентификации получателя платежа, которому послан чек
Является сделкой
Определяет, что чек является подклассом сделок
Payee (получатель платежа)
Личность или компания, которой мы посылаем чеки
Атрибуты Новый
Name (имя)
Имя любого, кому заплачены деньги
Строка
Новый
Address (адрес)
Адрес получателя платежа
Любой подходящий адрес
Новый
Phone Number (номер телефона)
Номер телефона получателя платежа
Любой подходящий номер телефона
Deposit (депозит)
Используется для добавления денег на счет
Атрибуты Date Written (дата написания)
Дата написания чека
Подходящие даты, возможно, не будущие даты
Deposit Number (номер депозита)
Номер для однозначного определения депозита
Целое число
Amount (сумма)
Количество денег, добавляемых к счету
Вещественное положительное число с двумя десятичными цифрами
Description (описание)
Описание из электронного источника
Строка
Отношения Является сделкой
94
Определяет, что депозит является подклассом сделок
Сущности, атрибуты, отношения и бизнес-правила
Сущность
Описание
Direct Withdrawal (прямое изъятие)
Используется для изъятия денег ее) счета без какого-либо бумажного документа
Домен
Атрибуты Date Withdrawn (дата изъятия)
Дата, когда был написан чек
Подходящие даты, возможно, не будущие даты
Number (номер)
Номер для однозначного определения чека
Целое число
Amount (сумма)
Количество денег, снятых со счета
Вещественное положительное число с двумя десятичными цифрами
Description (описание)
Описание из электронного источника
Строка
Отношения Является сделкой
Account (счет)
Определяет, что прямое изъятие является подклассом сделок Банковские отношения, установленные для организации финансовых сделок — мы, вероятно, будем иметь дело со многими банками
Атрибуты Account Number (номер счета)
Число, однозначно определяющее счет
Неизвестный, устанавливается, банком, возможно, строка
Balance Date (дата баланса)
Дата и время, когда произведен баланс счета
Подходящие даты
Running Total (текущий итог)
Текущее количество денег на счете
Вещественное положительное число с двумя десятичными числами
Отношения Проведение баланса, используя отчет
Определяет, получаются ли данные из баланса счета
Использует сделки
Для счета используются сделки, чтобы добавлять или изымать деньги Продолжение таблицы на следующей странице 95
Глава 4
Сущность
Описание
Домен
Check Register (регистр чеков)
Место, где фиксируется использование счета
Атрибуты Register Type
Bank (банк)
Описывает тип регистра, который мы используем для хранения записей счетов
"Документ"
Организация, которая использует чековые счета и которая выписывает чеки — мы, вероятно, будем иметь дело со многими банками
Атрибуты Bank Name (название банка)
Название банка, с которым мы имеем дело
Строка
Отношения Рассылает отчеты
Банк рассылает отчеты, чтобы мы могли сбалансировать счет
Имеет счета
Идентифицирует счета банка
Statement (отчет)
Документ (бумажный или электронный), который поступает из банка раз в календарный месяц и сообщает нам все, что думает банк о наших тратах
Атрибуты
96
Statement Type (тип отчета)
Определяет тип отчета, полученного из банка
"Интернет-данные", "Документ"
Previous Balance (предыдущий баланс)
Определяет, какой предполагался баланс счета после последнего баланса отчета
Вещественное число с двумя десятичными цифрами
Previous Balance Date (дата предыдущего баланса)
Определяет дату последнего баланса счета
Дата
Сущности, атрибуты, отношения и бизнес-правила
Описание
Домен
Current Balance (текущий баланс)
Определяет баланс счета после того, как все элементы отчета были сформированы
Вещественное число с двумя десятичными цифрами
Statement Date (дата отчета)
Определяет дату, когда сформирован отчет — это, вероятно, тот день, когда отчет создан банком
Total Credits (общий кредит)
Сумма всех элементов, которые добавляют деньги на счет в период, охватываемый отчетом
Вещественное положительное число с двумя десятичными цифрами
Total Debits (общий дебет)
Сумма всех элементов, которые изымают деньги со счета в период, охватываемый отчетом
Вещественное отрицательное число с двумя десятичными цифрами
Balancing Items (элементы баланса)
Все операции, которые выполнил банк, и о которых он теперь оповещает нас
Массив сделок (чеков, депозитов, прямых изъятий)
Сущность
Вы можете, вероятно, заметить, что мы достигаем такого состояния, когда даже для такого маленького примера этот формат документации становится неуправляемым. В следующей главе мы обсудим моделирование данных, которое обеспечивает метод сбора и представления этих данных, являющийся намного более легким для работы. Это не означает, что данный вид описания не имеет никакого смысла. По крайней мере, вы должны сформировать этот документ, чтобы представить клиентам. По этой причине мы выполнили это упражнение, а не перешли непосредственно к моделированию данных. Документ, который мы сформировали, дает хорошее описание того, что мы хотим реализовать либо техническим, либо нетехническим способом.
97
Глава 4
Бизнес-правила и процессы Для рассмотрения бизнес-правил и процессов мы вернемся к записям нашего интервью с клиентом. Процессы помечены серым фоном, а бизнес-правила обведены рамкой!
Здстрек с Сэмом Смитом, бухгалтерское дело, 24 ноября 2000,
JO iaco6 утра, Ъолыиой
конффтц-Зал посетителя: Луис Юэвидсон, фштеЬаф
данных; Сэм Смит, менедо/сер бух/гашерского
yiema; Юфо Юфонс, экономист 'Первая (kmpela 14спольЗуемме дополнительные документы: регистр банка, типовой 1вк, шиповой лицевой clem, а тлкфе электронный формат лицевою
фш.
(В настоящее время используется бумафный
(f>af>er) fjeiucmft кков, а тлкфе основной
внутренний fretucmf). (Рассматривали воЗмофносмъ использования подготовленных материалов, но не нашли ни одного, komojmu бы обвсшАивал, все, im,o xoiem SakaSiuk. My большинства п^едлофенных путей слишком много особенностей. уНуо/сно иметь многопользовательские воЗмофносмщ (ввод и nftocMomj?). Распределение iefie инт^анем, Щ>оцесс: 9~1уфно иметь последнюю информацию относительно clema
(account).
1д настоящее время баланс ШетЛ (halanmifj account) проводится одни j?a3 в месяц. МсполъЗуетсп omiem иЗ банка. Юля полу1ения баланса, испольЗуется по крайней мере полный день] Хотелось бы, полукть баланс по demy ефенеделыш (balanced weekly), испольЗуя данные иЗ ^Интернета (internet data). Мнтересует только ведшим сктов, но не сбережения, акции, обязательства, и т. д. Юля этого уу/се имеется система. Шудет ревйЗоватьоя (audited) ежегодно главным аудитором. Юдилко контрольного плаял нет. %огда Сэм Закон1ил, был Задан Запрос об отсле/фивании продавцов: Ъыло бы хорошо иметь информацию о полугателе платефа для большинства выписываемых нами 1ек.ов. 9JIah/ce относительно категоризации
(categorizing)
iekoS Сэм скйЗал,, 1то это было бы,
хорошо, но не является необходимым в данный момент времени.
98
Сущности, атрибуты, отношения и бизнес-правила
Эта документация дает нам следующее Бизнес-правило
Описание
Должны быть многопользовательские возможности
Возможно, что одновременно потребуется вводить сделки более чем одному человеку
Составлять баланс по счету нужно еженедельно
Нам нужно иметь возможность балансировать счет более часто, чем обычно один раз в месяц; возможно, чтобы уменьшить время между моментами баланса, нужно будет находить пропущенные сделки более часто
Будет ревизоваться ежегодно
Некая внешняя фирма будет проверять наши числа ежегодно, чтобы удостовериться, что все находится в хорошей форме; вероятно, мы сформируем некоторую документацию, чтобы облегчить этот процесс
Новое
Пользователь должен получить предупреждение, если попытается забрать слишком много денег
Пока это только идея, что пользователь может пожелать иметь помощь, чтобы избежать превышения счета
Новое
Не следует вводить сделки, которые произойдут в будущем
Можно избежать пост-датирования чека
Описание
Процесс
Новый
Составление баланса счета
Урегулировать все сделки с точки зрения банка, чтобы мы должны были удостовериться, что все из них зарегистрированы
Ревизия счета
Процесс, чтобы удостовериться, что все именно так, как зарегистрировано
Ввод сделки
Ввод нового депозита, чека или прямого изъятия
Имеется, вероятно, большее количество бизнес-правил и процессов, которые могут быть предложены, но то, что мы имеем — хорошее начало. К этому моменту вы сформировали проект документа для одобрения клиентом.
99
Глава 4
Метод водопада Заключительный момент, на который мы должны обратить внимание, состоит в том, что вам, возможно, потребуется выполнить весь этот процесс много раз, в зависимости от того, как хорошо вы выполнили процесс, описанный в этой главе. Те, кто имеет опыт руководства проектом, могут заметить, что процесс, отраженный в этой книге — точный метод водопада, который описывает процесс проектирования программного обеспечения подобно следующей схеме:
Описание
Проектирование
Кодирование
Проектирование
Тестирование
Она описывает некоторый путь, который мы выполняем здесь в процессе проектирования БД. Однако на практике этот процесс был бы более сложен, чем изображенный на предыдущем рисунке. Фактически он представлял бы цикл, а не линейно следующие друг за другом блоки. Как результат стадии тестирования, может потребоваться включить в проект новые определенные описания; это подразумевает, что весь процесс должен начаться заново. Каждый новый запуск цикла называется итерацией. Любой программный продукт или приложение выполняет много итераций, прежде чем завершит работу. Проблема добавления циклической информации состоит в том, что мы можем вернуться от кодирования назад к проектированию или от проектирования назад к описанию. Если все идет точно согласно плану, то вышеупомянутая диаграмма иллюстрирует полный процесс проектирования. Разумеется, решение в конечном счете должно быть получено, и ключ к этому — попытаться ограничить число итераций всякий раз, когда это возможно. Несколько больших изменений намного лучше, чем много небольших изменений.
100
Сущности, атрибуты, отношения и бизнес-правила
Резюме В этой главе мы рассмотрели процесс нахождения сущностей, которые, в конечном счете, составят основу решения вашей БД. Мы пропахали всю документацию, которая была получена на стадии сбора информации. При этом старались не добавлять наши собственные идеи в решение, пока не обработали всю исходную документацию. Следует заметить, что это не такая уж маленькая задача; в нашем исходном примере мы имели для работы только два абзаца, а завершили почти полутора страницами документации. Мы также провели интервью с клиентом и получили его согласие. Следует отметить, что запись всей этой информации в простой текстовый документ — не идеал. Мы выбрали этот способ размещения просто для целей этой книги, но обычно мы используем специализированные средства для формирования такого вида документации. Процесс моделирования данных, к которому мы перейдем в следующей главе, сформирует скелет документации с незаполненными местами, в которые вы будете вводить вашу информацию. До сих пор мы занимались только той частью процесса проектирования, которая связана с описанием. И этого действительно нельзя избежать. Любой процесс разработки, который требует хранения данных, должен полностью определить эти данные до перехода на стадию проектирования. Стадия реализации будет рассмотрена во второй половине этой книги, когда мы перейдем к физическому проектированию. Когда процесс, рассмотренный в этой главе, будет завершен, мы будем иметь почти все, что должны взять от клиента, прежде чем создадвать точную спецификацию данных.
101
*
Г
ч
'
.
Моделирование данных Введение Термин "моделирование данных" часто используется в очень широком смысле для описания всего процесса проектирования БД. Такая формулировка не так уж и плоха, но здесь мы будем понимать под данным термином не это. В этой главе мы будем использовать его для обозначения процесса разработки графических представлений структур данных. Документ, который мы начали разрабатывать в предыдущей главе, стал неуправляемым за короткое время, и идентификация отношений между различными сущностями стала очень трудной. Следовательно, мы нуждаемся в некотором способе представить информацию, которую представили как таблицы в документе, в более понятном формате. Чтобы сделать это, мы изображаем графические модели (картинки), чтобы преобразовать документ в форму картинки. Имеется много типов моделей или диаграмм: модели процесса, диаграммы потока данных, модели данных, диаграммы последовательностей; список мог бы продолжаться в течение весьма долгого времени. Мы, однако, сосредоточимся на двух специфических моделях: •
Модель действий (Use Case) — модель, которая входит в более общее описание, известное как UML (см. ниже), используемое для моделирования нужд и требований пользователей. Модель действий предназначена для задания всей известной о проекте информации, чтобы она была удобочитаемой как для пользователей, так и для проектировщиков.
•
Модели данных (Data Models) — модели, которые полностью связаны с представлением структур данных в реляционной БД.
Прежде чем мы посмотрим на вышеупомянутые модели более внимательно, обсудим кратко методологии моделирования, которые будем использовать в каждом случае.
Глава 5
Методологии моделирования Модель действий фактически является частью более общего описания, называемогоUML (Unified Modeling Language — унифицированный язык моделирования). UML-описание не имеет средств моделирования данных для реляционных БД (это — объектно-ориентированная технология), так что мы должны будем использовать другую методологию моделирования данных. Мы остановим наш выбор методологии моделирования данных, взяв ту, которая является самой легкой для чтения, а также позволяющей отображать и размещать все, что нам потребуется. В этой книге мы сосредоточимся на одном из наиболее популярных форматов моделирования для реляционных БД — IDEF1X (Integration Definition For Information Modeling — интегральное определение для информационного моделирования), и затем кратко коснемся другого формата — IE (Information Engineering — информационная разработка). В то время как методология моделирования данных может выбираться исходя из личных предпочтений, экономика, стандарты или возможности компании обычно влияют на выбор средств. В этой главе мы попытаемся непредвзято посмотреть на некоторые особенности средств, которые требуются или желательны, если нужно расширить основы графического моделирования. Вся информация, найденная в документе, который мы создавали в предыдущей главе, должна быть представлена и размещена в модели данных, и мы должны иметь возможность получать эти данные, чтобы поделиться с клиентом и программистами. U M L UML — де-факто стандартная методология для определения и документирования компонент объектно-ориентированных программных систем, созданная в значительной степени благодаря работе трех людей: Грэди Буча, Ивара Якобсона и Джима Рамбо. Каждый из этих людей имел свои собственные (или частично собственные) методологии, но они были объединены вместе, поскольку все эти люди начали работать вместе над продуктом Rational Rose на фирме Rational Software. Однако это не закрытая методология, и она сегодня используется и стандартизирована в программных средствах, предлагаемых многими большими корпорациями. В настоящее время полную документацию версии 1.3 можно найти в http://www.rational.com/uml. Корпорации, которые внесены в список использующих UML-описание: Rational Software, Microsoft, Hewlett-Packard, Oracle, Sterling Software, MCI Systemhouse, Unisys, ICON Computing, IntelliCorp, i-Logix, IBM, Object Time, Platinum Technology (CA), Ptech, Taskon, Reich Technologies, Softeam. По-видимому, важно упомянуть здесь, что UML не только методология моделирования, но и законченный метод объектно-ориентированного проектирования. В то время как методология моделирования — просто метод графического отображения компонент компьютерной системы, метод проектирования влечет за собой проектирование системы "от колыбели до могилы". UML предъявляет невысокие требования, когда мы хотим моделировать процессы типа тех, которые обнаружили при рассмотрении прошлой главы. Другая цель состоит в том, чтобы дать прямое объяснение того, почему при моделировании данных следует использовать UML, хотя мы не будем использовать его непосредственно для построения или моделирования наших реляционных данных, так как (об этом было сказано ранее) он предназначен не для реляционных технологий БД. 104
Моделирование данных UML составлен из весьма небольшого числа различных моделей, каждая из которых используется, чтобы представить компоненты системы, но мы используем только одну из них. Для чистого архитектора данных, имеющего дело только с построением реляционной БД, большинство моделей, вероятно, не так интересны, за исключением диаграмм действий (use case diagram). Позже мы дадим краткий обзор методики моделей действий на таком уровне, который позволит использовать ее в нашей задаче проектирования СУБД. I D E F 1 X Рассмотреть все существующие методологии моделирования было бы почти невозможно. Все они служат одной и той же цели и все имеют почти одни и те же объекты, отображаемые слегка различающимися способами. В этой главе мы остановимся на одной из наиболее популярной из них — IDEF1X, которая основана на сообщении FIPS (Federal Information Processing Standard — федеральный стандарт по обработке информации США) №184 от 21 сентября 1993 г. Чтобы быть справедливыми, имеется несколько методологий моделирования, которые являются в значительной степени эквивалентными для моделирования данных, типа IE (см. выше) и Chen ERD (диаграмма Чена отношений между сущностями), и если вы использовали одну из этих методологий, то вы, вероятно, привыкли к ней и не пожелаете изменить технологию на основе этой главы. Однако все примеры в книге будут в IDEFlX-формате. IDEF1X была первоначально разработана в Военно-воздушных силах США в 1985 г., чтобы обеспечить следующие требования: •
поддержка разработки моделей данных;
•
быть языком, который одновременно и легок в изучении, и является мощным;
•
быть доступным;
•
быть хорошо протестированным и проверенным;
•
быть подходящим для автоматизации.
Для получения полной копии IDEFlX-описания обращайтесь к http://www.qd.cl/webpages/idef/idef1/idef1x.html. IDEF1X очень хорошо работает в случае предъявления вышеупомянутых требований и используется во многих популярных средствах проектирования, типа СА ERwin, Embarcedero ERStudio и Microsoft Visio Enterprise Edition. Она также поддерживается другими распространенными средствами, которые можно найти в Интернете. Теперь, когда мы обсудили методологии, которые будем использовать, посмотрим, как их применять к нашим моделям.
Модели действий Как описано в Руководстве по UML, версия 1.1, изданная 1 сентября 1997 г. (полный документ находится в http://www.rational.com/uml), модели действий (Use Case) представляют внутри системы функциональные возможности этой системы или класса так, как это видят внешние пользователи.
105
Глава 5 Модель действий описывает: •
компоненты системы, смоделированные как действия (use cases);
Q
пользователей системы, смоделированных как исполнители (actors);
•
отношения между исполнителями и действиями.
Очень упрощенно модели действий представляют абстракцию диалогов между исполнителями и системой. Они описывают потенциальные взаимодействия, не вдаваясь в детали. Этот тип информации существенен для архитектора БД, так как нам всегда нужно идентифицировать пользователей, которые будут обращаться к БД, и то, что они хотят делать. Имеется несколько очень важных причин для этого: •
безопасность;
•
обеспечение данных, необходимых для поддержания существующего процесса;
•
создание хранимых процедур и триггеров для поддержания процесса.
Как мы увидим в следующем разделе, модели данных вообще не имеют отношения к процессам или пользователям. Так как модели действий являются частью UML, они часто используются не только строго объектно-ориентированными проектировщиками, но также и архитекторами систем всех типов, чтобы получить чертеж действий, которые должны дать желаемый результат. Различным категориям проектировщиков/архитекторов при использовании различных технологий моделирования понадобится использовать различные части диаграмм действий (как и нам с нашими моделями данных). Нет никакого набора приемов описания действий, но имеется определенный тип модели. Диаграммы действий очень просты, и имеются только два символа, используемые в них. Первый символ — исполнитель (actor), который является фигуркой человечка:
Исполнитель представляет пользователя, систему или часть аппаратных средств, которые выполняют действия в системе. Второй символ — действие (use case) — изображается обычным эллипсом с названием внутри:
106 I
Моделирование данных Чтобы смоделировать факт, что исполнитель использует действие, эти два символа связываются одинарной линией, именуемой отношением коммуникаций (communicates relationship):
Элементы действий могут быть также связаны друг с другом двумя способами: Q
с помощью другого действия для завершения цели — отношение "использует" ("uses");
•
расширяя другое действие, чтобы уточнить его поведение — отношение "расширяет" ("extends").
Они моделируются очевидным образом: «использует» или «расширяет Действие X )
( Действие Y
Пусть, например, мы хотим смоделировать процесс создания книги. Мы имеем, по крайней мере, следующих исполнителей: издатель, принтер, редактор и (надо надеяться) автор. Тогда нам понадобятся следующие элементы действий: предложение книги, прием книги, написание книги, редактирование книги, печатание книги и (мое любимое) плата за книгу. Это, вероятно, не включает всех исполнителей и действий, нужных для моделирования процесса публикации книги, но вполне достаточно для нашего примера. Отсюда мы получаем следующую модель:
Издатель
Редактор
Принтер
107
Глава 5 Один момент может показаться странным в этой диаграмме — прием книги находится до предложения книги. Однако в диаграмме действий нет никакого упорядочения. Упорядочение будет позже при рассмотрении процесса. Фактически, в очень большой диаграмме действий могут быть сотни элементов действий, связанных со многими исполнителями. Каждый элемент действия будет обычно иметь информацию, объясняющую его назначение. Следующая таблица показывает возможное описание для одного из наших действий: Название
Написание книги
Описание
Процесс выработки идеи и превращения ее в несколько сотен страниц
Предпосылки
Книга должна быть предложена и одобрена Схема должно быть полностью изложена в деталях и готова для написания книги
Постусловия
Рукопись произведения будет завершена и готова для издания
Этапы работы
Разработать план Написание введения Написание всех глав Написание резюме
Из модели и описаний сразу же можно видеть не только то, кто является пользователями, но также и каковы их взаимодействия с нашей системой написания книги. Обратите внимание, что и издатель, и принтер включены в действие "Печатание книги". Издатель заказывает печатание, а принтер фактически выполняет печатание. Какое бы средство ни использовалось для построения диаграммы действий, важно, чтобы оно могло хранить детальную информацию относительно каждого действия или исполнителя в модели. Хорошие средства от продавцов, перечисленных ранее, позволят нам формировать чрезвычайно полезные сообщения о данных, которые мы помещаем в модель. Я оставляю вас, читатель, далее копаться в UML, по крайней мере, до такой степени, чтобы вы были способны понять модели, которые создадут ваши деловой аналитик и системные архитекторы. Одна из лучших книг по этой теме — "Instant UML" (Wrox Press, ISBN 1861000871).
Модели данных Имеются два основных типа моделей данных — логическая и физическая. В течение процесса проектирования СУБД мы сначала строим логическую модель и затем на ее основе — одну или более физических моделей. Если процесс проектирования идет успешно, полная логическая модель должна быть функционально эквивалентна физической модели(ям), как только будет реализована полная логическая модель. Никакие данные, которые пользователь хочет видеть, не должны появиться в физической модели без того, чтобы не быть в логической модели.
108
Моделирование данных Логическая модель представляет основную неизменяемую сторону информации, которую клиент должен поддерживать. Логическая модель может быть реализована разными способами в зависимости от потребностей физической реализации. Независимо от того, используем ли мы SQL Server 2000, Oracle, Access или даже Excel для размещения данных, логическая модель должна остаться неизменной. В процессе логического моделирования мы в первую очередь должны быть убеждены, что каждая часть информации зарегистрирована, так что ее можно будет где-нибудь разместить. Поэтому, когда мы будем в следующих двух главах выполнять процесс нормализации, наша модель будет превращаться из дезорганизованной в значительной степени массы сущностей, с которыми мы начали работать в предыдущей главе, в достаточно организованный набор сущностей. К концу этого процесса мы должны идентифицировать каждую отдельную часть информации, которую мы, возможно, пожелаем физически разместить. Главное различие между логическими и физическими моделями — реализация. Так как мы используем тот же самый язык моделирования, логическая модель будет всегда в значительной степени походить на физическую модель, но в течение стадии логического моделирования лучше всего заставить себя игнорировать слово КАК и полностью сосредоточиться на слове ЧТО войдет в БД. Физическая модель, которую мы рассмотрим в главе 10, обеспечивает детальный план действий для реализации БД. Эта модель — то, где мы будем брать сущности, которые обнаружили в предыдущей главе, и превращать их в таблицы. Возможны различные процессы в зависимости от цели и использования данных в БД, и они будут обсуждены во второй половине книги. В то время как логическая модель должна быть достаточно полна, чтобы описать коммерческую деятельность, физическая модель данных, которую мы создадим позже, может быть подвержена компромиссам между характеристиками и эффективностью. Если эти две модели так сильно отличаются, то в чем цель логического моделирования? Это — серьезный вопрос. Фиксируя, какое должно быть оптимальное размещение данных с точки зрения логичности, мы увеличиваем нашу способность создать более хорошую физическую БД, даже если должны отклониться немного (или много) от логической модели. Логическая модель остается важным документом для более поздних изменений в системе и не будет чрезмерно извращена деталями размещения данных.
Сущности В стандарте IDEF1X сущности (которые являются синонимом таблиц) моделируются прямоугольниками, что используется в большинстве методологий моделирования данных. Имеются два различных типа сущностей, которые мы моделируем: идентификаторо-независимый и идентификаторо-зависимый, также часто упоминаемые как независимый (independent) и зависимый (dependent) соответственно.
Тип сущности Независимая сущность называется таким образом потому, что она не имеет никаких зависимостей первичного ключа от любой другой сущности или, другими словами, нет никаких внешних или мигрирующих ключей в первичном ключе. В главе 3 мы обсуждали внешние ключи, но IDEF1X вводит дополнительный термин, который является несколько запутывающим, но более иллюстративным, чем внешний ключ, — мигрирующий. Термин "мигрирующий" является обычным и включен в описание, 109
Глава 5 но это может ввести в заблуждение, так как мигрировать означает двигаться. Вместо того чтобы фактически передвигаться, он относится к первичному ключу одной сущности, "мигрируя" (копируется) как атрибут в другой сущности, таким образом, устанавливая отношения между этими двумя сущностями. Следовательно, сущность "независима" от любых других сущностей. Все атрибуты, которые не мигрируют, являются собственными, поскольку они имеют свое происхождение в текущей сущности. Независимая сущность отображается прямоугольником с незакругленными углами следующим образом:
Здесь Independent
— сущность "Независимая".
Зависимая сущность противоположна независимой сущности, поскольку она будет иметь первичный ключ другой сущности, который мигрировал в ее первичный ключ. Мы будем редко завершать работу, используя зависимые сущности на стадии физического моделирования проекта. Это связано с тем, что первичный ключ сущности не должен быть изменяем в БД, и, конечно, не иметь зависимые сущности, на которые он ссылается. Конечно, концепция каскадной коррекции существует, так что это не такая уж серьезная проблема, но мы увидим позже в главе 10 трудности, характерные для формирования сущностей таким образом. Мы рассмотрим более глубоко идею независимых и зависимых сущностей в разделе этой главы, посвященном идентифицируемым и неидентифицируемым отношениям. Зависимая сущность будет изображаться прямоугольником с закругленными углами: Dependent
Здесь Dependent — сущность
"Зависимая".
То, что мы здесь видим, немного напоминает ситуацию яйца и цыпленка. Зависимая сущность зависит от некоторого типа отношений. Последующий раздел, посвященный атрибутам, содержит информацию, которая основана на некоторых типах отношений, которые мы еще не рассмотрели. Однако ясно, что мы не можем ждать, пока рассмотрим отношения, чтобы представить материал, необходимый для завершения рассмотрения сущностей. Если Вы впервые рассматриваете модели данных, эту главу может потребоваться перечитать, чтобы получить полную картину, поскольку концепция независимых и зависимых объектов связана с отношениями.
110
Моделирование данных Обозначение Х о т я описание I D E F 1 X не дает п р а в и л а , к а к н а з ы в а т ь с у щ н о с т и , п р и ф о р м и р о в а н и и сущностей м ы д о л ж н ы у п о м я н у т ь этот момент. Один и з наиболее в а ж н ы х а с п е к т о в п р о е к т и р о в а н и я и л и р е а л и з а ц и и любой с и с т е м ы — то, к а к в ы н а з ы в а е т е в а ш и о б ъ е к т ы , п е р е м е н н ы е , и т. д. Обозначение объектов Б Д в этом не о т л и ч а е т с я , и в о з м о ж н о более в а ж н о ясно н а з в а т ь о б ъ е к т ы Б Д , чем другие о б ъ е к т ы п р о г р а м м и р о в а н и я . Н а з в а н и я , к о т о р ы е в ы даете в а ш и м с у щ н о с т я м (и а т р и б у т а м т а к ж е ) , будут переведены в н а з в а н и я с у щ н о с т е й , к о т о р ы е т о ж д е с т в е н н о будут и с п о л ь з о в а т ь с я п р о г р а м м и с т а м и и п о л ь з о в а т е л я м и . Л о г и ч е с к а я модель будет р а с с м а т р и в а т ь с я в а м и к а к п е р в и ч н о е схемное р е ш е н и е того, к а к Б Д б ы л а з а д у м а н а , и д о л ж н а б ы т ь ж и в ы м д о к у м е н т о м , к о т о р ы й в ы и з м е н я е т е перед и з м е н е н и е м любых физических структур. Несколько основных руководящих принципов для обозначения сущностей: •
Н а з в а н и я с у щ н о с т е й н и к о г д а не д о л ж н ы быть во м н о ж е с т в е н н о м ч и с л е . П е р в о п р и ч и н а этого — то, что н а з в а н и е д о л ж н о о б р а щ а т ь с я к э к з е м п л я р у с м о д е л и р о в а н н о г о о б ъ е к т а , а не к совокупности объектов. К р о м е того, звучит глупо, если с к а з а т ь , ч т о в ы и м е е т е " з а п и с ь а в т о м о б и л е й " ( з а п и с ь (record) — конструкция, используемая в программировании. Прим. перев.). Н е т , в ы имеете з а п и с ь а в т о м о б и л я . Д л я д в у х автомобилей в ы и м е л и бы две з а п и с и а в т о м о б и л я . (Однако, обратите в н и м а н и е , что с и с т е м н ы е т а б л и ц ы в SQL S e r v e r все во м н о ж е с т в е н н о м ч и с л е , к о т о р ы е я л и ч н о и м и т и р о в а л в период моего с т а н о в л е н и я . )
•
Д а в а е м о е н а з в а н и е д о л ж н о непосредственно соответствовать с о д е р ж а н и ю с у щ н о с т и . Н а п р и м е р , если в ы моделируете з а п и с ь , к о т о р а я п р е д с т а в л я е т ч е л о в е к а , н а з о в и т е ее person (человек). Е с л и в ы моделируете автомобиль, н а з о в и т е это automobile (автомобиль). Обозначение — не всегда урезанное и сухое, но б л а г о р а з у м н о и м е т ь н а з в а н и я п р о с т ы е и по сути.
Ч а с т о н а з в а н и я с у щ н о с т е й д о л ж н ы быть составлены и з н е с к о л ь к и х слов. К о н е ч н о , допустимо в к л ю ч а т ь в н а з в а н и я п р о б е л ы , когда н у ж н о и с п о л ь з о в а т ь н е с к о л ь к о слов, но это не о б я з а т е л ь н о . Н а п р и м е р , с у щ н о с т ь , к о т о р а я х р а н и т адреса л ю д е й , м о г л а бы б ы т ь н а з в а н а : Person Address, Person_Address и л и , и с п о л ь з у я с т и л ь , к которому я недавно п р и в ы к , и тот, которому м ы будем следовать в этой к н и г е , — p e r s o n A d d r e s s . Общее н а з в а н и е этого т и п а обозначения — " н о т а ц и я в е р б л ю д а " и л и с м е ш а н н ы й с л у ч а й . Обратите внимание, что мы находимся на логической стадии моделирования. Мы вообще хотели бы избежать использования пробелов в названиях наших физических структур. Хотя и допустимо иметь названия с пробелами, это не очень хорошо с точки зрения удобства. В SQL Server вам потребовалось бы использовать эти названия, окруженные квадратными скобками, [подобно этому примеру]. Н и к а к и е с о к р а щ е н и я не д о л ж н ы и с п о л ь з о в а т ь с я в л о г и ч е с к и х и м е н а х с у щ н о с т е й . К а ж д о е слово д о л ж н о б ы т ь полностью п о н я т н о . Аббревиатуры имеют т е н д е н ц и ю з а п у т а т ь с м ы с л . Однако с о к р а щ е н и я могут б ы т ь н е о б х о д и м ы в ф и з и ч е с к о й модели из-за некоторого стандарта о б о з н а ч е н и й , к о т о р ы й в ы н у ж д а е т вас это д е л а т ь . Е с л и в ы используете с о к р а щ е н и я , то д о л ж н ы г а р а н т и р о в а т ь , ч т о о п и с а т е л ь к а ж д ы й р а з и с п о л ь з у е т одно и то ж е с о к р а щ е н и е . Это — одна и з о с н о в н ы х п р и ч и н избегать с о к р а щ е н и й , чтобы не п о л у ч и т ь описатели, н а з в а н н ы е " d e s c r i p t i o n " , " d e s c r y " , " d e s c " , " d e s c r i p " и " d e s c r i p t n " , все обозначающие о п и с а т е л ь d e s c r i p t i o n . О ф и з и ч е с к и х о б о з н а ч е н и я х будем д о п о л н и т е л ь н о говорить в главе 10. О д н а к о небольшое п р е д у п р е ж д е н и е : в а ж н о не и с п о л ь з о в а т ь д л я 5—1868
"I "I "I
Глава 5 сущностей длинные описательные названия в виде целых предложений типа leftHandedMittensLostByKittensOnSaturdayAfternoons (рукавицы с левой руки, затерянные котятами в субботу после полудня) за исключением случая, когда эта сущность должна отличаться от leftHandedMittensLostByKittensOnSundayMornings (рукавицы с левой руки, затерянные котятами в воскресенье утром) — усечение на экране затруднит чтение названия. Лучшим названием могло бы быть mittens (рукавицы) или даже lostMittens (потерянные рукавицы). Многое из того, что кодируется в большом названии, будет, вероятно, само по себе сущностями: Mittens (рукавицы), Mitten Status (состояние рукавицы), Mitten Hand (с какой руки рукавица), Mitten Used By (рукавица, используемая для ...), Kittens (котята), и т. д. Однако это больше связано с понятием нормализации, и будет обсуждено далее в главах 6 и 7.
Атрибуты Атрибуты сущности должны иметь уникальные имена внутри нее. Они представляются в виде списка имен внутри прямоугольника сущности: AttributeExample Attribute 1 Attribute 2 Здесь AttributeExample
— сущность "Пример атрибутов"; Attribute
— атрибут.
Замечание: формально, это неправильная сущность, так как не имеет определенного первичного ключа (как требуется в IDEF1X). Мы рассмотрим ключи в следующем разделе. На данном этапе мы просто ввели бы все атрибуты, которые определили на стадии разработки. Практически, было бы удобно объединить процесс выявления сущностей и атрибутов с начальной стадией моделирования. Все будет зависеть от того, сколь хорошие средства вы используете в своей работе. Большинство средств моделирования данных обеспечивает быстрое построение моделей и хранение обширной информации для документирования их сущностей и атрибутов. На ранних стадиях логического моделирования' может быть весьма большое различие между атрибутом и столбцом. Как мы увидим в следующих двух главах, определение, что атрибут может хранить, изменится совсем немного. Например, атрибуты человека могут начинаться просто с адреса и номера телефона. Однако, как только мы это нормализуем, то разобьем их на много столбцов (адрес — на номер квартиры, название улицы, город, штат, почтовый индекс и т. д.) и, возможно, на много других сущностей.
Первичный ключ Как мы отметили в предыдущем разделе, сущность в IDEF1X должна иметь первичный ключ. Это удобно для нас, так как в главе 3 мы определили, что для кортежей или сущности каждая запись должна быть уникальна. Первичный ключ может быть единственным атрибутом, или он может быть составным ключом (определенным ранее как ключ с несколькими полями), и должны быть значения для всех атрибутов ключа (на физическом языке, в первичном ключе не допускаются значения NULL). Обратите внимание, что никакое дополнительное обозначение не требуется, чтобы указать, что величина является первичным ключом. 112
Моделирование данных Первичный ключ обозначается помещением его атрибутов выше горизонтальной линии, проходящей через прямоугольник сущности: Primary Key Example Primary Key Attribute 1 Attribute 2 Здесь Primary Key Example — сущность "Пример первичного ключа"; Primary первичный ключ; Attribute — атрибут.
Key —
На ранней стадии логического моделирования я обычно не люблю назначать какие-либо значащие атрибуты первичного ключа. Главная причина этого состоит в том, чтобы сохранить чистую модель. Я обычно добавляю абстрактный первичный ключ, чтобы перейти к другим сущностям; это позволяет мне видеть, кто чем владеет. Как мы увидим позже в этой главе, абстрактный первичный ключ будет содержать название сущности, так что, когда мы создаем отношения, которые вызывают миграцию ключа, независимо от того, какие первичные ключи мы выберем, они будут мигрировать к сущности-потомку в отношении. Поскольку, вероятно, мы не выберем первичный ключ, который будет в конечном счете реализован, я обычно моделирую все потенциальные ключи (или уникальные идентификаторы) как вторичные ключи (непервичные уникальные ключи). Результатом является то, что в логической модели становится ясно, какие сущности играют роль собственника по отношению к другим сущностям. Это, конечно, не является требованием логического моделирования, а отражает мое собственное предпочтение, которое я счел полезным методом документирования и который оставляет модель чистой и соответствует моему методу реализации, рассмотренному позже. Использование естественного ключа в качестве первичного ключа на логической стадии моделирования не только допустимо, но и предпочитается многими архитекторами. Я попытался приравнять все мои логические объекты объектно-ориентированным классам, которые идентифицированы не первичным ключом, а, скорее, указателем.
Вторичные ключи Наша следующая тема посвящена вторичным ключам. Как было предварительно определено в главе 3, вторичные ключи — набор одного или большего количества полей, чью уникальность мы хотим гарантировать по всей сущности. Вторичные ключи не имеют специальных обозначений, как и первичные ключи, и не мигрируют при любых отношениях. Они изображаются на модели очень простым способом: Alternate Key Example Primary Key Alternate Key (AK1) Alternate Key 2 Att 1 (AK2) Alternate Key 2 Att 2 (Ak2) Здесь Alternate Key Example — сущность "Пример вторичных ключей"; Primary первичный ключ; Alternate Key — вторичный ключ; Att — атрибут.
Key —
113
Глава 5 В этом примере имеются две вторичные ключевые группы: группа АК1 (Alternate Key 1), которая имеет в качестве своего члена один атрибут, и группа АК2 (Alternate Key 2), которая имеет два атрибута. Одно расширение, которое использует ERwin (средство моделирования данных, созданное Computer Associates: http://ca.com/products/alm/erwin.htm), показано ниже: Alternate Key Example Primary Key Alternate Key (AK1.1) Alternate Key 2 Att 1 (AK2.1) Alternate Key 2 Att 2 (Ak2.2) Обратите внимание, что имеется дополнительное обозначение , прибавляемое к АК1 и АК2, чтобы обозначить положение атрибута в ключе. Формально в логической модели эту информацию не следует показывать, и, конечно, она должна игнорироваться. Логически не имеет значения, который атрибут используется первым в ключе. Когда ключ будет физически реализовываться, это будет интересным только по причинам самой реализации.
Внешние ключи Внешние ключи также называются мигрирующими атрибутами. Они являются первичными ключами сущности, которые служат как указатель на экземпляр информации в другой сущности. Эти ключи снова являются результатом отношений, которые мы рассмотрим в следующем разделе. Внешние ключи обозначаются как вторичные ключи с добавлением символов "FK" (Foreign Key) после внешнего ключа, например, Foreign Key Example Primary Key Foreign Key (FK) Здесь Foreign Key Example — сущность "Пример внешнего ключа"; Primary первичный ключ; Foreign Key — внешний ключ.
Key —
Диаграмма не показывает, из какой сущности мигрирует ключ, и может возникнуть неразбериха в зависимости от того, как вы выберете ваши первичные ключи. Это — ограничение всех методологий моделирования, поскольку было бы излишне запутанным для процесса, если бы мы показали название сущности, откуда берется ключ, по нескольким причинам: •
Нет никакого предела (да и не должно быть) тому, как далеко ключ будет мигрировать от своего первоначального владельца.
•
Это также неблагоразумно потому, что один и тот же атрибут мог бы мигрировать из двух разных сущностей, особенно на ранних стадиях процесса логического проектирования.
Это — одна из причин, чтобы для схемы первичного ключа, которую мы создадим в нашей логической модели, просто выбирать название <entityName>Id (1с1) как имя ключа для данной сущности. По моему мнению, такое название сущности легко опознаваемо и более ясное.
114
Моделирование данных Домены Домен (Domain) — термин, который мы, к сожалению, собираемся использовать в двух очень близких контекстах. В главе 3 мы узнали, что домен — набор допустимых значений для атрибута. В IDEF1X домены имеют тонко отличающееся определение, которое охватывает предыдущее определение, но с полезной характерной особенностью. В этом случае домены — механизмы, которые не только позволяют нам определять допустимые значения, которые могут быть размещены в атрибуте, но также и обеспечивают вид наследования в определениях наших типов данных. Вот примеры: •
String (строка) — строка символов.
•
SocialSecurityNumber (номер социального страхования) — набор символов с форматом ###-##-####.
•
Positivelnteger (положительное целое) — целое число, которое предполагает домен от О до max (целое число).
Далее мы можем создать подтипы, которые унаследуют значения исходных доменов. Мы будем строить домены для любых атрибутов, которые регулярно используем, а также домены, которые являются шаблонами для нечасто используемых атрибутов. Например, мы могли бы иметь основной домен типа символа, где определяем, что нужны не все символы. Мы могли бы также определить домены, названные пате (название) и description (описание), для использования во многих сущностях, и определить их, как это нам потребуется. При логическом моделировании требуется только несколько видов информации, которую мы, вероятно, будем хранить, таких как обычные типы атрибутов — символ, числовой, логический, или даже двоичные данные. Мы должны хранить эти домены как реализацию описания независимых типов данных. Например, мы могли бы определить домен: •
GloballyUniqueldentifier (GUID — глобальный уникальный идентификатор) — значение, которое будет уникальным независимо от того, где оно сформировано.
В SQL Server мы могли бы использовать uniqueidentifier (GUID-значение), чтобы реализовать этот домен. В Oracle, где нет точно того же механизма, мы реализовали бы это по-другому; то же самое было бы истинным, если мы используем текстовые файлы для хранения данных. Когда мы начнем физическое моделирование, то будем использовать те же самые домены, чтобы унаследовать также и физические свойства. Это — реальное достоинство использования доменов. Создавая для атрибутов шаблоны многократного применения, которые будут также использоваться, когда начнем создавать столбцы, мы уменьшим количество усилий, которые потребуются, чтобы строить простые сущности, составляющие большую часть нашей работы. Это также позволит создать стандарты компании, многократно используя те же самые домены во всех наших общих моделях. Позже физические детали, такие как тип данных, ограничения и возможность не иметь значения, будут выбираться только из нескольких основных физических свойств, которые можно наследовать. Так как нам нужно иметь намного меньше доменов, чем реализованных атрибутов, мы получим двойную выгоду быстрой и последовательной реализации. Однако мы не можем использовать механизмы наследования, когда строим наши таблицы вручную. Реализация доменов строго основана на используемых программных средствах.
115
Глава 5 Например, мы могли бы определить следующую иерархию:
--—
String
.^
Name [ FileName J
[Description
1 "^^-~ LastName ] f FirstName J
Здесь String (строка) — базовый домен, от которого мы затем получим Name (название) и Description (описание). FileName (имя файла), FirstName (имя) и LastName (фамилия) являются наследниками Name (название). На стадии логического моделирования это может показаться работой впустую, потому что большинство этих доменов будет обладать некоторыми основными деталями типа запрета использования NULL или незаполненных данных. Однако вам не всегда может потребоваться FileName, в то время как LastName — практически всегда. Важно реализовать домены для возможно большего числа различных типов атрибутов в случае, если вы находите правила или типы данных, которые являются общими для любых доменов, которые вы задали. Домены — одна из наиболее захватывающих особенностей IDEF1X. Они обеспечивают легкий метод создания стандартных атрибутов, сокращения как промежутка времени, требуемого для этого создания, так и числа ошибок, которые возникают при этом. Специальные программные средства реализуют домены, обеспечивая определение и наследование большинства свойств цепочки доменов, что делает создание БД более легким. Очевидно, что если вы создаете ваши БД и модели вручную, менее вероятно, что вы реализуете ваши таблицы, используя наследование доменов. Домены или типы данных можно показать у сущности справа от названия атрибута следующим образом: domainExample attributeName: domainName attributeName2: domainName Здесь domainExample — сущность "Пример домена"; attributeName domainName — имя домена.
— имя атрибута;
Так, если мы имели сущность, которая содержит значения доменов для описания типа person (человек), мы могли бы сформировать модель: personType personTypeld: primaryKey name: name description: description Здесь personType — сущность "тип-человек"; personTypeld— типа-человека; name — имя; description — описание. 116
идентификатор
Моделирование данных В этом случае мы имеем три домена: •
•
•
PrimaryKey — является указателем строки для мигрирующего внешнего ключа. Реализация не подразумевает построения домена, так что мы можем выбрать это значение любым образом, как пожелаем. . Name — домен общего вида для хранения имени экземпляра сущности. Используя его как наш стандартный домен имени всюду, где мы хотим поместить название, мы обеспечим согласованность. Если имя данной сущности не соответствует этому образцу, мы можем создать новый домен. Description — тот же самый тип домена, что и домен имени, только хранит описание.
Обозначение Обозначение атрибута несколько более интересно, чем обозначение сущности. Мы сказали, что название сущности никогда не должно быть во множественном числе. Тот же самое формально истинно и для атрибутов. Однако в этом месте процесса моделирования мы будем все же использовать названия атрибутов во множественном числе. Использование названия во множественном числе может быть хорошим напоминанием, что мы ожидаем несколько значений. Например, мы могли бы иметь сущность Person (человек) с идентифицированным атрибутом Children (дети). Сущность Person идентифицировала бы единственного человека, а атрибут Children идентифицировал бы сыновей и дочерей этого человека. Стандарты обозначения атрибутов всегда были весьма горячей темой с несколькими различными схемами обозначений, разработанными за прошлые 30 лет создания БД. Мы рассмотрим это в главе 10, когда начнем реализовывать нашу БД. Стандарт обозначений, которого мы будем придерживаться, очень прост. Q
Обычно не нужно повторять название сущности в названии атрибута, за исключением первичного ключа. Название сущности подразумевается включением атрибута в сущность. Так как первичный ключ будет мигрировать к другим сущностям, эта маленькая уступка делает миграцию более простой, поскольку мы не должны переименовывать каждый атрибут после миграции, не говоря уже о создании присоединения к устройству очистки памяти в SQL.
•
Выбранное название атрибута должно отражать точно, что содержится в атрибуте и как это относится к сущности записи.
•
Как и у сущностей, никакие сокращения не должны использоваться в логическом обозначении атрибутов. Каждое слово должно быть записано полностью. Если какое-то сокращение все же должно использоваться в каком-то месте из-за некоторого стандарта обозначений, то должен быть какой-то способ, чтобы удостовериться, что оно используется последовательно, как было рассмотрено ранее в этой главе.
Последовательность — ключ к надлежащему обозначению атрибутов, так что если у вас или у вашей организации нет стандарта назначения имен, стоило бы его разработать. Мой принцип задания имен — они должны быть простыми и удобочитаемыми, избегая сокращений. Этому стандарту мы будем следовать и при логическом моделировании, и на физической стадии. Независимо от того, каков ваш стандарт, задание образцов обозначения будет делать ваши модели легкими для работы и для вас самих, и для ваших программистов и пользователей. Любой стандарт всегда лучше отсутствия какого-либо стандарта.
117
Глава 5 Другой шаг, который вы должны сделать — это спросить о стандартах обозначений Вашего клиента (или работодателя), чтобы обеспечить будущее взаимопонимание, а не создавать новые стандарты.
Отношения До сих пор мы рассматривали конструкции, в значительной степени одинаковые при всех методологиях моделирования данных. Сущности всегда обозначались прямоугольниками, и атрибуты — словами в этих прямоугольниках. Однако отношения — совсем другой вопрос. Каждая методология дает свои отношения. IDEF1X весьма отличается от всех других прежде всего хорошим способом представления. Причина, по которой я предпочитаю IDEF1X — его способ представления отношений. Чтобы понять это, возьмем термины parent (передок) и child (потомок) и пример отношения между ними. Из глоссария в описании IDEF1X мы находим следующие определения: •
Entity, Child (сущность, потомок) — сущность в определенных отношениях связи, чьи экземпляры могут быть связаны с нулем или одним экземпляром другой сущности (сущность-предок).
•
Entity, Parent (сущность, предок) — сущность в определенных отношениях связи, чьи экземпляры могут быть связаны с множеством экземпляров другой сущности (сущность-потомок).
•
Relationship (отношение) — связь между двумя сущностями или между экземплярами одной и той же сущности.
Это настолько ясные определения, что заслуживают быть принятыми на государственном уровне. Каждое отношение обозначается линией, проведенной между двумя сущностями со сплошным кружком на одном конце этой линии. Parent
Child
Первичный ключ предка мигрирует к потомку. Это представляет то, как мы обозначаем внешний ключ на модели. Мы попытаемся пройти все различные структуры в IDEFlX-методологии, сопровождая их кратким обзором некоторых других методологий, что будет необходимо нам для понимания, так как не каждый будет использовать IDEF1X. Имеется несколько различных типов отношений, которые мы рассмотрим: •
1 1 8
Идентифицирующее
Table A
Table В
L
LJ
Неидентифицирующее
Table A
Q
Необязательное
Table A
J Л
Z
D
Table В
Table В
о-
•
Моделирование данных
•
Рекурсия
•
Классификация или подтипы
•
Многие ко многим
Здесь Table — сущность "таблица"; Tree Entity ОДИН КО
Table A
Table В
— сущность "дерево".
МНОГИМ
Отношение "один ко многим" является на самом деле примером неправильного употребления, и может рассматриваться как отношение "один к любому числу". Оно охватывает отношения "один к нулю", "один к одному", "один ко многим" или, может быть, "один к точно п". Мы увидим, что формально более точно — это "один к (от m до п)"; так мы можем определить многие очень точные (или очень свободные) термины, как диктует ситуация. Однако более привычный термин — "один ко многим", и мы не будем делать уже запутанный термин еще более запутанным. Имеются два основных типа отношения "один ко многим" —идентифицирующее и (неудивительно) неидентифицирующее. Различие, как мы увидим, определяется тем, мигрирует ли первичный ключ. Имеется очень немного разновидностей отношения "один ко многим", и мы рассмотрим все их (и их обозначения) в этом разделе.
Идентифицирующие отношения Идентифицирующее отношение указывает, что атрибут мигрирующего первичного ключа мигрирует в первичный ключ потомка следующим образом: Parent Parent Primaiy Key Attribute 1 Attribute 2
Child 'Child Primaiy Key Parent Primaiy Key (FK) Attribute 1 .Attribute 2
;
Здесь Parent — сущность "предок"; Child — сущность "потомок"; Parent Primary Key— первичный ключ предка; Child Primary Key — первичный ключ потомка; Attribute — атрибут.
119
Глава 5 Обратите внимание, что сущность-потомок в отношении изображена как прямоугольник с закругленными углами, что означает зависимую сущность. Причина, что оно называется идентифицирующим отношением, заключается в том, что мы должны будем иметь экземпляр предка, чтобы иметь возможность определить запись экземпляра-потомка. Существование экземпляра-потомка (определяемого как "характерные или необходимые свойства, которые служат, чтобы определить или идентифицировать кое-что") определяется существованием предка. Другими словами, идентификация и определение записи-потомка основаны на существовании записи-предка. Пример — заказ на поставку и его элементы. Без заказа на поставку элементы не имеют никакого смысла.
Неидентифицирующие отношения Неидентифицирующее отношение означает, что атрибут первичного ключа не мигрирует в первичный ключ потомка. Оно используется более часто, чем идентифицирующее отношение. В то время как идентифицирующие отношения были основаны на необходимости существования предка, что определяет даже само существование потомка, неидентифицирующее отношение — (что не удивительно) полная противоположность. Взяв пример заказа на поставку, рассмотрим продавца изделия. Он не определяет существование элементов заказа. Продавец в этом случае может быть, а может и не быть, так как бизнес-правила могли бы определять, но обычно они это не делают, является ли отношение идентифицирующим или неидентифицирующим; сами данные будут фундаментальными свойствами отношения. Неидентифицирующие отношения моделируются пунктирной линией: Parent
Child
Здесь Parent — сущность "предок"; Child — сущность "потомок". Почему вам следует использовать идентифицирующие вместо неидентифицирующих отношений? Причина фактически довольно проста. Если сущность-предок (как мы отметили в предыдущем разделе) определяет сущность-потомок, то мы будем использовать идентифицирующее отношение. Если же, с другой стороны, отношение определяет один из атрибутов потомка, то мы используем неидентифицирующее отношение. Как другой характерный пример рассмотрим следующее: •
Идентифицирующее — Пусть есть сущность, которая хранит партнеров, и сущность, которая хранит номера телефона партнеров. Партнер (contact) определяет номер телефона, и без партнера не имелось бы никакой необходимости в номере телефона партнера (contactPhoneNumber). Contact
ContactPhoneNumber J
120
Моделирование данных •
Неидентифирующее — Возьмем сущности, которые мы определили для идентифицирующего отношения, вместе с дополнительной сущностью, которую назовем contactPhoneNumberType (тип номера контактного телефона). Эта сущность связана с сущностью contactPhoneNumber, но неидентифицирующим способом, и определяет набор возможных типов номеров телефонов ("Голосовой", "Факс" и т. д.), каким мог бы быть contactPhoneN umber. Тип номера телефона не определяет номер телефона, а просто классифицирует его. Contact
ContactPhoneNumber
С
J
ContactPhoneNumberType Сущность contactPhoneNumberType обычно называется сущностью домена или таблицей домена. Вместо того чтобы иметь фиксированный домен для атрибута, мы проектируем сущности, которые позволяют программно изменять домен без перекодирования ограничений и кода клиента. Как дополнительное преимущество, мы можем добавлять столбцы, чтобы определять, описывать и расширять значения домена для обеспечения бизнес-правил. Это также позволяет пользователю клиента строить для других пользователей списки, чтобы выбирать значения с очень небольшой долей программирования. Имеются два различных класса неидентифицирующих отношений — обязательное и необязательное. Мы поподробнее рассмотрим их в следующих двух разделах.
Обязательное отношение Обязательные неидентифицирующие отношения называются таким образом потому, что требуется мигрирующее поле в экземпляре потомка. Когда реализуется это отношение, мигрирующий ключ должен быть помечен как NOT NULL (не является NULL). D
arent Parent Primary Key Attribute 1 Attribute 2
Child Child Primary Key Parent Primary Key (FK) Attribute 1 Attribute 2
Здесь Parent — сущность "предок"; Child — сущность "потомок"; Parent Primary Key— первичный ключ предка; Child Primary Key — первичный ключ потомка; Attribute — атрибут.
121
Глава 5 Обратите внимание, что сущность-потомок в этом примере не имеет закруглений у прямоугольника. Это потому, что обе сущности независимы, так как ни один из атрибутов первичного ключа потомка не является внешним ключом. Другой пример такого отношения мог бы быть взят из БД проката кинофильмов: genre
-CD
Здесь genre — сущность "жанр"; movie — "кинофильм". Отношение можно было бы представить в виде Genre Movie, где сущность Genre — сущность "один", a Movie — "многие". Обязательно каждый арендуемый кинофильм должен иметь жанр, чтобы их можно было расставить по полкам.
Необязательное отношение Нам не всегда нужно, чтобы потомок имел значение мигрирующего ключа. В этом случае мы зададим мигрирующий ключ потомка необязательным, для которого в этом случае разрешается значение пустого указателя (NULL). Так как неидентифицирующее отношение означает, что предок — атрибут потомка, то это эквивалентно наличию необязательного атрибута (атрибут, который может иметь значение NULL). Мы показываем это пустым ромбом на противоположном конце линии с черным кружком, как изображено ниже: Parent Parent Primary Key Attribute 1 О Attribute 2
Child Child Primary Key -• Parent Primary Key (FK) Attribute 1 Attribute 2
Здесь Parent — сущность "предок"; Child — сущность "потомок"; Parent Primary Key — первичный ключ предка; Child Primary Key — первичный ключ потомка; Attribute — атрибут. Если вы задаетесь вопросом, почему не может быть необязательного идентифицирующего отношения, то потому, что вы не можете иметь никаких необязательных атрибутов в первичном ключе; это справедливо и для IDEF1X, и для SQL Server 2000. В качестве необязательного отношения "один ко многим" рассмотрим следующую структуру: invoice
invoiceLineltem
С
>—
dsicountType
Здесь invoice — сущность "счет-фактура"; invioceLineltem счета-фактуры"; discountType — сущность "тип скидки". 122
— сущность "элемент
Моделирование данных Сущность invoiceLineltem содержит элементы счета-фактуры, определяющие оплату. Далее рассмотрим ситуацию, когда мы в некоторых случаях применяем стандартные скидки к элементу счета-фактуры. Тогда отношение от invoiceLineltem к сущности discountType — необязательное.
Мощность отношения Мощность отношения означает число экземпляров потомка, которое может быть помещено для каждого предка этого отношения. Следующая таблица показывает шесть возможных мощностей, которые могут быть у отношения.
Один к нулю или большему числу
Один к одному или большему числу
Rarent
Один к нулю или одному (не больше, чем к одному)
Child 7
V
J Один к (от четырех до восьми)
Один к точно N (здесь 5 означает, что предок имеет точно 5 потомков)
Специальная запись, описывающая мощность
Здесь Parent — сущность "предок"; Child — сущность "потомок". Возможное отношение "один к одному или большему числу" могло бы использоваться, чтобы представить отношение между опекуном (guardian) и учащимся (student) в школьной БД: Guardian
Student О
4
Р
Мне нравится этот пример, потому что он выражает потребности чрезвычайно хорошо. Он говорит, что если запись guardian существует, то должен существовать student, но для нас не требуется, чтобы запись student имела поле guardian для размещения данных о нем.
123
Глава 5 Другой пример, который я обычно люблю рассматривать при обсуждении вопроса мощности отношения, следующий. Рассмотрим клуб, который имеет членов (Member) и некоторые положения (Position), которые они должны или могли занять: Member
Position
Member
Position
А
В
z
Member С
Position 0-2*
В примере А мы говорим, что член клуба может занять столько положений, сколько их имеется. Во втором случае мы говорим, что он может занять по крайней мере одно положение, в последнем случае — 0, 1 или 2. Все они выглядят одинаково, но Z или 0-2 используются чаще. Лично я рассмотрел, включая примеры, все из этих типов мощностей, но в большинстве случаев это либо слишком трудно, либо слишком глупо, так что мы просто упомянули несколько более типичных. Однако очень хорошо иметь их под рукой, если они станут необходимы.
Функциональные имена Функциональное имя — альтернативное название, которое мы даем атрибуту, когда используем его как внешний ключ. Цель функционального имени состоит в том, что мы иногда должны разъяснять использование мигрирующего ключа, потому что или сущность-предок очень общая, и мы хотим дать более определенное название, или мы имеем многократные отношения от той же самой сущности. Так как названия атрибутов должны быть уникальны, нам нужно назначить различные названия для записей потомка. В нашем примере: Parent Parent Primary Key
Child • • Child Primary Key Parent Primary Key 2.Parent Primary Key (FK) Parent Primary key 1 .Parent Primary Key (FK) Здесь Parent — сущность "предок"; Parent Primary Key — первичный ключ предка; Child — сущность "потомок"; Child Primary Key — первичный ключ потомка. 124
Моделирование данных
... мы имеем два отношения от сущности-предка к сущности-потомку, и мигрирующие атрибуты были названы как P a r e n t Primary Key 1 (первый первичный ключ предка) и Parent Primary Key 2 (второй первичный ключ предка). В качестве примера пусть у вас есть сущность User (пользователь), и вы хотите разместить name (имя) или Id (идентификатор) пользователя, которые формируют сущность Object (объект). Мы получили бы ситуацию наподобие следующей: User Primary Key
Object • * Primary Key Created For Primary Key.Primary Key (FK) Created By Primary Key.Primary Key (FK) Обратите внимание, что мы имеем два отношения к сущности Object от сущности User. Один назван [Created For Primary Key] (созданный для первичного ключа), и другой [Created By Primary Key] (созданный первичным ключом). Этот пример также показывает, почему вы поместили бы название сущности в атрибут первичного ключа. Когда первичный ключ мигрирует к другой сущности, важно знать, какова первоначальная сущность-предок. Так как атрибут в сущности Object имеет название [Primary Key] (первичный ключ), мы бы имели название первого атрибута таким же, как и второго.
Другие типы отношений Имеется несколько других, менее важных типов отношений, которые не используются так уж часто. Однако они чрезвычайно важны для понимания.
Рекурсия Одно из более сложных для реализации отношений, но одно из наиболее важных — рекурсивное отношение, известное также как самоприсоединяющееся, иерархическое, самоссылающееся или самоотносящееся. Оно моделируется неидентифицирующим отношением не к другой сущности, а к себе самой. Мигрирующему ключу отношения дают функциональное имя (и я обычно использую соглашение по обозначению, добавляя "parent" (предок) в начале названия атрибута, но это не обязательно). Tree Entity -"I ? 1 Здесь Tree Entity
1 1 1
— сущность-дерево. 125
Глава 5 Рекурсивное отношение удобно для создания древовидных структур, как в следующей структуре организации:
A l l « _
—
,
^
—
—
—
-
•
—
I T
H R
—
M a r k e t i n g
**•
Programming
D a t a b a s e M a n a g e m e n t
Здесь All — вся организация; IT—подразделение информационных технологий; HR — отдел кадров; Marketing — подразделение маркетинга; Programming — отдел программирования; Database Management — отдел менеджеров БД. Чтобы объяснить эту концепцию, нам нужно рассмотреть данные, которые размещаются с помощью следующей иерархии:
Здесь organization
— сущность "организация".
Мы используем organization!^rame (название организации) в качестве первичного ключа и parentOrganizationName (название организации — предок) как мигрирующий ключ, атрибут с функциональным именем, который задает само-ссылку соединения: OrganizationName
parentOrganizationName
All IT HR Marketing Programming Database Management
All All All IT IT
х
Account Activity Check Usage ОгафТ Check Vfage В п п ( 3 — Cheowege осадеБи>8сец> t Cheousepr CXcKCCxbjra*» TJtii«ckUsagr drop? Checkuwgf o » a * * check usage a m i s CheckUtage ОгадрбСХЪфгаф I Check Usage 0«CM*d0>9«xtii c£ckut^9i0k«> ^ CKeckU
JXXXXXX
Как вы можете видеть, мы добавили простое резюме к основным требованиям, которые нам выдали. Тогда мы имеем каждую из полученных в итоге групп типов использования чеков, и суммы, перечисленные во втором столбце. Мы создадим прототипы для каждого из трех требуемых отчетов. Будем заниматься здесь именно ими, поскольку они должны быть хорошо обработаны нашими разработанными структурами. Специализированными отчетами, которые требовали пользователи, можно было бы заняться в следующей итерации системы, которая последует в нашей работе. 223
Глава 8
Взаимодействие с внешними системами Единственная внешняя система, с которой мы должны будем иметь дело, принадлежит банку. Как вы можете вспомнить из исходных документов интервью, мы имеем формат автоматического регистра банка, повторенного здесь: Column
Data type
Required
Transaction Date
Date Only
Yes
Transaction Number
String(20)
Yes
Description
String(lOO)
Yes
Item Amount
Money
Yes
Transaction. Transaction Date Transaction Number
Обозначения см. в главе 4. В бумажной версии они имеют дополнительные итоговые типы значений, которые мы очевидно не получим из электронной версии. Мы будем, наверное, желать сделать поля нашего отчета, заполненные нулевыми значениями, или, возможно, заполнить их из итоговых элементов. Мы также должны добавить несколько атрибутов к нашей модели данных для s t a t e m e n t l t e m s (элементы отчета) и обращаем внимание, что даже не рассматриваем, как элемент будет согласован. Так что мы корректируем нашу модель данных следующим образом: account bankld: PrimaryKey (FK) (AK1.1) accountld: PrimaryKey
statement
number: AlphaNumericNumber (AK1.2)
преобразует фонды с помощью ввода
transaction bankld: PrimaryKey (FK)(AK1.1) accountld: PrimaryKey (FK)(AK1.2) transactionld: PrimaryKey statementld: PrimaryKey (FK) statementltemld: PrimaryKey (FK) date: Date number: AlphaNumericNumber (AK1.3) description: Description amount: Amount type: Code
224 I! I
bankld: PrimaryKey (FK) (AK1.1) statementld: PrimaryKey type: Code previousBalance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.2) totalDebits: Amount totalCredits: Amount balancingltems: Balancingltems
имеет элементы в используется для согласования statementltem счета bankld: PrimaryKey (FK) с помощью
statementld: PrimaryKey (FK) statementltemld: PrimaryKey •--О date: Date number: AlphaNumericNumber description: Description amount: Amount
Завершение фазы логического проектирования Обратите внимание, что мы добавили четыре атрибута к statement Item (элемент отчета — см. эту сущность в гл. 6) для каждого из полей в загружаемых пунктах отчета банка. Мы также обращаем внимание, что для использования этих пунктов для баланса нужно связать каждый пункт, используемый для баланса, с пунктом в регистре.
Так же как и в реальной жизни, мы обнаруживаем новые вещи относительно наших представлений, поскольку продвигаемся вперед. Обратите внимание, что мы построили твердую основу в наших предыдущих главах и все, что теперь собираемся делать, — заняться вылизыванием, залатыванием дыр, которые мы вначале не замечали. На этом этапе мы должны попытаться избежать "паралича анализа". Постоянно анализируя, непрерывно опрашивая клиентов и внося все больше и больше изменений в проект, мы никогда не перейдем к стадии реализации. В некоторый момент под проектом должна быть поставлена точка, даже если он и не совершенен на 100 процентов. С этими недостатками придется иметь дело на стадии реализации.
План преобразования данных В нашей системе клиент решил начать с нуля с регистром чека; следовательно, нам не нужно осуществлять никаких преобразований данных. Мы просто начнем счет с кредита или дебета для начального баланса. Это — самый простой тип плана преобразования данных, то есть, ничего не нужно. Поскольку мы заменяем бумажную систему, то если бы клиенты хотели сделать преобразование данных, им потребовалось бы ввести все данные вручную. Часто это невозможно, поскольку слишком дорого и проблематично.
Требуемые объемы Для определения требуемых объемов мы строим простую таблицу, содержащую детали всех других таблиц нашей БД, с ожидаемым числом строк, которые таблицы будут первоначально содержать. Лучший способ оценки этого состоит в том, чтобы взять важные таблицы системы, как показано в нашей модели данных из главы 7, и начать с них. Будем считать, что хорошей отправной точкой для наших оценок была бы таблица^апзас^д-оп (сделка). Тогда мы просто спрашиваем у пользователя относительно каждой таблицы или группы таблиц, и экстраполируем это на некоторые другие таблицы, особенно те, которые мы сами добавили. Обратите внимание, что мы также будем должны собрать некоторую статистику по столбцам. Сделаем весьма немного решений относительно того, как реализованы столбцы, а пользователи могут помочь в оценке их значений. Вы можете также обратиться к существующей системе как к руководству. Возьмем сначала таблицу сделок. В нашем случае пользователи решили начинать с нуля, без фактических сделок. Они ожидают иметь около 300 сделок в месяц, из которых 245 являются чеками, 5 — депозитами, и 50 — прямыми изъятиями.
225
Глава 8 Отсюда мы получаем оценки числа различных получателей платежа, с которых можно начать, и сколько ожидается добавлять. Обычно мы предполагаем остальную часть количества параметров, зависящей от этих значений. Например, из оценки 245 чеков в месяц мы предполагаем, что они имеют 100 получателей платежа, которым эти чеки предназначены, и, вероятно, будет добавлено 30 новых получателей платежа в месяц. Очевидно, чем большую исходную информацию вы получаете от клиентов, тем более достоверны будут эти оценки. Мы завершим следующей таблицей оценок, которую следует рассмотреть с клиентом. Начальное число строк
Увеличение числа строк в месяц
Максимальное число строк
account (счет)
1
0
20
accountReconcile (согласование счета)
0
1
36
address (адрес)
50
30
600
address Line (строка адреса)
55
35
700
address Type (тип адреса)
5
0
10
bank (банк)
1
0
5
check (чек) checkRegister (регистр чека)
0
245
10000
0
1
36
checkUsage (использование чека)
75
400
12000
CheckUsageType (тип использования чека) c i t y (город)
20
0
40
450
20
25000
d e p o s i t (депозит)
0
5
4000
directWithdrawal (прямое изъятие) payee (получатель платежа)
0
50
1000
100
30
300
PayeeAddress (адрес получателя платежа) payeePhoneNumber (номер телефона получателя платежа) phoneNumber (номер телефона)
100
30
600
100
30
600
50
30
600
PhoneNumberType (тип номера телефона) s t a t e (штат)
5
0
10
50
0
70
s t a t e m e n t (отчет) s t a t e m e n t l t e m (элемент отчета)
0
1
36
0
300
15000
t r a n s a c t i o n (сделка)
0
300
15000
user
10
2
50
1000
10
99999
Название таблицы
(пользователь)
zipCode (почтовый индекс) 226
Завершение фазы логического проектирования Вот описание содержания таблицы:
•
Начальное число строк — сколько строк, как мы ожидаем, будет добавлено к таблице, когда система начинает функционировать (нуль указывает, что нет никаких строк).
•
Увеличение числа строк в месяц — число строк, на которое, как мы ожидаем, увеличится таблица (нуль означает отсутствие роста). Максимальное число строк строк.
грубая оценка максимального числа возможных
План проектирования Сделаем некоторые оценки относительно длительности процесса проектирования в виде плана проектирования. Например, чтобы закончить задачу создания этой БД, мы могли бы воспользоваться диаграммой Гантта, наподобие:
:Конек
Задача Физическое моделирование Проектирование защиты данных
5ЛДС01
Продолжи^ гельность ? (дни)
Июнь 2001
Май 2001 5/6
5/13
5/20
5/27
10 6/17 6/24
Июль 2001 ?):-: 7/S . 7/15
5/7Й001 6/1Д 001
19
Создание кода доступа
6Ж2001
7/5Й001
24
Создание отчетов
7/5Ш01
8Л5Д001
30
Нам нужно конкретизировать каждую задачу, например: Физическое моделирование: Выбор типов данных — 1 день Проектирование оптимистических механизмов блокировки — 2 дня Или кое-что вроде этого. Количество времени для задач будет, конечно, субъективным, но должно быть основано на предыдущем опыте, а не на том, сколько страниц Дилберта мы надеемся прочитать, когда сидим за своим столом и делаем вид, что заняты. Поскольку план проектирования вне возможностей этой книги, больше мы не будем это обсуждать.
227
Глава 8
Безопасность Она основана на нашей диаграмме действий, приведенной ранее в этой главе:
Основной пользователь счета
Клерк бухгалтерского учета
Менеджер
228
Завершение фазы логического проектирования Так как мы добавляли новую информацию, как только ее обнаруживали, это должно дать полную картину того, как мы должны будем реализовать безопасность. Мы можем видеть, что есть три роли пользователей и несколько функциональных групп действий, которые следует рассмотреть. Каждый отчет также определяется диаграммой действий. Эта диаграмма действий будет нашим шаблоном для обеспечения безопасности позже при реализации действий.
Окончательный обзор документации Как только мы завершим работу, мы складываем все наши бумаги, делаем копии, распределяем их и назначаем встречу несколькими днями позже. Во время этой встречи наша цель состоит в том, чтобы получить подписи; согласовать то, что все, что требуется в нашей документации, соответствует пожеланиям клиента. Это — то, что я люблю называть отправной точкой. Как только пользователь подпишет копии документов системы, вы готовы начать создание системы. Вы больше не должны тратить много времени на пользователя, за исключением сообщений об успехах. Это не говорит о том, что наши планы незыблемы, поскольку мы, вероятно, найдем вещи, в которых не преуспели в достаточной мере. Хотелось бы надеяться, тем не менее, что любые изменения будут в форме дополнительных атрибутов, а не целого модуля таблиц вместе с новыми экранами.
Резюме Цель, правда, недостижимая, логического проектирования состоит в том, чтобы обнаружить все, что нам нужно для реализации системы. Как только начнется работа по реализации системы, будет очень трудно изменить планы. Однако я предполагаю, что когда вы прочитаете эту книгу, то, по крайней мере, будете иметь здравый смысл, чтобы не продолжать процесс "проектирование — оценка — повторение" до тех пор, пока вас не охватит "паралич анализа", но найдете золотую середину, когда проект готов для реализации. На протяжении всей части книги, связанной с логическим проектированием, мы старались так проектировать систему, чтобы избегать изменения основного направления проектирования всякий раз, когда приходилось "делать откат". Мы собрали данные, взяли интервью у клиентов, детально изучили документацию, построили модели наших данных и, наконец, нормализовали их, так что данные стали близки к реализации. В этой главе мы очень приблизились к физической реализации в наших попытках завершить эту часть процесса. Рассмотрено планирование наших потребностей в формировании данных от проблем безопасности до формирования отчетов. Также добавлен подход к плану проектирования и сделаны некоторые оценки относительно размера и роста данных, которые будут храниться в таблицах. Как только эта стадия завершена, мы заново рассматриваем проект — возвращаемся, чтобы удостовериться, что мы разработали то, что намереваемся создать, и что любые изменения по сравнению с первоначальными требованиями были согласованы. Если нам повезет, наша документация будет теперь в таком состоянии, что не нужно фактически возвращаться назад и начинать все заново.
229
Глава 8 Результатом стадии логического проектирования являются чертежи наподобие тех, которые разрабатывает архитектор здания совместно с клиентом, его надежды и мечты относительно системы, далее увязка их с действительностью и физикой, требуемой, чтобы фактически построить это. Как только клиент одобрит окончательные чертежи, вызываются субподрядчики, чтобы добавить стены, осветительную арматуру, водопровод, провода и всякие другие части, которые входят в состав физической структуры. Наши чертежи мало чем отличаются от таких чертежей зданий — с описаниями, моделями, диаграммами и т. д. для физической реализации. Следующая стадия проекта теперь уведет нас довольно далеко от этого очень абстрактного мира, в котором мы находились, в стадию, где мы начинаем ковать гвозди, устанавливать стены и двигаться к БД, где наши данные могут жить и быть безопасными.
230
Часть II — Физическое проектирование и реализация Физическое проектирование БД и реализация — совсем другая ситуация по сравнению с логическим проектированием БД. В то время как на логической стадии мы рассматривали наш проект исключительно с точки зрения получения "надлежащих" структур, в течение физической стадии проектирования мы становимся более прагматичными. Таким образом, мы преобразуем наш чисто логический проект в практический проект, который уже можем реализовать. В то время как логическое моделирование — явно тяжелая часть процесса проектирования БД, физическое моделирование также не должно рассматриваться как простая задача. Если вы ранее не разрабатывали БД, физическая реализация БД очень отличается от реализации программного обеспечения любого другого вида, с которым вы могли иметь дело, по одной основной причине:
Структуры данных в БД обычно являются центральной частью разрабатываемой системы, следовательно, большая часть кода в проекте будет связана с ними. Поэтому любые изменения, которые мы будем производить, должны быть тщательно рассмотрены, так как они будут связаны с использованием как самих структур данных, как и кода, который мы должны реализовать.
Часть И — Физическое проектирование и реализация
Помня это, мы рассмотрим следующие главы:
232
•
Глава 9 Планирование физической структуры — В этой главе мы рассмотрим принятие решений относительно того, как мы реализуем структуру. Здесь мы начнем учитывать число пользователей, размер системы, и то, как будут использоваться данные.
•
Глава 10 Планирование и реализация основной физической структуры — В этой главе мы рассмотрим механику выбора типов данных, построения таблиц и создания индексов.
•
Глава 11 Обеспечение целостности данных — Имеется много различных проблем, которые управляют целостностью данных нашей системы. Мы рассмотрим некоторые различные типы бизнес-правил, которые хотим реализовать, и то, как можно сделать это с учетом функций и ограничений, определенных пользователем.
•
Глава 12 Расширенный доступ к данным и методы корректировки — В этой главе мы рассмотрим некоторые методы доступа к данным в созданной нами БД, в особенности с помощью представлений и хранимых процедур.
•
Глава 13 Определение требований к аппаратным средствам — Одно из наиболее трудных действий для многих архитекторов данных — перевести требования системы в фактические требования к аппаратным средствам ЭВМ. В каком формате диска я нуждаюсь? Сколько мне нужно дискового пространства, и как эти требования изменятся в будущем? Каков должен быть размер ОЗУ? Сколько требуется процессоров? Слишком много вопросов. Это обычно вопросы для администратора БД, но для многих архитекторов может и не быть администратора, который поможет принять надлежащие решения относительно аппаратных средств ЭВМ.
•
Глава 14 Завершение проектирования — В этой главе будет рассмотрено завершение работы. Как только мы имеем наши данные, наши структуры и построенные запросы, проект закончен, не так ли? Чтобы завершить наш проект, мы разберем точную настройку нашей системы, включая рассмотрение проблем функционирования, связанных с формированием отчетов, особенно в интенсивно используемых системах, испытание системы и развертывание, которое мы должны предпринять. Мы также рассмотрим составление аварийных планов, чтобы защитить наши данные от всех тех неудач, которые предсказал Мерфи (в его известном законе).
Планирование физической структуры Введение В этой главе мы рассмотрим, как нужно реализовать нашу БД. Чтобы проиллюстрировать тип суждений, которые нам, вероятно, придется делать, рассмотрим следующие сценарии и попробуем оценить то, чего каждый требует для успешной реализации, типа отдельных структур только для чтения, используемых для формирования отчетов, отдельного оперативного хранилища данных или просто дополнительных аппаратных средств, чтобы улучшить характеристики запросов. Каждый из сценариев является реалистическим примером БД, которые являются действительно весьма банальными сегодня. В каждом случае предположим, что имеется соответствующий логический проект. Сценарий 1 БД содержит 100 таблиц. Одна из таблиц будет иметь миллион строк и будет расти со скоростью одной сотни тысяч строк в день. Имеется 2000 пользователей, вводящих данные в систему, и только три человека, формирующих отчеты в течение всего дня. Сценарий 2 БД содержит 20 таблиц. Все таблицы связаны по крайней мере с одной другой таблицей, а десять из таблиц растут со скоростью 100 строк в день. Трое людей вводят все строки двадцать четыре часа в день, в то время как отчеты формируются автоматически и посылаются через электронную почту старшим администраторам в организации. Сценарий 3 БД имеет два миллиона пользователей, пятнадцать тысяч из которых будут обращаться к БД одновременно. Каждую минуту каждый из пользователей создает две новых записи в БД.
Глава 9 Решение Так который из этих сценариев требует специальной обработки? К сожалению, ответ неясен, так как ни один из сценариев не содержит достаточную информацию, чтобы позволить нам принять квалифицированное решение. В сценарии 1, например, чтобы определить, нужно ли строить специальную БД, чтобы поддержать ежедневные потребности формирования отчетов, мы были бы должны знать, сколько потребуется времени для двух тысяч пользователей, чтобы ввести сотню тысяч строк. Это могло занять весь день, или это могли быть строки от пользователей, которые фактически являются машинами, которые автоматически вводят данные в течение часа каждый день. Кроме того, хотя мы и можем ожидать, что данные в этом случае должны быть преобразованы, просуммированы и запасены в хранилище данных для формирования отчетов, точный характер формирования отчетов в этом случае не определен. Сценарий 2 может показаться более простым, так как мы могли бы предположить, что добавление одной тысячи строк к БД в день — не так уж и много, особенно если их вводит один пользователь. Поскольку автоматический процесс управляет отчетами, мы знаем, что их формирование может быть намечено на подходящее время. В этом случае мы, вероятно, не будем нуждаться ни в какой специальной БД для формирования отчетов, так как система не должна быть занята, а запросы можно непосредственно подавать на OLTP-БД. Однако нужно учесть, что мы собираемся добавлять несколько сотен тысяч строк в год к БД, что, хотя конечно, и не очень большое количество строк, но может создать проблемы в зависимости от типа данных в строках и типа требуемых отчетов. Простые агрегатные операции, которые внутренне поддерживаются в SQL Server, не будут, вероятно, вызывать никаких проблем, в то время как более сложные статистические операции могут потребовать специальной обработки. Мы рассмотрим глубже тему комплексных требований к отчетам позже в этой главе. Сценарий 3 показателен для Web-сайта. Также сомнительно, что он будет самым простым из сценариев для решения. Основная причина этого — то, что, если вы имеете БД с двумя миллионами пользователей, с пятнадцатью тысячами активных соединений, вы, вероятнее всего, будете иметь деньги, чтобы использовать в этой ситуации столько аппаратных средств, сколько необходимо. Однако кроме различных проблем инвестиций в аппаратные средства, которые у вас могут возникнуть, нужно решить другие важные проблемы относительно характера формируемых записей и требований к формированию отчетов. Эту главу было труднее всего писать по сравнению со всеми другими главами, так как хороший логический проект диктуется проанализированной проблемой, без компромиссов, возникаемых во время реализации, при учете требований эксплуатационных характеристик и удобства. Хороший разработчик логической модели придет обычно к тем же заключениям для тех же самых проблем. Мы следуем одному и тому же типу методологии проектирования независимо от того, работаем ли мы с маленькой БД, чтобы хранить телевизионное расписание, или большой БД, чтобы хранить технические данные управления качеством. Выполнив такое проектирование хорошо однажды, вы должны быть способны делать это много раз, с постоянным ожидаемым результатом. Определение, как физическое решение БД должно в конце концов быть подготовлено и реализовано, — вообще-то другая история. Здесь мы должны взять набор параметров и построить решение, которое представляет логическую модель в виде, который позволит фирме оплатить все ее ожидаемые требования. Будьте уверены, что нелегко быть правым каждый раз. В процессе реализации вы не только рассматриваете вашу модель логического проекта, но также основываетесь на работе ваших коллег по поддержке (администратор БД и программисты БД) для лучшего выбора аппаратных средств ЭВМ, кодирования SQL-процедур и формирования преобразований из OLTP-БД в ODS. Не всегда возможно сделать оптимальный выбор, и так как сервер БД обычно является центральной частью любой системы, он может часто включать львиную долю ответственности за проблемы системы. 234
Планирование физической структуры Данная задача может показаться вам несколько упрощенной, но не волнуйтесь, поскольку мы теперь начнем рассматривать все проблемы предреализации, с которыми нам нужно разобраться перед созданием таблиц. В то время как мы не можем описать любую ситуацию, с которой вы можете столкнуться при физической реализации, мы можем по крайней мере рассмотреть два главных фактора, на которые в состоянии влиять, а именно: •
Формирование отчетов — Ключевой проблемой всей нашей работы по проектированию является баланс потребности в частом обновлении нашей БД с чтением данных, чтобы обеспечить точное формирование отчетов. Поддержание средств обслуживания бизнеса очень важно для процесса формирования аргументированных решений относительно точных настроек, которые требуются для оптимизации эффективности бизнеса.
Q
Эксплуатационные характеристики — Эксплуатационные характеристики — совсем другой вид проблемы. Имеется большое количество ситуаций, где хорошо нормализованная БД может обеспечить менее чем адекватные характеристики относительно того, что требуется. Мы посмотрим на некоторые из основных вещей, которые можем сделать, чтобы регулировать характеристики, все еще сохраняя наши нормализованные структуры БД (в большинстве случаев).
Это — наше утверждение, что первичное намерение создания систем клиент-сервер состоит в том, чтобы позволить изменениям быть сделанными с нашими текущими данными и иметь возможность это использовать немедленно, чтобы не отсылать запрос и ждать его завершения. Следовательно, мы должны делать то, что может сохранить OLTP-систему необремененной запросами только для чтения. Рассматривая эксплуатационные характеристики, мы не должны ставить под угрозу целостность данных, которые мы храним. Это — основная обязанность архитектора данных хранить необходимые данные без изъянов, защищая их от аномалий, которые происходят в плохо структурированных БД.
Проблемы формирования отчетов Формирование отчетов предназначено для преобразования сырого материала в значащий формат, который позволит пользователям принимать своевременные и адекватные решения. Как рассмотрено в предыдущей главе, отчеты — обычно наиболее важная часть системы для конечных пользователей, и в этом разделе мы обсудим некоторые из путей, которыми отчеты могут быть оптимизированы, чтобы вписаться в диапазон структур БД. Возникает довольно немного проблем, когда мы обсуждаем точное и своевременное формирование отчетов, не все из них столь же очевидны, как можно было бы подумать.
Размер данных Одна из наиболее частых проблем в формировании отчетов — то, что объем данных, необходимых, чтобы ответить на вопросы пользователя, является слишком большим, чтобы исполнить запросы в разумной манере. Характер проблемы зависит от аппаратных средств, на которых вы запускаете SQL Server — является ли это настольным компьютером с шестнадцатью мегабайтами ОЗУ или кластером (соединением) с четырьмя узлами по 4 гигабайта каждый. Обычно, однако, техника запросов должна быть подогнана под объемы используемых данных, хотя определение, когда "маленькие" объемы станут "большими", может оказаться несколько проблематичным, как иллюстрируют следующие примеры: Предположим, что Правительство Соединенных Штатов где-то имеет БД, которая содержит все номера социального страхования, которые оно присвоило, наряду с каждой транзакцией, 235
Глава 9 которую оно произвело, чтобы добавить деньги к счету или распределить деньги из счета социального страхования. Ясно, что за прошедшие 50 с лишним лет были бы миллионы и миллионы записей, добавляемые каждый месяц. В то время как это может выглядеть как большое количество данных, оно может оказаться небольшим по сравнению с БД, создаваемыми некоторыми провайдерами Интернета и сайтами Интернета при желании регистрировать каждый щелчок, каждую закупку и каждый поиск, который выполняют их пользователи. Таким образом, Web-сайты электронной торговли отслеживают столько информации о своих посетителях, сколько могут, чтобы персонифицировать показ товаров, которые, по их мнению, будут интересны для посетителя. Такие приложения, вероятно, будут регистрировать огромное количество информации относительно миллионов ежедневных посетителей таких сайтов, все из которых будут нужны для анализа владельцам сайтов. Другой пример регистрации операций, который становится банальным, — производственные БД обеспечения качества, где роботы фиксируют сотни и тысячи измерений в минуту, чтобы гарантировать, что произведенные изделия попадают в допустимые границы параметров. Этот вид приложения, очевидно, сформирует огромное число записей. В этих ситуациях имеется несколько основных подходов, которые мы можем использовать, чтобы попробовать решить проблему:
236
•
Оптимизация — Если мы сталкиваемся с ситуацией, где нам кажется, что существует верхний предел для количества данных, с которыми наша система может работать, во многих случаях простое изменение настройки нашей системы (типа разделения данных по различным группам файлов) исправит проблему. В то время как особенности типа большого количества индексов (или, конечно, недостатка их) или плохо рассмотренных возможностей оптимизатора (без учета, как оптимизатор обращается с запросами), могут загубить характеристики, во многих случаях при возгласах "слишком много" можно сделать больше, даже при неоптимальных настройках сервера или БД.
•
Доступ к данным — Рассмотрим случай, когда мы используем программное средство типа Access со связанными таблицами. Выполнение операции соединения в Access может успешно использовать добавление каждой из связанных таблиц клиенту перед созданием соединения, используя собственный механизм запросов, что не сложно, когда мы имеем дело с тысячами строк, и невыполнимо в случае миллионов строк. Однако запрос мог бы выполняться превосходно при использовании SQL Server. Во многих случаях проблемы с большими количествами данных связаны в большей мере с фактической передачей данных по проводу, нежели с непосредственной работой SQL. Больше о времени доступа к данным будет сказано позже в этой главе.
•
Мощные аппаратные средства — Возможно, мы могли бы просто собрать более эффективные аппаратные средства, которые работают с таким количеством данных, которое нам нужно. Использование более быстрого центрального процессора — ЦП (CPU-Central Processing Unit), объема ОЗУ, допустимого для данной машины, и полностью оптимизированных массивов диска не могут одни решить проблему, но устранят, конечно, большую часть ее. Мы обсудим аппаратные средства несколько глубже в главе 13.
•
Формирование отчетов в БД — Как мы обсудили в главе 1 и осуществим в главе 14, мы можем создать копию наших данных, которая содержит ответы на многие запросы, которые пользователь захочет сформировать, в повседневной работе. В нашем примере электронной торговли, приведенном выше, относительно быстрый процесс — создать кэшированную копию наших данных, чтобы отвечать на запросы пользователя. Альтернативой этому является просмотр всех записей, которые регистрировали бы все действия не только одного, но и всех пользователей сайта — действительно головная боль реализации!
Планирование физической структуры По всей вероятности, мы будем завершать работу, используя смесь этих идей, хотя первые две — хорошая отправная точка, так как они не требуют огромных расходов капитала и являются довольно сложной задачей (но не допускайте, чтобы ваш менеджер слышал, что Вы говорите это)!
Сложность Сложность требуемого формирования отчетов непосредственно зависит от того, как мы используем основные SQL-операции. Простые запросы, используя только встроенные SQL-операции, обычно не вызывают никаких проблем (хотя плохо разработанные запросы могут вызывать неприятные ситуации, подобно пропущенному оператору WHERE и соответствующим образом запирающему всю таблицу, запрещая другим изменять ее), но не всегда возможно ограничить вычисления только такими операциями. Например, я сформировал отчеты, которые используют единственный SQL-оператор, содержащий более чем 300 строк кода. Такая длина кода получилась, потому что требуется около трех строк кода, чтобы выполнить стандартную функцию вычисления дисперсии, и было необходимо 20 или около того раз ее использования внутри SQL-оператора. Хотя время, требуемое для выполнения оператора, не было чрезмерно большим (порядка секунд), операция оказалась дорогостоящей в терминах использования ресурсов (использование диска и ЦП), и таким образом ограничивала параллельные операции. Если пользователи хотят видеть последние 100 вычислений этого запроса на графике, то для выполнения вычислений каждых предыдущих 100 значений используется агрегатирование в таблице. Было бы неблагоразумным для пользователей выполнять 500 раз в день по 100 запросов в 300 строк. В этом и в других подобных случаях, которые включают чрезвычайно сложные условия формирования отчетов, может оказаться необходимым хранить некоторые предварительно агрегатированные данные, которые нарушают ЗНФ, особенно, когда дополнительные данные или бизнес-правила требуют ответов на одни и те же запросы. Это — иллюстрация процесса денормализации, которого мы кратко коснулись в главе 7. Обратите внимание, что, конечно, предварительно агрегатированные данные могли бы получаться и при вычислениях вне SQL Server. При использовании предварительно агрегатированных данных вы должны гарантировать, что если основные данные изменяются, должен быть снова выполнен код, формирующий агрегатированные данные, для изменения запасенных значений. В нашем примере выше изменение единственного значения могло привести к 100 выполнениям запроса, чтобы восстановить денормализованные данные. Не так уж и много можно сделать в этой ситуации, поэтому аномалии корректировки этого вида являются стержневым аргументом нормализации. Чтобы гарантировать целостность данных, все ссылки на те же самые данные должны быть изменены одновременно.
Требования поиска Если отчет требует большой свободы поиска (например, когда пользователи должны выполнить запросы на многих различных полях из многих различных таблиц), это может увеличить сложность до полной неуправляемости. Таблицы в нормализованной БД не оптимизированы для запросов, потому что процесс нормализации пытается создать таблицы по отдельным темам. Один из способов работать с этим типом проблем — использование одной из новых возможностей, имеющейся в SQL Server 2000 — индексированные представления (Indexed Views). Это обеспечивает средства предварительного вычисления представления, основанного на его собственном определении, и таким образом может использоваться, чтобы построить представление, 237
Глава 9
которое обеспечивает пользователям данные в понятном интерфейсе, которые они далее могут запрашивать обычным способом. Этот метод страдает теми же самыми проблемами характеристик, когда размещаются наши собственные денормализованные данные, так как SQL Server должен обслуживать индексированное представление одинаковым способом всякий раз, когда исходные данные изменяются. Дальнейшие детали индексированных представлений могут быть найдены в "Professional SQL Server 2000 Programming" (Wrox Press, ISBN 1861004486).
Конкуренция доступа пользователей Для любой конкретной системы, чем больше параллельных пользователей, тем больше случаев конкуренции, которые мы, вероятно, будем иметь между ними. В OLTP-операциях транзакции и запросы должны быть разработаны так, чтобы длиться как можно меньше времени. Отчеты — другой вопрос. Многие запросы, формирующие отчеты, могут иметь длительное время выполнения; совсем неудивительно иметь отчеты, которые требуют для выполнения пятнадцать минут, час, или даже несколько часов. Проблемы могут возникнуть, например, тогда, когда вы имеете очень терпеливого и относительно хорошо осведомленного пользователя вашей системы, кто пишет нерегламентированные запросы. Если пользователь не имеет никакого понимания, как точно работает SQL Server, то он может формировать и выполнять запросы, которые включают многочисленные соединения больших таблиц данных, таким образом затрачивая много часов, чтобы завершить действия этих запросов. Таким образом он может в конечном счете начать блокировать действия других пользователей, несмотря на блокирующие возможности на уровне строк, имеющихся в SQL Server 7.0, чтобы существенно уменьшить вероятность именно этого типа сценария блокировки. Однако путь, которым реализуется блокировка на уровне строк, позволяет оптимизатору выбирать между блокировками на уровне строк, уровне страниц или уровне таблицы. Когда пользователь пишет особенно плохо построенный запрос, который должен обращаться ко многим страницам в таблице, оптимизатор будет часто наращивать блокировки на страницы или даже на таблицы. Часто, когда пользователи исполняют такие запросы, возможно, блокировки будут включать страницы и таблицы, которые в это время изменяются активными пользователями, причиняя много сбоев!
Своевременность Своевременность означает, какой должен быть порядок потока данных, чтобы поддержать потребности пользователя в принятии квалифицированных решений. Например, предположим, что мы формируем оперативное хранилище данных из наших OLTP-данных, заполняем хранилище данных и витрины данных из оперативного хранилища данных и освежаем данные ежедневно. Вся эта система будет бесполезна пользователю, если все решения должны быть сделаны, используя только самые последние данные. Как быстро мы можем обеспечить доступ к данным, зависит не только от количества времени, которое требуется для передачи данных с одного места в другое (так называемое время ожидания), но также и от нашего проекта. Наши различные подходы могут быть подытожены следующим образом: •
238
Немедленный — Немедленный доступ наиболее проблематичен, так как мы можем сделать очень мало для его обеспечения. Имеется много способов обеспечить эти потребности, и все они вызывают проблемы функционирования. Мы должны будем часто строить лишь хранимые процедуры, которые читают данные непосредственно от наших таблиц, обращаясь к данным таким образом, чтобы избежать их
Планирование физической структуры
блокировку. Если это невозможно сделать без организации "попадания", то мы можем включать денормализованные структуры или, возможно, даже индексированные представления в наших хранимых процедурах. Иногда организация попадания неизбежна и фактически представляет допустимый недостаток в наших потребностях представления данных. В равной степени мы можем придумать компромисс, типа вероятностной выборки данных вместо включения каждого значения, что улучшит нашу скорость поиска данных. •
Некоторое допустимое время ожидания — Если вам не нужно мгновенно читать данные, мы можем использовать любую из других методологий. Можно создать структуры в оперативном хранилище данных, которые обновляются настолько часто, насколько позволяет размер данных (и бизнес-правила). Нужно позаботиться о том, чтобы гарантировать, что частота обновления была больше частоты обращения пользователей.
•
Большое время ожидания — Когда время — не основная проблема, и особенно когда вовлечено чрезвычайно большое количество исторических данных (типа анализа тенденций через какое-то время), хранилище данных или сопутствующие витрины являются лучшим средством, потому что мы можем иметь больше возможностей (и все они часто используются) агрегатирования и настройки запросов.
Частота Частота в этом контексте означает число раз получения отчета. Одна из систем, над которой я раньше работал, содержала отчет, который считался самым важным в ее функционировании. Он запускался каждые 24 часа в полночь и занимал для своего формирования три часа. Так как он запускался в наименее важные часы суток, на работу пользователей это мало влияло. С другой стороны, у многих других систем пользователям нужно иметь актуальные таблицы в течение дня, например, менеджер банка, который должен быть уверен, что банк имеет достаточно денег на руках, или брокер, кто должен иметь самые последние отчеты с тенденциями по каждому из клиентов или фондов. Формирование отчетов этого типа будет, вероятно, требовать супермощных аппаратных средств и чрезвычайно оптимизированных запросов, а также некоторые денормализованные структуры.
Влияние характеристик Если мы возьмем пример БД, созданной для контроля и хранения результатов деятельности потребителей на Web-сайте, которую мы обсуждали ранее, мало того, что эта БД будет логически спроектирована, чтобы регистрировать каждую операцию, мы также могли бы размещать каждый элемент и каждую страницу, которые отображены на экране. Это очень большая БД (думаю размером в терабайты), и с ее данными, размещенными нормализованным образом, единственная БД, вероятно, не была бы способна выдержать обе группы пользователей — пользователей, изменяющих данные, и пользователей Web-приложения, запрашивающих его страницы. В следующем разделе мы рассмотрим некоторые возможные компьютеры и конфигурации сетей (обычно называемые топологией), которые будем использовать, чтобы бороться с множеством общих проблем функционирования.
9 1868
.
239
Глава 9
Скорость соединения Первостепенный интерес здесь имеют глобальные сети (Wide Area Networks — WAN) наряду с Web-приложениями (которые можно фактически расценивать как глобальные сети, но в другом облачении). Возьмем очень простой пример — классическую двухъярусную систему клиент-сервер:
Рабочая станция
Сервер БД
Изменение скорости соединения или расстояния между клиентом и сервером БД существенно трансформирует окончательное решение по организации нашей СУБД. В следующем перечне мы рассмотрим некоторые общие скорости в сети, и как они повлияют на наше решение изменить эту простую топологию:
240
•
Чрезвычайно быстрое соединение типа 10 Мбит, 100 Мбит или больше — эти типы соединений наиболее характерны в пределах физической сети компании. Если мы не имеем проблем с размером БД, то нетрудно использовать простую двухъярусную систему клиент-сервер, как показано на рисунке выше. Пользователь может присоединяться непосредственно к SQL Server, и требуется очень небольшая специальная оптимизация.
•
Умеренное соединение типа ISDN (Integrated Services Digital Network — цифровая сеть связи с комплексными услугами), DSL (Digital Subscriber Line — цифровая абонентская линия), кабельного (от 2 Мбит до 128 Кбит) соединения — обычно основные приложения клиент-сервер реализуются с такими соединениями, но нам нужно быть уверенными, что мы оптимизировали все наши запросы к БД, чтобы уменьшить число байтов, которые передаются туда и обратно по сети, и число различных используемых коммуникаций. Возможное улучшение могло бы быть получено, оптимизируя наше программное обеспечение клиента, кэшируя частые запросы к БД, которые могут повторяться, и группируя многократные действия вместе.
•
Медленное соединение типа коммутируемого соединения (Dual-up) (в принципе 56 Кбит, но более часто примерно 28.8 Кбит) — приложения с коммутируемым соединением более распространены, чем мы могли бы себе представить. Многие служащие, связанные с продажами или обеспечением, используют коммутируемые соединения для управления приложениями БД по ночам из своих гостиниц. Хотя пользователи, осуществляющие доступ к своим приложениям через коммутируемую связь, вообще-то чаще имеют в виду скорость доступа, при которой приложения должны работать соответственно. В этих случаях мы, конечно, должны будем оптимизировать приложения или, что даже более возможно, применить схему тиражирования части БД клиенту, используя некоторую форму сжатия. Мы рассмотрим тиражирование позже в этой главе.
Планирование физической структуры Независимо от быстродействия рабочей станции или сервера БД скорость, с которой вы соединяетесь с сервером, является критической с точки зрения производительности. Если вы пытаетесь передать 1000 строк назад клиенту, где строка содержит 3000 байт, и вы осуществляете это по 128-килобайтовой ISDN-линии, вашему клиенту будет, конечно, плохо с вами, так как будет требоваться приблизительно 3 минуты ((3000 * 1000 * 8 (бит на байт)) / (128 * 1000) = 187.5 секунд), чтобы закончить передачу. Это не учитывает издержки ни в самой сети, ни за счет любых других пользователей. Если бы десять лет назад пользователи попытались использовать ту же самую полосу пропускания, то потребовалась бы дополнительно 31 минута для получения своих данных! Следует отметить, что вы могли бы написать вашу программу так, чтобы восстанавливать записи и отображать их по мере необходимости вместо восстановления всех 1000 записей в одно и то же время. Например, вы могли бы показывать по 20 записей на одной странице, и поскольку пользователи просматривают результаты, остающиеся записи были бы тогда разделены на части. Однако для всех из нас, кто использовал анализатор запроса и возвращал дополнительно 1000 строк для тестирования запроса в полсекунды (включая и запрос, и выборку данных), это все еще было бы неприятным. Действительность такова, что очень немногие большие системы сегодня строятся с простой архитектурой клиент-сервер. Большинство из них имеет распределенные части по всей сети, и даже по всему миру. Следующий пример — проект, который выглядит довольно дико, и который включает Web-серверы, хранилища данных и даже, как наследство, систему универсальных ЭВМ. Лондон
Рабочая станция
Даллас
Рабочая станция
Наследство приложений на универсальных ЭВМ
Web-сервер
[о] бооооо] |ocj Концентратор
I . Рабочая станция
Сервер БД
Хранилище данных
Рабочая станция
Рабочая станция
Оперативное хранилище данных
2 4 1
Глава 9 Из этого рисунка вы можете видеть, что сложность нашего решения ограничена только фантазией архитектора. Некоторые организации имеют десятки или даже сотни серверов БД, многие из которых связаны вместе с помощью различных видов соединений, обладающих различными показателями надежности. В этом случае каждая линия между элементами представляет связь сети. В любом из этих мест связи мы имеем скорости, которые могут изменяться от чрезвычайно большой до чрезвычайно медленной, и каждое место связи должно рассматриваться как ответственное (с точки зрения проекта), так как сети имеют свойство быть меньше чем 100-процентной надежности. В то время как для проектировщика БД важно, чтобы, по крайней мере, знать, как скорость соединения влияет на работу приложений, более широкие аспекты проблем организации сети попадают в сферу деятельности системного администратора.
Количество данных SQL Server 2000 — широко масштабируемый сервер БД. Он масштабируется от карманного компьютера с ОС Windows через отдельный ноутбук Pentium-200 МГц с тридцатью двумя мегабайтами ОЗУ и Windows 95 или двухпроцессорный Pentium III с половиной гигабайт ОЗУ до кластера из шестнадцати серверов, каждый из которых содержит по восемь процессоров с четырьмя гигабайтами ОЗУ каждый, и которые связаны друг с другом. Количество данных, которые вы можете поддерживать, изменяется от мегабайта или двух у вашего карманного компьютера до многих терабайт у группы серверов. Размер данных будет существенно влиять на изменение наших стандартов кодирования. Мы можем легко отойти от некоторых удобств программирования (игнорирование оптимизации и подобные), если наша основная таблица в БД будет иметь одну тысячу записей в противоположность одному миллиарду. В качестве иллюстрации возьмем следующий простой пример: child childld child attribute 1 child attribute 2 child attribute N parentld (FK)
parent parentld parent attribute 1 parent attribute 2 parent attribute N
Здесь child — таблица "потомок", childld — идентификатор потомка, child attribute — атрибут потомка, parentld — идентификатор предка, parent — таблица "предок", parent attribute — атрибут предка. Мы заявим как определение, что, если вы имеете запись в таблице-потомке, должен существовать мигрирующий ключ из таблицы-предка. Теперь рассмотрим ситуацию, когда мы хотим удалить запись из таблицы-предка. Тогда становится очевидно, что размер таблицы-потомка будет чрезвычайно важен. Если в таблице-потомке имеется десять строк, то чтобы удалить одну строку в таблице-предке, вы должны просмотреть десять записей — не так уж много. Однако если имеется миллион строк, это — не столь уж простая задача для SQL Server (даже с индексами это могло потребовать непомерное количество времени). Из этого следует, что, если вы имеете миллиард строк в таблице, строку не удастся удалить даже в две недели. В этом случае мы, вероятно, поместили бы в таблицу-предок 242
Планирование физической структуры дополнительный столбец, чтобы указать, что запись больше не может использоваться, но не фактически удаляли запись. Однако задание этого флага в таблице-предке оставит записи и в таблице-потомке, связанные с помеченной флагом записью, и эти записи в таблице-потомке требуют осторожной обработки. Следовательно, в любом случае мы не можем управлять ситуацией оптимально.
Бюджет Любые решения, которые мы изобретаем, должны быть основаны на том, что подходит для организации, так как увеличение размера бюджета, чтобы оплатить дорогие аппаратные средства, не всегда может быть приемлемым. Имеются три главных области, связанные с этим: •
Аппаратные средства — Недостаточно сказать, что нехватка аппаратных средств вызовет у вас проблемы. В главе 13 мы рассмотрим определение количества аппаратных средств при увеличении БД, в зависимости от числа пользователей и т. д. Простой принцип — никогда не урезайте производительность аппаратных средств. В главе 14 мы обсудим довольно глубоко концепцию наличия по крайней мере трех различных видов оборудования — для разработки, испытания и производства. Чтобы создавать, проверять и реализовывать надлежащую СУБД, вам будет нужно, по крайней мере, два отдельных сервера.
•
Программное обеспечение — Бюджет должен быть достаточен для ОС, лицензионного программного обеспечения БД и любых программных средств, которые вам потребуются для создания программного обеспечения. Внимательно читайте лицензионные соглашения по программному обеспечению, чтобы избежать переплаты за него.
•
Укомплектование персоналом — В зависимости от размера проекта, вы должны быть готовы иметь штат, способный работать с каждой частью СУБД, которую вы разрабатываете. Очень немного архитекторов БД являются также и большими специалистами по организации сети, большими администраторами БД, большими документаторами, большими испытателями, большими программистами и большими ... — поставьте точку сами.
Число пользователей Одной из наиболее трудных характеристик, с которой приходится иметь дело, является число пользователей, которых вы должны будете поддерживать. Поскольку каждое соединение требует приблизительно 12 Кб + (3 * (размер сетевого пакета, который изменяется, но по умолчанию равен 4 Кб)), или 24 Кб, большинство системных архитекторов не очень обеспокоено числом пользователей, которых система будет иметь. Однако нынче, когда БД поддерживает Web-сайты, общее количество пользователей, обращающихся к нашей БД, может быть где-нибудь от 10 до 10 миллионов или даже больше! В попытке иметь дело с таким большим числом пользователей используется ряд методов, включая: •
Организация пула (накопителя) соединений — Если пользователь в своем приложении соединяется и разъединяется с данными много раз, может быть выгодно использовать возможности организации пула в ODBC 2.5 (и последующих версий) или OLE DB. Они позволяют организовать драйверы так, что пользователь "думает", что он отключился, но фактически сохраняет связь открытой, ожидающей дальнейших команд. 243
Глава 9 Как краткое предостережение, организация пула соединений НЕ производит собственную очистку, когда вы сообщаете, что соединение завершено. Наиболее важно не закрывать любые открытые транзакции, которые могут затрагивать более поздние соединения, даже в случае многих пользователей, использующих соединение, поэтому должно быть выдано соответствующее предостережение.
244
•
СОМ+ (или любая разновидность n-ярусного приложения) — СОМ+ позволяет вам перенести часть обработки, которая могла бы быть выполнена на стороне клиента, и организовать пул соединений среди многих различных пользователей, так что вы устраните добрую часть непроизводительных издержек, связанных с созданием и разрывом соединений. При организации пула соединений десять тысяч параллельных связей может напоминать 1000 или даже 100.
•
Увеличение производительности, увеличение памяти — Только добавляя большее количество аппаратных средств, черт побери! К сожалению, ограничения бюджета могут означать, что это решение невозможно. Как ранее упомянуто, каждое соединение требует - 24 Кб ОЗУ. Это может казаться не слишком большой проблемой, но если вы имеете 10000 соединений с сервером, величина могла бы равняться 240000 Кб, что является существенной частью от 256 Мб, которые может использовать стандартный SQL Server. Если мы снова и снова добавляем к накладным расходам процессора управление всем этим ОЗУ, мы быстро увидим, как возникнут проблемы.
•
Кластеризация серверов — Microsoft SQL Server и Windows 2000 поддерживают кластеризацию нескольких серверов БД и работу с ними как с единственным сервером. Использование кластеризации позволяет нам создавать очень большие, очень мощные серверные системы БД. Идея таких систем состоит в том, чтобы распределить груз приложения на ряд серверов, что приводит к увеличению выполняемых запросов, так как могут быть добавлены дополнительные серверы для обслуживания дополнительной нагрузки. Этот процесс известен как балансирование нагрузки. Так как каждый сервер (в идеале) может обрабатывать требования клиента индивидуально, то если один сервер сломается или потребуются дополнительные серверы, добавленные к кластеру, в работе не будет никакой неприятности. Дальнейшие детали относительно кластеризации могут быть найдены в "Professional Windows DNA: Building Distributed Web Applications with VB, COM+, MSMQ, SOAP, and ASP" (Wrox Press, ISBN 1861004451).
•
Приложение со специальными копиями данных только для чтения — Это — технология, которая хорошо работает при создании Web-сайтов. Большинство данных, которые нам нужны для формирования Web-страниц, — только для чтения. Например, рассмотрим гипотетическую БД описания изделий, в которой мы имеем все наши изделия, размещенные в хорошо нормализованном наборе структур, избегая все ловушки избыточности. Далее рассмотрим Web-страницу, которую пользователь будет видеть, и где на одной странице перечислены текущая цена, цвет, размеры, формы, спецификации и т. д. В этом случае мы могли бы построить единственную таблицу, которая имеет все значения, необходимые для Web-страницы, вместо пятидесяти таблиц и трех сотен строк. Мы, вероятно, не хотели бы предварительно формировать всю таблицу пользователя, так как, конечно, хотели бы иметь возможность персонифицировать ее (добавить скидки и т. д.) и, конечно, изменить его взгляд и намерения относительно предмета. Потребуется дополнительное хранилище, но требуемая обработка существенно уменьшится, благодаря сокращению числа выполняемых соединений.
Планирование физической структуры
Возможности SQL Server Рассмотрев многие из проблем, касающихся функционирования наших СУБД, мы должны также рассмотреть средства, которые SQL Server предлагает проектировщику. В этом разделе мы кратко рассмотрим, что они собой представляют и как могут использоваться прежде всего теми, кто не знаком близко с SQL Server. Для тех, кто является постоянным пользователем SQL Server, — пожалуйста, пропустите этот раздел вплоть до заголовка "Примеры основных топологий" в следующем разделе.
Тиражирование Тиражирование — замечательное средство, которое помогает в создании копий БД и поддержании их для нас в некотором интервале. Этот интервал может изменяться от "немедленно" до дней или недель. Тиражирование может быть выполнено между различными SQL Server или даже из SQL Server в Microsoft Access, Oracle, или почти любую БД, которая поддерживает требования к подписчикам для SQL Server (для дополнительной информации см. SQL Server Books Online). Пара терминов, которые нам нужно здесь ввести: •
Публикация — источник копируемых данных, хотя это может или не может быть первоначальным источником. Подобно физически напечатанной публикации (следовательно, имеющей название), публикация — это БД, которая была отмечена для других БД, чтобы они могли подписаться на нее. БД разбита на таблицы, помеченные для тиражирования ответа, на которые ссылаются как на статьи.
•
Подписка — когда БД использует публикацию, чтобы получить точную копию одной или нескольких статей этой публикации, то ее называют подписчиком. Подписка может быть как на одну статью, так и на все статьи.
SQL Server обеспечивает три различных модели для реализации тиражирования: •
Тиражирование копии — периодически делает копию данных публикации и заменяет полный набор данных у подписчика.
•
Тиражирование с помощью транзакций — первоначально делает копию публикации подписчику (точно так же, как тиражирование копии), а затем использует любую транзакцию асинхронно к подписчику, как только копия возникнет у публикации.
•
Тиражирование слиянием — позволяет вам иметь две БД (одна из которых выбрана как издатель, формирующий публикацию, а другая — как подписчик), где вы берете все изменения от издателя и добавляете их к подписчику и наоборот. Это позволяет вам редактировать данные в любом месте и использовать изменения в другой БД автоматически. Тиражирование слиянием очень полезно для передвижных клиентов, когда поддержание связи с основным сервером невыполнимо. Оно также имеет варианты по умолчанию и специальные, чтобы разрешать конфликты в случае, когда несколько пользователей редактируют одни и те же части данных.
245
Глава 9
Связанные серверы Связанные серверы позволяют нам организовать доступ к неоднородному SQL Server или источникам данных OLE DB непосредственно изнутри SQL Server. Мы не только можем обеспечить доступ, но и можем выполнять для этих данных изменения, команды и транзакции. Например, для доступа к SQL Server по имени LOUSERVER, мы выполнили бы следующее: sp_addlinkedserver @server • N'LinkServer', @provider - N'SQLOLEDB', @datasrc = N'LOUSERVER', @catalog = N'Pubs'
I
Затем мы можем выполнить запрос, чтобы получить строки из этого источника данных: 1
SELECT * FROM LinkServer.pubs.dbo.authors
Это — простой пример, но он кратко иллюстрирует наше утверждение. Со связанными серверами вы можете иметь доступ к дополнительным SQL Server, серверам Oracle, БД Access, электронным таблицам Excel лишь по их именам. И прелесть этого состоит в том, что вы не должны изучать никакой дополнительный синтаксис доступа к несметному числу различных типов данных. Уродливая сторона этого заключается в том, что для присоединения одного набора данных к другому набору, возможно, придется большое количество данных пропустить из одного SQL Server во временное хранилище на другом прежде, чем будет выполнено соединение, что может иметь серьезные побочные эффекты для работы.
Сервисы преобразования данных Для случая, когда тиражирование не будет работать из-за изменений (а, следовательно, и изменения названия), сделанных в данных, фирма Microsoft обеспечила сервисы преобразования данных (DTS — Data Transformation Services). Используя их, вы можете преобразовывать и передавать данные между источниками OLE DB. DTS также позволяет вам передавать индексы и хранимые процедуры между SQL Server. Используя DTS, вы можете создавать пакеты, которые группируют вместе многократные операции преобразования/объекты, которые могут быть запущены или синхронно (одна операция ожидает завершение другой) или асинхронно, используя все возможности SQL Server. Вы можете, например, выполнять поочередно очистку данных (удаление/чистка данных) в таблицах, при этом имеется возможность вычислить новые значения, используя простую программу на Visual Basic. DTS — полностью специализированный инструмент очистки данных и имеет множество настроек и возможностей. Дальнейшие детали могут быть найдены в "Professional SQL Server 2000 DTS", (Wrox Press, ISBN 1861004419).
Контроллер распределенных транзакций Контроллер распределенных транзакций (DTC — Distributed Transaction Controller) — средство, которое осуществляет двухфазную фиксацию транзакций, что позволяет вам создать транзакцию, которая использует больше чем один сервер. Причина, по которой 246
Планирование физической структуры процесс называется двухфазной фиксацией, заключается в том, что на каждый из серверов, который используется транзакцией, посылаются две специальные команды, чтобы начать и закончить процесс. •
Подготовка — менеджер транзакций посылает команду каждому серверу БД для его менеджера ресурсов. При этом сервер готовится принимать команды, которые будут посланы.
•
Фиксация или откат — как только "пользователь(и)" сформировал любую задачу и выдает команду, что он хочет ее выполнить, начинается фаза фиксации или отката, работая наподобие того, как вы выполняли бы фиксацию транзакции на каждом сервере.
Строго в манере кодирования для SQL Server, если DTC был включен и вы создали связанные серверы, то можно использовать следующий код, чтобы модернизировать таблицу авторов как на сервере, где этот код выполняется, так и на связанном сервере. USE pubs GO BEGIN DISTRIBUTED TRANSACTION UPDATE authors SET au_iname = 'Davidson' WHERE au id - '555-55-5555' IF t e r r o r 0 BEGIN ROLLBACK TRANSACTION GOTO e x i t END UPDATE linkserver.pubs.dbo.authors SET au_lname = 'Davidson' WHERE au_id - '555-55-5555' IF @@error 0 BEGIN ROLLBACK TRANSACTION GOTO exit END COMMIT
TRANSACTIONT
exit: GO
Обратите внимание, что контроллер DTC работает с данными любого типа, для которых существует драйвер OLE DB, который поддерживает интерфейсы распределенных транзакций. Следует также знать, что если DTC не установлен, оператор BEGIN DISTRIBUTED TRANSACTION не будет выполнен.
Объекты управления данными SQL Объекты управления данными SQL (SQL-DMO — SQL Data Management Objects) — набор СОМ-объектов, которые включают почти каждую особенность SQL Server. Из DMO вы можете выполнять задачи типа создания таблиц и индексов, создания объектов 247
Глава 9 автоматизации и задач администрирования. Объекты могут использоваться из любого программного средства, которое допускает реализацию СОМ-объектов, типа Visual Basic, VB Script, VB for Applications, C++ и, как вы увидите в следующем разделе, самого SQL Server. Есть много хороших примеров в SQL Server Books Online, которые иллюстрируют мощь SQL-DMO; дальнейшие детали можно также найти в "Professional SQL Server 7.0 Development Using SQL DMO, SQL-NS & DTS", (Wrox Press, ISBN 1861002807).
Реализация СОМ-объектов Другая изящная особенность, которая существует с версии 6.Х SQL Server — реализация СОМ-объектов. Используя симпатичный мощный интерфейс T-SQL, вы можете вызвать большинство СОМ-объектов и делать с ними почти все, что вам потребуется. В следующем примере мы реализуем DMO-объект SQLServer и затем соединимся с ним: DECLARE SobjectHandle int @recVal int, —реализация объекта sqlserver EXECUTE @retVal = sp_OACreate 'SQLDMO.SQLServer', @objectHandle OUT IF @retVal 0 BEGIN EXECUTE sp_displayoaerrorinfo @objectHandle, @retVal RETURN END --связь с сервером EXECUTE @retVal = sp_OAMethod @objectHandle, 'Connect', NULL, 'LOUSERVER', 'louis', '' IF @retVal 0 BEGIN EXECUTE sp_displayoaerrorinfo @objectHandle, SretVal RETURN END
Хотя реализация СОМ-объекта и очень полезна, она может при этом быть медленным процессом. Объект должен быть создан (и память для него выделена), исполнен и, наконец, удален — все не в стиле SQL. Используйте эти функциональные возможности, только когда основанное на SQL решение невозможно, или в процедурах, которые вы не рассчитываете использовать часто.
Почта SQL Почта SQL (SQL Mail) позволяет системе SQL Server посылать и получать почту, используя простой интерфейс Outlook или Exchange либо серверы Exchange или РОРЗ (почтовый протокол Интернета). Сервер может быть сделан так, чтобы отвечать на сообщения электронной почты, которые содержат запросы, возвращая результаты ответа так же, как сообщения электронной почты. Он может быть также добавлен к хранимым процедурам и триггерам, если это потребуется. Примерами того, насколько это полезно, могут быть задачи типа посылки напоминаний в конкретные пункты по календарю или даже посылка предупреждений, чтобы сообщить пользователю, что произошло некоторое событие типа отрицательного баланса на его счете в банке. В следующем фрагменте кода мы используем почту SQL, чтобы послать очень приятное сообщение по электронной почте: 248
Планирование физической структуры EXEC xp_sendmail @recipients = '
[email protected]', @query = 'select ''hi'' ', @subject = 'SQL Server Report', ^message = 'Hello!', @attach_results = 'TRUE'
Полнотекстовый поиск Полнотекстовый поиск с помощью SQL Server позволяет нам строить очень мощные средства, которые работают подобно любому поиску сайта в Интернете. Использование полнотекстового поиска позволяет нам искать в столбцах текста так же как во внешних системных файлах. Синтаксис запроса для полнотекстового поиска — просто расширение обычных команд SQL Server. Например, рассмотрим следующий запрос:
1
SELECT title_id, title, price FROM pubs..titles WHERE CONTAINS (title, '"Database" near "Design"')
Используя ключевое слово CONTAINS, мы ищем не только названия, которые содержат Database и Design, но и названия, где они встречаются в сочетании близко друг к другу. Обратите внимание, что хотя полнотекстовые способности добавлены в SQL Server 2000, они не устанавливаются по умолчанию. Если вы попробуете выполнить этот оператор в pubs без конфигурации БД публикаций, а также конфигурации столбца названий в таблице t i t l e s (названия) для полнотекстовой поддержки, пример, приведенный выше, не будет выполнен с сообщением об ошибке. Полнотекстовые индексы размещаются отдельно от SQL Server и поэтому могут быть медленнее, чем собственные операции SQL Server. Дополнительные детали относительно установки и осуществления полнотекстового поиска могут быть найдены в "Professional SQL Server 2000 Programming" (Wrox Press, ISBN 1861004486).
Агент SQL Server Последним в нашем разделе, посвященном возможностям SQL Server, и далеко не самым последним, является возможность планирования работы с помощью агента SQL Server, который является связующим элементом, обеспечивающим работу вашей системы. Тиражирование использует агента, чтобы обрабатывать транзакции, пакеты DTS могут быть запланированы на выполнение в определенное время, и вы можете также запланировать ваши собственные хранимые процедуры, чтобы выполнять и запускать программы ОС. Установка агента SQL Server — вне возможностей этой книги, и читатель должен проконсультироваться относительно деталей в SQL Server Books Online. Важно отметить, что SQL Server имеет встроенные возможности, которые запускают свой собственный контекст безопасности, который будет выполняться почти каждый раз, когда будет требоваться ваша БД.
249
Глава 9
Примеры основных топологий В этом разделе мы рассмотрим некоторые наиболее важные топологии, которые могут быть выбраны при разработке наших приложений.
Тонкий" клиент в сравнении с "толстым" клиентом "Толщина" клиента непосредственно связана с тем, за какую часть работы отвечает программа клиента. Его не следует путать с тем же самым термином, используемым в описании концепции программы, написанной на классическом языке программирования вроде Visual Basic или C++, где все представление логики закодировано в программе клиента в противоположность выделенному клиенту.
'Толстый" клиент Лучшим примером "толстого" клиента было бы приложение, написанное на Visual Basic, которое организует доступ к БД текстовых файлов, используя ODBC (Open DataBase Connectivity — открытый интерфейс доступа к базам данных). Так как система для текстовых файлов, конечно, не СУРБД и поэтому не может защищать себя, вы должны включить в клиент код проверки достоверности каждого бита данных. Использование очень "толстого" клиента может уменьшить влияние помех приложения, потому что, если каждая возможная проблема с данными обрабатывается клиентом, и нет никакой возможности серверу изменить какие-либо значения, мы можем полагать, что наши данные будут сохранены точно так, как мы ввели их в БД. Если происходит ошибка, то это будет из-за проблемы в SQL Server (типа отказа диска или некоторых видов порчи данных). Однако "толстый" клиент не без своих недостатков. Так как вы уже предварительно проверили данные, кажется чрезмерным иметь SQL Server для их проверки. Но с другой стороны, если только клиент — не единственный метод получения данных из сервера, вы оставляете ваши данные открытыми для ошибок всякий раз, когда с ними осуществляется работа не из приложения. Это может казаться маленькой ценой, которую мы платим, чтобы иметь дружественное приложение, но редко все редактирование данных можно выполнить строго с помощью единственной части программного обеспечения.
"Тонкий" клиент На другом конце спектра — тонкий клиент. В этом сценарии код клиента совсем не проверяет вводимые данные, и считается, что механизмы SQL Server будут заботиться о любых ошибках и сообщать о них. Хороший пример очень "тонкого" клиента — окно запроса SQL Server Enterprise Manager:
250
Планирование физической структуры
[2:Data tn Table 'authors']
}п SQL Seivei Enterprise Manager V'n • Iff?
;
> 1 aujrame Johnson Marjorie Cheryl Mc i hael I Dean | Meander Abraham j Ann JBurt i Chare l ne Mornn i gstar : Regn i ad l Akk io Innes Mc i hel Dirk Stearns Livia
°°-o" [13 Щ1
au id ; _•_ 172-32-1176 213-46-8915 238-95-7766 267-41-2394 274-80-9391 341-22-1782 409-56-7008 427-17-2319 472-27-2349 486-29-1786 527-72-3246 648-92-1872 672-71-3249 712-45-1867 722-51-5454 724-08-9931 724-80-9391 756-30-7391 iLJ
|au Iname : White Green Carson O'Leary [Straight ; Smith Bennet Dull j Gringlesby Locksley Greene ; Blotchet-Halls Yokomoto ; del Castilo DeFrance Stringer MacFeather Karsen W
*
1
г
•
1
p
4
h
0
o
n
1 address
e
8
4
9
6
-
7
2
2
3
:
4
1
5
9
8
6
-
7
0
2
0
j
4
1
5
5
4
8
-
7
7
2
3
4
2
8
1
9
;
4
0
8
4
1
5
9
1
3
4
1
2
8
3
6
5
8
8
5
6
4
-
-
4
8
2
2
3
-
9
-
0
9
9
4
6
3
2
2
:
4
1
5
8
3
6
-
7
1
2
:
7
0
7
9
3
8
-
6
4
4
5
5
8
5
-
4
6
2
0
3
1
4
;
1
5
8
6
1
5
2
9
7
-
2
7
2
5
0
3
7
4
5
-
6
4
0
2
4
1
5
9
3
5
-
4
2
2
8
5
6
1
5
9
9
6
-
8
2
7
2
1
9
5
4
7
-
9
9
8
4
1
5
8
4
3
-
2
4
4
1
1
5
5
9
9
2
1
3
5
4
-
7
1
2
8
5
3
4
-
9
2
1
9
ЩИ
1сУ
10932 BiggeRd. Г-~3 309 63rd St. #411 ( :] j589 Darwin Ln. ; E;: || 22 Cleveland Av. # « 5420 College Av. ( : 10 Mississippi Dr. L :6223BatemanSt. E • ;3410 Blonde St. F JPOBox792 С ;; 18 Broadway Av. S " 22 Graybar House 1Г :-я 55 Hillsdale Bl. ( Ц 3 Silver Ct. \ : 2286 Cram PI. #86 t ': 3 Balding Pi. X | j 5420 Telegraph Av ( 1 ! 44 Upland Hts. С 5720McAuleySt. c d > j :. • •
Здесь au_id — идентификатор автора, au_lname — фамилия автора, au_fname — имя автора, phone — номер телефона, address — адрес. Независимо от того, что вы будете вводить в пустые места, входная форма будет брать это и передавать к SQL Server. Он, в свою очередь, будет брать данные и размещать их, пока не будет проинструктирован, чтобы не делать это. Один из важных моментов использования "тонких" клиентов SQL Server для редактирования всех данных — то, что вы никогда не должны волноваться относительно того, откуда поступают данные, так как, если вы закодировали ваш сервер должным образом, все данные в БД будут чисты. Данные к тому же проверяются однажды и только однажды. Это улучшит работу ваших приложений, особенно, когда случаи ошибок очень редки. Обратная сторона — уменьшение используемости приложения. Так как приложение понятия не имеет о том, как должны выглядеть данные, оно не может уберечь пользователя от ошибок. Это особенно видно в приложениях, которые имеют правила, затрагивающие более одного столбца. Возьмем следующий пример: i i i i l i i i l E
m
a
i
l
A
d
d
r
e
s
'.jqtxl OK
s
Cancel f
/
Г
S
e
n
d
e
m
a
i
l
r
o
t
i
Email me offers
f
c
a
t
i
o
n
s
;
Здесь Email Address — адрес электронной почты, Send email notifications — послать уведомления по электронной почте, Email me offers — электронная почта мне предлагает. 251
Глава 9 Легко видеть, что мы должны иметь правило предотвращения проверки любого из этих выключателей, пока пользователь не имеет адреса электронной почты. В БД это просто осуществить, но если мы не запрограммируем какую-либо из подобных логик у клиента, нет никакой возможности избежать следующей ситуации:
Email Adc ^
i
lMicrosoft][ODBC SOL Server Driver][SQL1Server]INSERT Statement conflicted with TABLE CHECK Constraint 'cnkSemailAddressSemailBlankNoChecks. The conflict occurred in database Pubs' table 'aulh.email' [Micro$oft}[ODBCSQL Server Driver][SQL ServerJThe statrnement has been aborted
17 Send: Г' Email rne offers
Здесь выводится текст: [Microsoft][Драйвер ODBC SQL Server][SQL Server] Оператор INSERT конфликтует с ограничением TABLE CHECK 'chk$emailAddres$emailBlankNoChecks'. Конфликт возник в БД 'Pubs' (публикации), таблице 'aut_email' (электронные адреса авторов) [Microsoft][Драйвер ODBC SQL Server][SQL Server] Оператор был выброшен Мы могли бы придумать довольно сложную схему отображения ошибок, чтобы выводить ошибку в более дружественном формате, однако основной процесс никогда не будет очень хорошо воспроизводить конкретную ситуацию.
То, что в промежутке Что наиболее для нас приятно, так это "где-нибудь в промежутке". Идеальные приложения клиент-сервер (или n-звенная структура подобного типа) использовали бы БД в НФДК с клиентом, который контролирует наименьшее количество данных, необходимых для обеспечения потребностей пользователя, и были бы способны обрабатывать любые сообщения об ошибках, которые возвращаются от SQL Server. Некоторые типы правил не подходят для обеспечения на стороне клиента, к ним относятся: •
правила связей между таблицами;
•
уникальность данных;
•
отношения через внешние ключи.
Для этих правил вам, конечно, будет нужно позволить серверу обрабатывать ошибку и создавать средство для отображения пользователю информации об ошибке в разумной форме. Если мы вернемся к нашему предыдущему примеру в форме Visual Basic, нам бы потребовалось сделать кое-что подобно следующему: . Email Address
Lsl OK
Cancel
252
Планирование физической структуры Здесь два выключателя были заблокированы, чтобы пользователь не мог взаимодействовать с ними, пока не заполнен адрес электронной почты. Как далеко мы пойдем с предварительной проверкой данных — это осмотрительная смесь практичности с удобством сопровождения (например, изменения к схеме данных могут также потребовать изменений в программе на Visual Basic и, как следствие, повторного испытания и схемы, и кода на Visual Basic). Если бы мы хотели проверить, что напечатанный адрес электронной почты правильный, мы могли бы просто поручить системе SQL Server выполнить контроль, используя проверку ограничений, и отобразить результаты проверки в подходящем сообщении для пользователей.
Клиент и конфигурация данных Теперь мы посмотрим на некоторые из основных конфигураций, которые можем использовать при создании систем сервера БД. Хотя группа, которую мы рассмотрим, не является всеобъемлющей, она, определенно, даст нам основу для выбора настроенных конфигураций, которые решают наши проблемы.
Классический клиент-сервер Старый, верный, и все же все еще важный после стольких лет клиент-сервер! Десять лет назад это была очень новая концепция. Девять лет и триста шестьдесят четыре дня назад (На момент выхода в свет перевода этот срок существенно изменился. Прим. перев) все мы начали придумывать лучшую конфигурацию, и хотя многие приложения теперь n-звенного типа, клиент-сервер все еще подходит во многих ситуациях, когда используется SQL Server. Обычно вы имеете одну или несколько рабочих станций, непосредственно соединенных с сервером БД SQL Server. Соединение должно быть разумно быстрым, так как в первую очередь это нужно для передачи по сети SQL-команд, посылаемых серверу, и набора результатов, возвращаемых клиенту.
Рабочая станция
Сервер БД Рабочая станция
Одно из основных достоинств этой конфигурации — простота. Программное обеспечение клиента посылает команды непосредственно БД, чтобы отыскать или изменить то, что требуется. Если эти команды хорошо продуманы и хорошо оформлены (тема, которую мы рассмотрим несколько глубже в главе 12), а результаты, которые мы получаем от сервера, настолько узки (настолько мало столбцов, насколько возможно) и настолько коротки (настолько мало строк, насколько возможно), насколько возможно, достижение требуемого результата будет обычно не слишком трудным. Если вы имеете относительно небольшое число пользователей, которым разрешен доступ к вашим данным, эта конфигурация будет подходящей. Однако что означает "небольшое" число, будет зависеть непосредственно от типа аппаратных средств, на которых работает сервер БД. 253
Глава 9 Когда число пользователей становится очень большим, или вы должны использовать слабую машину для управления вводом данных, непроизводительные расходы конфигурации системы клиент-сервер могут стать слишком большими, чтобы их допустить. Одна из главных причин в том, что наиболее дорогостоящие действия — соединение/разъединение с сервером. Большинство приложений требует нескольких связей с сервером и, чтобы уменьшить непроизводительные расходы сети, они часто формируются так, чтобы постараться сохранить свои связи. Как мы сказали ранее, сохранение от пяти до десяти тысяч открытых связей сервера БД может привести к большому расходу ресурсов обработки данных.
Трехзвенная конфигурация Конфигурации с тремя звеньями (и более общие n-звенные конфигурации) становятся очень популярными как способ отделить логику представления от бизнес-логики и бизнес-логику от логики данных. Как изображено на следующем рисунке, рабочая станция пользователя никогда непосредственно не связывается с сервером БД, она просто соединяется с группой объектов (обычно написанных на языке, поддерживающем СОМ или CORBA). Представление
Данные
Рабочая станция
Объект-посредник
Сервер БД
Рабочая станция
В идеале, бизнес-объекты, находящиеся на бизнес-уровне, имеют две очень важных задачи: Формирование пула соединений Чтобы уменьшить непроизводительные затраты модели клиент-сервер, объект-посредник объединяет соединения. Так как клиенты имеют тенденцию делать одни и те же запросы много раз, вместо того, чтобы открыть соединение с сервером БД на время, немного меньшее, чем обычно используется, объект-посредник формирует пул соединений, который он использует, чтобы соединиться с сервером БД. Вы также можете иметь много экземпляров объектов, кэшированных для использования в объекте-посреднике, а также иметь несколько объектов-посредников. По существу, философия, которая стоит за этой конфигурацией, заключается в том, чтобы забрать работу по соединению с клиентом у сервера БД и передать ее более масштабируемому устройству. Дальнейшие детали этого могут быть найдены в "Professional Windows DNA: Building Distributed Web Applications with VB, COM +, MSMQ, SOAP, and ASP" (Wrox Press, ISBN 1861004451). Обеспечение бизнес-правил Мы можем легко столкнуться с проблемами функционирования, связанными с конкретными бизнес-правилами, которые кажутся несколько произвольными и включают большое количество операций AND (И), NOT (HE) и OR (ИЛИ), которые делают их неэффективными для кодирования. Примером такого правила могло бы быть следующее: 254
Планирование физической структуры Подписка пользователя охватывает будние дни с понедельника по пятницу, но НЕ субботы и воскресенья, если они не дают увеличение оплаты, связанное с уикэндами, хотя у банка не выходные дни. Если инженер требуется вне диапазона от 9 до 17 часов, это потребует дополнительной оплаты, причитающейся ему за посещение. Правила, подобные этим, вообще не способствуют применению кода Transact-SQL, так как обработка будет требовать обратной связи от пользователя после первого же раза, когда он попытается сохранить запись, а правила подвержены частым изменениям в зависимости от потребностей изменения управления. Если мы кодируем на функциональном языке, способном взаимодействовать с пользователями, бизнес-правила легче реализовать и, следовательно, изменять. Как мы увидим в следующих двух главах, правила SQL Server должны управлять всеми неизменяемыми правилами, предписанными для данных в БД.
Сложное использование Web-серверов Другой сценарий, который становится все более распространенным, — наличие тысяч или даже миллионов пользователей, только читающих информацию, связанных с нашей БД. В этой ситуации мы можем делать одну или большее количество копий наших данных только для чтения (в сильно денормализованной форме). Переключатель, балансирующий нагрузку (и целая серия настроек, которые лежат за пределами моих основных способностей), берет запросы от клиента, направляет эти запросы на Web-сервер (каждый Web-сервер, являющийся копией одного и того же Web-сервера), далее Web-сервер вызывает один из серверов БД (снова управляемый переключателем, балансирующим нагрузку). Это показано на следующем рисунке:
Web-сервер 1
Web-сервер 2
Web-сервер N
БД1 только для чтения
БД2 только для чтения
БДЫ только для чтения
Переключатель, балансирующий нагрузку
Сервер нормализованной БД
Нормализованная БД — источник всех отредактированных данных, и если пользователь должен изменить данные (типа сделанного заказа или сохранения некоторых настроек), то мы можем обеспечить интерфейсы к исходному серверу БД или, возможно, другому серверу, который мы используем только для редактирования данных. Возьмем, например, большие медиа-носители информации и электронные коммерческие сайты книг с десятками тысяч CD, книг и т. д. Вся информация относительно этих элементов будет находиться в нормализованной БД, сконфигурированной так, чтобы обеспечить целостность данных. 255
Глава 9 Учитывая особенности функционирования, мы могли бы взять эти данные из своей нормализованной формы и преобразовать их с помощью предварительно построенных, предварительно соединенных запросов, чтобы создать Web-страницы, которые будут видеть клиенты Web-сайта. Мы могли бы также разделить данные на серверы. На основе факторов типа частоты использования мы могли бы поместить классическую музыку на один сервер, рок-музыку на другой, и так далее.
Глобальная сеть В нашей глобальной экономике многие организации расположены по всей стране или даже на разных континентах. В некоторых случаях мы можем построить простой клиент-сервер или даже многозвенные приложения, которые будут достаточны для наших нужд редактирования данных. Однако есть много случаев, когда непрактично редактировать и передавать данные по глобальной сети. Следовательно, мы можем построить топологию, которая в основном выглядит следующим образом: Теннесси, США
Лондон, Англия
'
:
тш'1||щц ни $Щ,
Сервер БД
Флорида, США
OLAP-сервер
Сервер БД
OLAP-сервер
Мы можем использовать тиражирование слиянием, чтобы позволить редактировать на любом конце канала, а затем сливать изменения назад во все серверы. В этом случае каждый из офисов на нашем рисунке будет чувствовать, как будто он имеет свою собственную БД, где он может редактировать все строки в БД. Мы могли бы разделить данные, чтобы избежать ситуаций, когда один пользователь в штате Теннесси редактирует какую-то строку, в то же самое время другой в Лондоне делает то же самое, но мы могли бы просто использовать средства разрешения конфликтов SQL Server для управления ситуациями, когда они возникают.
Учебный пример В нашем учебном примере мы собираемся использовать самую простую из возможных топологий, в то же время отобразив все идеи, которые мы рассмотрели. Мы будем использовать простую установку клиент-сервер с дополнительным оперативным хранилищем данных:
256
Планирование физической структуры
Основной пользователь счета
Управляющий
Сервер БД
. Основной пользователь счета
Сервер оперативного хранилища данных
Одна из главных причин придерживаться испытанной и надежной топологии клиент-сервер заключается в том, что мы будем реализовывать бизнес-правила на сервере БД, когда это возможно. Мы, конечно, обсудим любую ситуацию, когда эта топология клиент-сервер является помехой. Последняя вещь, которую следует упомянуть относительно нашего решения использовать структуру клиент-сервер, — независимо от того, какую топологию нужно реализовать, следует обычно иметь как можно больше правил, помещенных на сервер БД, по причинам, которые мы опустим. Мы реализовали их здесь как пример, и именно так я построил бы систему.
Резюме Как вы, наверное, догадались, познакомившись с предыдущими главами этой книги, абсолютно нет НИКАКОГО простого ответа, как быть с чрезвычайно широким диапазоном возможных ситуаций/конфигураций/параметров, которые могут затрагивать реализацию. Очень важно, чтобы вы использовали не только то, что вы узнали в этой главе, но, в зависимости от вашей роли, изучили бы все, что можете изучить относительно того, как работают SQL Server и его дополнительные средства, используя как другие книги (некоторые из них мы упомянули в тексте), так и SQL Server Books Online. Мы рассмотрели маленький подраздел различных возможных способов того, как связать БД с клиентом или как выбрать структуру решения. Данная глава не дает вам ничего, что вы могли бы почерпнуть относительно физической структуры проекта БД. Это потребовало бы целой книги само по себе. Что мы стремимся сделать — дать вам основную часть информации, которая позволить прочувствовать предмет и работать с 80 процентами проблем, с которыми вы столкнетесь, и решить, где найти дальнейшую информацию, чтобы бороться с оставшимися 20 процентами проблем. Следующая глава возвращает нас назад на путь, где мы более глубоко посмотрим на процесс создания таблиц, включая полное рассмотрение основных объектов SQL Server, таких как индексы и типы данных, которые мы будем использовать, чтобы создавать наши окончательные БД.
257
'
Планирование и реализация основной физической структуры Введение Наша цель в этой главе — построение таблиц, которые составят нашу БД. Мы говорили относительно этого процесса, планировали его, даже рисовали картинки, но теперь настало время реализовать все физически. Процесс создания OLTP-таблиц может быть в ряде случаев трудоемким, но, если вы используете любое из известных средств проектирования БД, задача будет гораздо более легкая. На стадии реализации мы должны реорганизовать наши объекты, добавив практические детали и, наконец, преобразовать это все в код. В этой главе мы будем строить наш код без использования каких-либо средств проектирования или генераторов кода, так что вы сможете точно увидеть, что происходит. Вместо этого мы будем использовать для создания таблиц Query Analyzer (средство выполнения SQL-запросов) и ручное редактирование на языке определения данных (Data Definition Language — DDL). Мы рассмотрим код формирования таблиц и отношений, но не код определения доменов данных, которые используются для выбора типов данных. Мы исследуем в некоторой степени каждый из внутренних типов данных, обсудим отдельные расширения их, которые могут нам потребоваться, и определим некоторые основные столбцы и типы данных, которые будем, вероятно, неоднократно использовать. Следующая глава будет посвящена уточнению определения доменов, чтобы в дальнейшем ограничивать значения данных конкретными размерами или использовать их в задачах определения типов.
Глава 10 Мы определим уникальные ограничения и индексы для наших таблиц и рассмотрим физические структуры расположенных на диске индексов и таблиц. Основы понимания, как размещаются индексы и данные, помогут строить представления того, что происходит внутри SQL Server при размещении данных и выполнении запросов. В свою очередь, это поможет нам получить ясную картину, в каких индексах мы нуждаемся при тех или иных ситуациях. Когда мы завершим рассмотрение тем этой главы, то получим схему БД, включающую группу таблиц с индексами и отношениями. Наша БД будет далека от завершения, но мы создадим прочную основу для реализации бизнес-правил, которые будут рассмотрены в следующей главе.
Средства генерации БД Чего нам следует придерживаться на практике — никогда не создавать БД без приличного программного средства. Имеется несколько превосходных средств формирования БД, которые также хороши и для моделирования БД. Создание сервера БД возможно и без них, так как имеется ряд администраторов БД, которые имеют множество текстовых файлов, представляющих их БД, но они расточительно тратят время. На этой стадии реализации многие программисты будут стремиться начать писать код. Если мы начнем создавать все наши таблицы и код вручную, это потребует для выполнения огромное количество времени. Первая система, для которой я написал код БД, включала более чем 800 хранимых процедур и триггеров, написанных вручную более чем за восемь месяцев. Это было восемь лет назад, и это был фантастически поучительный пример. Сегодня современные средства проектирования БД позволяют мне строить БД с сопоставимым кодом в течение недели. Имеются средства, которые выполняют для нас многие "детали" работы, но если мы не вполне понимаем, как они делают это, очень трудно обеспечить их успешное применение. Выполнение этих "деталей" вручную перед использованием таких мощных средств будет стоить крови, пота и слез. Однако чтобы показать вам, как работает процесс физической реализации, мы не будем использовать никакие из этих средств в течение следующих трех глав. Мы будем строить вручную сценарии, которые средства проектирования сформировали бы автоматически. Это поможет нам понять то, что мы получаем с помощью таких средств. Оно является также хорошим упражнением для любого архитектора БД или администратора БД, чтобы рассмотреть синтаксис SQL Server; только не делайте это на БД с девяноста таблицами, если у вас нет в запасе восьми месяцев. В этих главах мы будем иметь дело, прежде всего, с разработкой БД. Разработка БД связана с размещением данных, которые не имеют никакой деловой ценности. Разработчики должны быть способны изменять или удалять любые элементы данных (но не структуры), разрабатывая код доступа к БД. К сожалению, многие программисты будут сразу же просить реальные данные для БД.
260
Планирование и реализация основной физической структуры
Схема физического проектирования В части книги, посвященной логическому проектированию, мы обсуждали весьма детально, как проектировать наши объекты. В этом разделе мы будем брать этот проект и разрабатывать на его основе реальные объекты БД. Так же как инженеры-конструкторы берут чертежи архитектора и исследуют их, чтобы разобраться в том, какие необходимы материалы и является ли все это реализуемым, мы возьмем нашу логическую модель и выделим те части, которые могли бы быть нереализуемы или слишком трудны для реализации. Некоторые части первоначального представления архитектора, вероятно, придется изменить, чтобы сделать его реалистическим на основе факторов, которые не были известны, когда проект был первоначально задуман, наподобие типа почвы, наличия холмов, и т. д. Мы также окажемся перед необходимостью выполнить процесс перевода представлений из сырой концепции в реализацию. В течение логического проектирования мы поверхностно смотрели на любые реальные примеры кода и старались рассматривать только особенности, общие для всех реляционных СУБД. Логическая модель, которую мы разработали, могла бы быть реализована и в Microsoft SQL Server, и в Microsoft Access, и в Oracle, и в Sybase и в любой другой СУРБД. Начиная с этого момента, я буду считать, что вы установили Microsoft SQL Server 2000 и создали рабочую БД. Мы рассмотрим многие из особенностей, которые SQL Server дает нам, хотя и не все. Для получения большего количества информации относительно Microsoft SQL Server 2000 читайте "Professional SQL Server 2000 Programming", Rob Vieira (Wrox Press, ISBN 1-867004-48-6). На протяжении всей главы нашей целью будет преобразование логического проекта в физическую реализацию, настолько близкую, насколько возможно. Мы рассмотрим следующее: •
преобразование логического проекта в физический проект;
•
создание таблиц;
•
создание столбцов;
•
типы данных;
•
служебные столбцы;
•
сопоставление;
•
ключи;
•
создание отношений;
•
распределение ваших метаданных между разработчиками.
Каждый из этих шагов будет подробно рассмотрен, чтобы получить надлежащее понимание того, что в основном доступно для получения реального проекта и правильной реализации.
Преобразование нашего логического проекта Раньше раздел, подобный этому, посвященный физическому проектированию, был бы намного более длинным, чем те немногие страницы, которые мы имеем здесь. Как мы уже говорили, потенциальные возможности аппаратных средств и программного обеспечения, совместно с методологиями хранилищ данных, потенциально позволяют нам реализовать БД почти точно в соответствии с логическим проектом. Однако нужно быть осторожным при проектировании физической БД, чтобы не попытаться реализовать что-то, что является слишком трудным для практического использования.
261
Глава 10
Подтипы Нам, вероятно, придется отклониться от нашего логического проекта, когда имеем дело с подтипами. Вспомните, что подтип в терминах моделирования БД указывает определенный тип отношений "один к одному", где мы имеем одну основную таблицу, например,person (человек), и одну или более таблиц подтипов, которые уточняют значения этой таблицы, например, employee (служащий), t e a c h e r (преподаватель), customer (заказчик) и т. д. Они особенно существенны в логическом проектировании, но имеются также серьезные основания, чтобы сохранить их и в нашем физическом проекте. В зависимости от того, как вы завершили ваше логическое моделирование, вам, вероятно, придется выделить дополнительные подтипы. Всегда имейте в виду, что имеется столько путей проектирования БД, сколько существует архитекторов данных. Я приведу примеры случаев, когда мы будем или не будем хотеть иметь таблицы подтипов. Пример 1 Пусть мы имеем таблицу movie (кинофильм), которая содержит названия кинофильмов (наряду с другой информацией типа жанра, описания и т. д.). Мы также имеем таблицу movieRentallnventory (каталог арендной платы кинофильмов), которая определяет арендную плату за видеокассеты или DVD. Зададим число, которое используется как вторичный ключ, и столбец itemCount (количество), определяющий число видеокассет или дисков, которые находятся в пакете арендуемых фильмов. Имеется также столбец mediaFormat (формат фильма), который определяет, является ли фильм видеофильмом или DVD.
Movie Movield
MovieRentallnventory MovieRentallnventoryld
Name
MovieidW Number (AK1.1) ItemCount MediaFormat
MediaFormat
L 262
VideoTape MovieRentallnventoryld (FK) HiFiSoundFI StereoSoundFI
DVD |_MovieRentallnventoryld (FK) RegionEncoding Version I AspectRatio
SoundFormat SoundFormatld Name(AKI.I) Description
DVDSoundFormat DVDSouindFormatld SoundFormatld (FK)(AK1.1) MovieRentallnventoryld (FK)(AK1.2)
Планирование и реализация основной физической структуры Здесь Movie — таблица "кинофильм"; Movield — идентификатор кинофильма; Name — название (кинофильма); MovieRentallnventory — таблица "каталог арендной платы за кинофильмы"; MovieRentallnventory Id — идентификатор арендной платы за кинофильмы; Number — номер элемента каталога; itemCount — число элементов; MediaFormat — формат фильма; VideoTape — таблица "видеокассета"; HiFiSoundFl — флаг звука высокого качества; StereoSoundFl — флаг стереозвука; DVD — таблица "DVD-диск"; RegionEncoding — код региона; Version — версия; AspectRatio — форматное соотношение; SoundFormat — таблица "формат звука"; SoundFormatld — идентификатор формата звука; Description — описание; DVDSoundFormat — таблица "формат звука DVD"; DVDSoundFormatld — идентификатор формата звука DVD. Мы определяем подтипы значения mediaFormat для videoTape и DVD. Нам нужно знать формат, потому что DVD имеют намного больше особенностей, чем видеозаписи, наиболее важными из которых являются код региона и формат звука. Мы реализуем специальный DVDSoundFormat для DVD как отношение "многие ко многим", так как DVD поддерживают любые наборы форматов звука; в то время как для типа videoTape мы имеем только поля для высококачественного и стерео звука. Имеется несколько проблем с этой реализацией: •
Чтобы сформировать новую арендную плату за видеофильм, мы должны создать записи, по крайней мере, в двух таблицах (movieRentallnventory и videoTape или DVD, и еще в DVDSoundFormat для DVD). Это — не слишком большая проблема, но может потребовать некоторой специальной обработки.
•
Если мы хотим видеть список всех элементов каталога арендной платы, включая звуковые характеристики, мы должны будем написать очень сложный запрос, который присоединяет movieRentallnventory к videoTape, и объединить с другим запросом между movieRentallnventory и DVD, что может быть достаточно медленно или просто слишком тяжело для реализации. (Мы детально рассмотрим запросы в главе 12.)
Это общие проблемы с подтипами. Что мы должны решить — это насколько целесообразно держать данный набор таблиц, реализованных как подтипы. При рассмотрении того, что делать с отношением подтипов, одна из наиболее важных задач, которую нужно решить, — сколько будет в общем сущностей в каждом подтипе и сколько атрибутов они в общем имеют. Простой обзор атрибутов таблиц подтипов в этом примере показывает, что они имеют много общего. В большинстве случаев это — просто вопрос количества элементов. Возьмем DVD. Код региона характерен для этого видеофильма, но некоторые видео имеют различные версии, и технически каждое изделие видео имеет форматное отношение (отношение высоты и ширины изображения), которое будет интересно любому энтузиасту. Поскольку мы можем определить характеристики звука видеозаписи, используя два простых выключателя, этот же самый метод определения звука мы можем использовать для DVD. Мы будем только должны ограничить количество элементов soundFormat на видео одним, в то время как DVD может иметь их до восьми.
263
Глава 10 Таким образом, мы изменяем отношения подтипов следующим образом: MovieRentallnventory Movie Movield Name
SoundFormat SoundFormatld Name (AK1 .1) Descriptiori
MovieRentallnventoryld Movield (FK) Number (AK1.1) Version itemCount MediaFormat AspectRatio DVDRegionEncoding
• MovieRentallnventorySoundFormat MovieRentallnventorySoundFormatld ш MovieRentallnventoryld (FK)(AK1.1) SoundFormatld (FK)(AK1.2)
Здесь дополнительно MovieRentallnventory SoundFormat — таблица "формат звука каталога арендной платы за кинофильмы"; MovieRentallnventorySoundFormatld — идентификатор формата звука каталога арендной платы за кинофильмы. Обратите внимание, что это нарушает правила нормализации. Однако действительно трудно объединить использование подтипов, которые не нарушают нормализацию, если два объекта, являющиеся подтипами, содержат в основном одни и те же составные части. Последний момент, который должен быть сделан, связан с тем, что поскольку мы изменили нашу структуру и храним различные типы информации в одной и той же сущности, то теперь должны быть осторожны, чтобы не допустить ввода в таблицу неподходящих данных. Например, когда mediaFormat — видеокассета, то поле DVDRegionEncoding не используется и должно быть установлено в NULL. Эти бизнес-правила будут реализованы, используя ограничения и триггеры, так как значения в одной таблице будут определяться значениями в другой. Может показаться, что сворачивание (или объединение) подтипов в единственную таблицу дает более легкую и более прямую реализацию, в то время как фактически облегчение работы возможно лишь где-нибудь еще. Знание, какие значения используются, конечно, поможет нам решить, стоит ли сворачивать подтипы. Пример
2
В качестве второго примера рассмотрим случай, когда не имеет смысла сворачивать подтипы. Вы будете обычно иметь подтипы, которые не стоит сворачивать, когда имеются два (или больше) объекта, которые делят общего предка, но, являясь подтипами, эти подтипы не имеют абсолютно никакого отношения друг к другу. Посмотрим на следующую структуру:
264
Планирование и реализация основной физической структуры
Person
Employee 'Personld (FK) Function Number
Customer Personld (FK) Number AccountNumber
Здесь Person — таблица "человек"; Personld — идентификатор человека; Type — категория; Name — имя; Employee — таблица "служащий"; Function — должность; Number — номер; Customer — таблица "заказчик"; AccountNumber — номер счета. Мы имеем таблицу person, которая содержит имя и другие атрибуты, включая такие, которые реализованы как дополнительные таблицы с отношениями к таблице person (такие как адрес, информация о партнерах, журнал партнеров и т. д.). В нашем примере и служащие, и заказчики являются людьми, но сходство на этом и завершается. Поэтому в данном случае лучше оставить подтипы, как они есть. Единственная проблема здесь состоит в том, что если кто-то хочет узнать имя служащего, вы должны будете использовать соединение. Это не является проблемой с точки зрения программирования, но это проблема с технической точки зрения. Во многих случаях основные особенности, которые определяют подтипы, более разумно использовать в создаваемых представлениях, которые позволяют рассматривать таблицу employee как будто она содержит также и параметры таблицы person. Определение, когда следует сворачивать таблицу — один из тех случаев, который приходит со временем и с опытом. Как правило, обычно лучше следующее: •
Свернуть, когда таблицы подтипов очень похожи характеристиками на таблицу-предка, особенно, когда мы часто хотим видеть их в списке вместе.
•
Оставить в качестве подтипов, когда данные в подтипах разделяют общие характеристики, но логически не связаны друг с другом каким-либо образом.
265
Глава 10 Другие причины отхода от логического проекта Наиболее частая причина отхода от логической модели — денормализованные структуры, обычно итоговые данные, суммирующие заданный набор строк, как в следующих таблицах: Account Accountid Number Balance
1
Transaction Transactionld Accountid (FK) Number Amount
Здесь Account — таблица "счет"; Accountid — идентификатор счета; Number — номер; Balance — баланс; Transaction — таблица "сделка"; Transactionld — идентификатор сделки; Amount — сумма. Здесь атрибут b a l a n c e будет эквивалентен итогу всех сумм сделок для данного accountid. Мы обсудим, как реализовать этот тип итоговых данных, используя триггеры, так как иногда может потребоваться введение денормализованных данных, чтобы решить конкретные задачи функционирования. Введение этого на ранних стадиях физического проектирования может показаться разумным. Однако следует подчеркнуть, что денормализацию не следует выполнять, если нас не вынуждают обстоятельства. Обычно денормализация причиняет больше проблем, чем их решает. Для архитекторов-новичков может быть тяжело понять, почему лучше восстановить значения, используя соединения вместо создания дубликатов значений в БД, чтобы ускорить запрос или группу запросов. Объединения в SQL Server чрезвычайно эффективны и очень часто обеспечивают адекватное функционирование.
Таблицы Таблицы являются центральными объектами в SQL Server. Основы таблиц очень просты. Если вы посмотрите на полный синтаксис для оператора CREATE TABLE, то увидите, что имеется много различных необязательных параметров. В этой главе и части следующей мы рассмотрим оператор создания таблицы и как лучше всего выбрать, что делать с каждой разновидностью. Первый необходимый момент очень прост: Щ CREATE TABLE [ < Б Д > . ] [ < в л а д е л е ц > . ] < и м я _ т а б л и ц ы >
Мы будем добавлять элементы в угловых скобках (). Обратите внимание, что выражение в квадратных скобках ([дополнительный_элемент]) является необязательным и может быть опущено.
266
•
— обычно вам не нужно определять БД в операторе CREATE TABLE. Если она не определена, подразумевается текущая БД, где выполняется оператор.
•
— это имя владельца таблицы. Им должен быть текущий пользователь или dbo — владелец БД. Если оператор выполняется лицом из категории системных администраторов (s уsadmin) сервера, имеющим функции либо владельца БД (db_dbowner), либо администратора данных (db_ddladmin), то таблица может быть создана для любого пользователя путем задания его имени.
Планирование и реализация основной физической структуры Лучшая практика — минимизация числа владельцев. Наличие многих владельцев создает проблемы "цепи владельцев" между зависимыми объектами. Обычно все объекты принадлежат только одному пользователю, имеющему имя dbo (database owner — владелец БД).
Обозначение Значение дает имя таблицы. Имя должно соответствовать типу системных данных sysname. В SQL Server он определен как строка из 128 двухбайтовых символов Unicode. Сочетание owner (владелец) и tablename (имя таблицы) должно быть уникально для БД. Если первый символ имени таблицы — одинарный символ #, таблица является временной локальной. Если первые два символа имени таблицы — ##, это — глобальная временная таблица. Мы кратко упомянем временные таблицы в главе 12, хотя они не такая уж большая часть проекта БД, как механизм размещения промежуточных результатов в сложных запросах. Правила SQL Server для имен объектов состоят из двух разных методов обозначения. Первый использует ограничители (или квадратные скобки, или двойные кавычки) вокруг имени (хотя двойные кавычки допустимы только тогда, когда установлена опция SET QUOTED_IDENTIFIER — задание идентификатора в кавычках). Помещая ограничители вокруг имени объекта, вы можете использовать в качестве имени любую строку. Например, [Table Name] или [3232 f jfa*& (&л (] были бы оба законными (но раздражающими) именами. Использование имен с ограничителями вообще-то плохая идея при создании новых таблиц, и его нужно по возможности избегать, поскольку оно делает кодирование более трудным. Однако оно может быть необходимо для взаимодействия с таблицами данных в других средах. Второй и предпочтительный метод обозначения состоит в том, чтобы использовать имена без ограничителей, но они должны следовать нескольким основным правилам: Q
Первый символ должен быть символом, определенным в Unicode Standard 3.1 (короче говоря, латинские символы от А до Z верхнего или нижнего регистра), или символом подчеркивания (_). Стандарт Unicode может быть найден в www.unicode.org.
•
Последующие символы могут быть буквами Unicode, десятичными цифрами, амперсандом (@) или знаком доллара ($).
•
Имя не должно быть зарезервированным словом SQL Server. Имеется довольно большой список зарезервированных слов в электронном руководстве по SQL Server 2000 (смотрите раздел Reserved Keywords — зарезервированные ключевые слова).
Q
В имени не должно быть пробелов.
В то время как правила создания имен объектов достаточно очевидны, более важный вопрос — "Какие имена мы должны выбирать?" Ответ предсказуем: "Независимо от того, что вам кажется, лучшее — это когда другие могут их прочитать." Это может звучать как попытка уйти в сторону, но имеется столько много различных стандартов обозначения, сколько существует архитекторов данных. Стандарт, которого я обычно придерживаюсь, — стандарт, который мы будем использовать в этой книге, но это, конечно, не единственный способ. В основном, мы будем следовать тому же стандарту, который мы начали использовать в логических именах. Учитывая размер в 128 символов, есть немного причин делать много сокращений. Это наиболее важно понять при выборе имен. 267
Глава 10 Так как большинство компаний имеет уже существующие системы, неплохо бы узнать стандарт предприятия для обозначения, чтобы новые разработчики вашего проекта лучше понимали вашу БД и быстрее вошли в ритм. Как пример, дадим имя объекту, который мы могли бы использовать, чтобы хранить телевизионную программу. Следующий список даст несколько различных способов сформировать имя этого объекта: •
t e l e v i s i o n _ s c h e d u l e _ i t e m (телевизионная программа) — использование символов подчеркивания для отделения различных слов. Большинство программистов не очень любят символы подчеркивания, поскольку они непривычны в программе, пока вы не свыкнетесь с ними.
•
[ t e l e v i s i o n schedule item] или " t e l e v i s i o n schedule item" — ограничение скобками или кавычками. Не одобряется большинством программистов, поскольку невозможно использовать это название при задании переменных в коде и легко допустить ошибки.
•
t e l e v i s i o n S c h e d u l e l t e m — смешанный случай для разграничения слов. Это — стиль, который мы используем в нашем учебном примере, так как он мне нравится.
•
tvSchedltem или tv_sched_item или [tv sched item] —сокращенные формы. Они проблематичны, потому что вы должны быть осторожны, чтобы всегда сокращать одно и то же слово одинаковым образом во всех ваших БД. Необходимо вести словарь сокращений, или вы получите различные сокращения для одного и того же слова, например, description (описание) как desc, descr, descrip, и/или description.
Выбор имен для объектов, в конечном счете, — личный выбор, но никогда это не нужно делать произвольно, и он должен быть основан на существующих общих стандартах, существующем программном обеспечении и доходчивости.
Владелец Пользователь, который создает БД, называется владельцем (owner или dbo) БД. Владелец (dbo) — один из администраторов системы или системных специалистов, создающих БД. Любой пользователь может быть помещен в группу владельцев БД, добавляя функцию db_owner (владелец БД), означающую, что пользователь может создавать и удалять любые данные, таблицы, представления и хранимые процедуры. Когда любой пользователь, не-владелец, создает объект, этот объект принадлежит только этому пользователю (и, конечно, dbo или db_owner). Если пользователь по имени Боб пробует получить доступ к таблице без того, чтобы задать владельца примерно так: Щ SELECT * FROM tableName ... то SQL Server сначала проверяет, существует ли таблица, названная tableName и принадлежащая на самом деле Бобу. Если нет, то проверяется, существует ли таблица с этим именем, принадлежащая dbo. Если это так, но dbo не дал Бобу права доступа к таблице, оператор выдаст ошибку. Для доступа к таблице конкретного владельца непосредственно в коде мы должны определить владельца таблицы, используя две части имени, примерно так: §Ц SELECT * FROM dbo.tableName 268
Планирование и реализация основной физической структуры Если мы определим конкретную таблицу и ее владельца, как сделали в предыдущей строке кода, а эта таблица не существует, возникнет ошибка. Владелец получает неявные права создавать, изменять и удалять данные из таблицы, как и саму таблицу. Любому другому пользователю нужно давать права делать это. В данный момент просто важно понять, что каждая таблица, представление и хранимая процедура в SQL Server имеют владельца. Мы должны также обратить внимание, что SQL Server использует схему имен, включающую до четырех частей: [.][.][.] где: •
сервер — определяет сервер, на котором расположен объект. Для объектов на том же самом сервере имя сервера должно быть опущено. К объектам на других серверах можно обращаться только в том случае, если администратор системы задал сервер как связанный.
•
БД — определяет БД, где находится объект. Если эта часть опущена, объект разыскивается в БД, из которой к нему обращаются.
•
владелец — определяет пользователя, кто создает/владеет объект(ом). Если эта часть опущена, SQL Server использует пользователя, который связан с объектом по умолчанию.
•
имя объекта — определяет имя таблицы, представления или хранимой процедуры.
Ограничения Перед тем, как идти дальше, мы должны рассмотреть некоторые ограничения, с которыми должны будем работать при создании таблиц в SQL Server. При создании таблиц для нас важны четыре ограничения: •
Число столбцов — Максимальное число столбцов (то есть полей данных) в таблице — 1024. Если БД должным образом нормализована, это за границами обычных требований.
•
Число байт на строку — Максимальное число байт в строке — 8060. В отличие от числа столбцов максимальное число байт не так уж трудно достичь. Мы увидим это, когда начнем рассматривать символьные и двоичные данные.
•
Число байт на вход индекса — Предел — 900 байт, и для этого есть серьезные основания. Страница содержит примерно 8000 байт, так что если бы индекс был намного больше, чем 900, он стал бы очень неэффективным.
•
Число индексов — Предел — 1 кластерный и 249 некластерных. Мы могли бы подойти близко к этому пределу в системах хранилищ данных. Наличие 250 индексов таблицы в OLTP-системе существенно уменьшило бы эффективность работы при частых корректировках, которые, вероятно, будут производиться с данными. Мы обсудим кластерные и некластерные индексы позже в этой главе.
Более полный список основных требований будет дан в главе 13, когда мы рассмотрим требования к аппаратным средствам.
269
Глава 10
Столбцы Строки, выделенные полужирным шрифтом, являются строками, которые мы используем для определения столбца: CREATE TABLE [.][.] ( [ «спецификация NULL>] -- или AS )
Имеются два типа столбцов, с которыми мы будем иметь дело, — физический и вычисленный (или виртуальный). •
Физические столбцы — это обычные столбцы, где реализовано физическое хранение, и данные фактически содержат значения.
•
Вычисленные (или виртуальные) столбцы — столбцы, которые получаются в результате вычислений, включающих значения любого из физических столбцов таблицы.
Мы, прежде всего, будем иметь дело с физическими столбцами, хотя вычисленные столбцы имеют некоторые симпатичные свежие использования.
Обозначение Значение — это то, где вы определяете имя, под которым будет известен столбец. Правила обозначения столбцов те же самые, что и для таблиц, и связаны с SQL Server. Что касается того, как мы выбираем имя для столбца — снова, это одна из тех задач для конкретного архитектора, основанная на тех же самых видах критериев, как и прежде (стандарты предприятия, лучшее использование и т. д.). В этой книге мы будем следовать такому перечню основных принципов: •
Q
270
За исключением первичных ключей, имя таблицы не стоит включать в имя столбца. Например, в сущности по имени person (человек) мы не должны иметь столбцы, называемые personName (имя человека) или personSocialSecurityNumber (номер соцального обеспечения человека). Никакой столбец не должен иметь в качестве префикса person за исключением первичного ключа p e r s o n l d . Это уменьшает потребность в функциональном имени (изменяющем имена атрибутов, чтобы корректировать смысл, особенно используемом в случаях, когда мы имеем много мигрирующих внешних ключей). Имя должно быть настолько описательным, насколько возможно. Мы будем использовать очень немного сокращений в наших именах. Имеются три известных . исключения: Сложные имена — Так же как и в именах таблиц, если вы имеете имя, которое содержит несколько частей, например, "Conglomerated Television Rating Scale" (комплексный показатель рейтинга ТВ-передач), вам хотелось бы иметь название вроде ConTvRatScale, даже если потребуется некоторая тренировка, прежде чем ваши пользователи привыкнут к его значению.
Планирование и реализация основной физической структуры Признанные сокращения — Например, если бы мы писали систему закупок и нам нужен бы был столбец для таблицы заказа, мы могли бы назвать объект РО (purchase order — заказ), потому что это очень широко используется. Указатели типов данных — Мы будем иногда добавлять короткую строку (скажем, два символа) в конец имени столбца. Например, столбец, который мы используем как Boolean, будем завершать суффиксом "fl" (сокращение от flag — флаг), а столбец даты, заканчивающийся суффиксом "dt", очевидно, будет означать сокращение даты. Заметьте, что мы не будем использовать эти суффиксы, чтобы указывать точные типы данных (например, вы могли бы реализовать тип Boolean с типами int, t i n y i n t , s m a l l i n t , b i t или даже s t r i n g , содержащей "Да" или "Нет"). Обратите внимание, что мы не упоминали о Венгерской нотации, чтобы обозначить тип столбца. Я никогда не был большим поклонником этого стиля. Если вы не знакомы с Венгерской нотацией, она означает, что мы используем префиксы имен столбцов и переменных, указывающие тип данных и возможное использование. Например, мы могли бы иметь переменную, называемую vclOO_columnName, что указывает тип данных varchar (100). Или мы могли бы иметь столбец типа Boolean или b i t , который назван ЬСаг или isCar. По моему мнению, такие приставки — массовое самоубийство, так как легко взять тип из другой документации, которую мы можем получить из SQL Server или другими методами. Наши индикаторы использования обычно идут в конце имени и необходимы только тогда, когда было бы трудно понять без них, что означает конкретная величина. Храня точный тип имен, мы не будем омрачать детали реализации идентификацией сущностей. Одна из красивых сторон использования реляционных БД — что имеется уровень абстракции, который скрывает детали реализации. Отображать их через обозначение столбца означает сделать их стабильными, хотя требования модернизации могут привести к тому, что они устареют (например, расширяя размер переменной, чтобы приспособить к будущим деловым потребностям).
Домены В логическом моделировании концепция доменов включала построение шаблонов для типов данных и столбцов, которые мы используем многократно. В физическом моделировании домены — те же самые, но с дополнительными свойствами, добавленными с точки зрения физических потребностей. Например, на логической стадии моделирования мы определили домены для таких столбцов, как name (имя) и d e s c r i p t i o n (описание), которые возникают регулярно на протяжении всей БД. Причина определения доменов полностью не могла быть понятна на стадии логического проектирования, но становится совершенно ясной на стадии физического моделирования. Например, для домена пате мы могли бы определить следующее: Свойство
Значение
Имя
Name
Тип данных
V a r c h a r (100)
Возможность значения NULL
Not NULL
Проверка ограничений
LEN (RTRIM(Name) ) > 0 — не может быть пустым
10 1868
271
Глава 10 Большинство таблиц будет иметь столбец name, и мы будем использовать данный шаблон для его построения. Это преследует, по крайней мере, две цели: •
Последовательность — если мы определяем каждый столбец name одним и тем же образом, никогда не возникнет вопрос, как обращаться со столбцом.
•
Простота реализации — если средство, которое вы используете для моделирования/реализации БД, поддерживает создание доменов/шаблонов столбцов, вы можете просто использовать шаблон, чтобы построить столбцы, и не должны многократно устанавливать значения. Если средство поддерживает наследование свойств, то когда вы изменяете свойство в определении, значения изменятся всюду.
Домены не являются необходимостью хорошего проекта БД, логического или физического, и не должны использоваться в обязательном порядке в SQL Server, но они позволяют легко и последовательно проектировать, что само по себе является хорошей идеей. Конечно, последовательное моделирование — всегда хорошо, независимо от того, действительно ли вы используете программное средство, которое делает работу для вас.
Выбор типов данных | []
Выражение используется для выбора типа данных столбца. Выбор надлежащих типов данных, чтобы соответствовать домену, определенному на стадии логического моделирования — очень важная задача. Один тип данных мог бы быть более эффективен, чем другой, подобный тип. Например, размещение целых данных можно сделать с использованием типа целых данных, числовых данных или даже с плавающей точкой, но они, конечно, не эквивалентны в реализации или функционировании. В этом разделе мы рассмотрим все встроенные типы данных, которые обеспечиваются фирмой Microsoft, и обсудим ситуации, где их лучше использовать.
Точные числовые данные Имеется много базовых типов данных, в которых вы можете хранить числовые данные. Мы рассмотрим две различные категории числовых данных: точные и приближенные. Различия очень важны и должны быть хорошо поняты любым архитектором, кто создает систему, которая хранит показания, измерения или другие числовые данные. Точные числовые значения включают типы b i t , i n t , b i g i n t , s m a l l i n t , t i n y i n t , decimal и денежные типы данных (money и smallmoney). Точные значения не имеют никакой ошибки, как бы они ни были размещены — в виде целых чисел или чисел с плавающей точкой, потому что они имеют фиксированное число цифр до и после десятичной точки (или другой системы счисления). Однако когда мы должны хранить десятичные значения в точных типах данных, мы заплатим за работу с ними тем, что будем должны указать, как они хранятся и как с ними работать.
272
Планирование и реализация основной физической структуры bit Тип b i t (бит) имеет значения 0, 1 или NULL. Он обычно используется как тип Boolean. Это — не идеал типа Boolean, потому что SQL Server не имеет отдельного типа данных Boolean, но это — лучшее, что мы в настоящее время имеем. Столбцы типа b i t не могут быть индексированы — наличие только двух значений (фактически три с NULL) приводит к неэффективному индексу, так что он не должен использоваться в сложных поисковых комбинациях полей. Если возможно, вы должны избегать значения NULL в полях типа b i t ; включение значений NULL в логическое сравнение увеличивает сложность запроса и вызывает проблемы. Столбец типа b i t требует одного байта для хранения восьми экземпляров данных таблицы. Следовательно, наличие восьми столбцов типа b i t приведет к тому, что ваша таблица будет не больше, чем если бы она имела только один столбец типа b i t . int Целые числа от -2 147 483 648 до 2 147 483 647 (то есть от -2
31
до 2
31
- 1).
Тип данных i n t (целое число — можно также использовать идентификатор integer, прим. перев.) используется, чтобы хранить знаковые (+ или-) целые числа. Тип данных i n t часто используется как первичный ключ для таблиц, поскольку он очень мал (требует четырех байт хранения) и очень эффективен для хранения и извлечения. Единственный реальный недостаток типа данных i n t состоит в том, что он не включает беззнаковой версии, которая хранила бы неотрицательные значения от 0 до 4294967296 (или 32 2 ). Поскольку большинство значений первичного ключа начинается с 1, это дало бы нам более двух миллиардов дополнительных значений для первичного ключа. Это может казаться ненужным, но системы, которые имеют миллиарды строк, становятся все более обычными. Другое приложение, где поле типа i n t играет важную роль — хранение IP-адресов как целых чисел. IP-адрес — просто 32-битовое целое число, разбитое на четыре октета. Например, если вы имеете IP-адрес 234.23.45.123, то можете взять (234 * 2563) + (23 * 2562) + + (45 * 256 ) + (123 * 256 ). Это значение будет хорошо подходить под 32-битовое целое число без знака, но не подходит для знакового числа. Однако в SQL Server есть 64-битовое целое число, которое полностью покрывает текущий стандарт IP-адресов, но требует вдвое больше памяти. bigint Целые числа от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 (то есть от -2 6 3 до 2 ё з - 1). Единственная причина использовать 64-битовый тип данных b i g i n t (большое целое) — в качестве первичного ключа для таблиц, где вы будете иметь больше чем два миллиарда строк, или если ситуация непосредственно диктует это, наподобие ситуации с IP-адресами, которую мы только что обсудили. smallint Целые числа от -32768 до 32767 (или от - 2 1 5 до 2 1 5 - 1). Если мы можем гарантировать, что нам нужно меньше чем 32767 элементов, может быть полезен s m a l l i n t (малое целое). Он требует 2 байта хранения. 273
Глава 10 Использование s m a l l i n t может быть компромиссом. С одной стороны, это экономит два байта по сравнению с типом i n t , но с другой стороны s m a l l i n t недостаточно велик, чтобы использовать его в любых случаях. Если данные могут вписаться в ограничения этого домена, и ваша таблица будет очень большой, стоит использовать s m a l l i n t , чтобы сэкономить два байта. Однако использование s m a l l i n t для уменьшения БД может привести к исчерпанию места, что потребует изменить весь код, чтобы использовать другой тип данных, — все это может оказаться слишком сложным. Мы будем использовать для нашей БД тип i n t . Также обычно плохо использовать s m a l l i n t для первичного ключа (кроме особых ситуаций, типа того, когда нам нужны миллиарды строк, или когда байт или два приведут к изменению в функционировании). Однородность требует кодировать нашу БД более последовательно. Это может показаться несущественным, но большинство средних систем намного более легко закодировать, когда вы автоматически знаете, каков тип данных. Одно из использований s m a l l i n t , которое возникает время от времени, — использование его как тип Boolean. Я предполагаю, что это возникает потому, что в Visual Basic 0 равен False, a -1 — True (фактически Visual Basic будет обращаться с любым значением, отличным от нуля, как с True). При хранении данных таким образом — не только огромная трата места (2 байта против потенциально 1/8 байта), но и запутывание всех других программистов SQL Server. Драйверы ODBC и OLE DB делают этот перевод для вас, но даже если бы они не делали это, стоит потратить время, чтобы написать метод или функцию на Visual Basic, чтобы перевести True в значение 1. tinyint Целые числа от 0 до 255. Те же самые комментарии, что и для s m a l l i n t , могут быть сделаны для t i n y i n t (очень маленькое целое). Тип t i n y i n t очень маленький, использует один байт для хранения, но мы редко используем его, потому что имеется очень немного ситуаций, где мы можем гарантировать, что значение никогда не будет превышать 255 или принимать отрицательные значения. Единственное использование, которое я сделал по отношению к t i n y i n t за эти годы в предыдущих версиях SQL Server, — если мне нужно было группировать немного столбцов. Я не мог бы сделать это непосредственно, если бы запускал запрос подобно этому:
I
SELECT bitColumn, count (' FROM testbit Group GROUP BY bitColumn
В этом случае я получал ошибку. Однако я написал следующий запрос, и он заработал:
I
SELECT CAST(bitColumn AS t i n y i n t ) AS b i t C o l u m n , FROM t e s t b i t Group GROUP BY CAST(bitColumn AS t i n y i n t )
count(*)
SQL Server 2000 больше не делает это различие, но оно может все еще вызвать замешательство, если вы должны писать запросы, которые нужны для перехода от одной к другой версии SQL Server.
274
Планирование и реализация основной физической структуры decimal (или numeric) Все числовые данные между -10 38
1 и 10 3 8 - 1.
Тип данных decimal (десятичный) или numeric (числовой) — точный тип данных, потому что он размещается наподобие символов (как если бы данные имели только 12 символов, включающие цифры от 0 до 9, минус и десятичную точку). Способ, которым он (тип) размещается, предотвращает от неточности, которую мы увидим немного позже в типах чисел с плавающей точкой (float) и вещественных (real) числах. Это, однако, приводит к дополнительным затратам при получении и управлении значениями. Чтобы определить десятичное число, мы должны задать точность и масштаб: •
Точность — общее количество значащих цифр в числе. Например, число 10 имеет точность 2, а 43.00000004 — точность 10. Точность может изменяться в пределах от 1 до 38.
•
Масштаб — возможное число значащих цифр, расположенных правее десятичной точки. Используя наш предыдущий пример, 10 имело бы масштаб 0, а 43.00000004 потребует масштаба 8.
Числовые типы данных ограничены точностью и масштабом, определяющими, насколько велики данные. Например, возьмем следующее объявление числовой переменной: Ц
DECLARE
@testvar
decimal(3,1)
Это позволит нам вводить любую числовую величину больше чем -99.94 и меньше чем 99.94. Ввод 99.949999 допустим, но ввод 99.95 — нет, потому что это будет округлено до 100.0, что не может быть отображено с помощью decimal (3, 1). Например, следующие операторы:
О
SELECT @testvar = -10.155555555 SELECT @testvar
возвращают -10.2. Когда язык программирования компьютера делает это для вас, операция рассматривается как неявное преобразование. Это является и благословением, и проклятием. Следует иметь в виду, что вы должны быть очень осторожны у границы допустимых значений типа данных. Обратите внимание, что имеется параметр настройки SET NUMERIC_ROUNDABORT ON (задание отбрасывания при округлении числовых данных), который вызовет ошибку, если произойдет потеря точности от неявного преобразования данных. Его весьма опасно использовать, так как приложения, использующие SQL Server, могут быть выброшены, если параметр включен. Однако если вы должны предотвратить неявное округление с помощью системных ограничений, это очень ценный инструмент. Хранение числовых типов зависит от того, какая требуется точность: Точность
Требуемое число байт
1-9
5
1 0 - 19
9
2 0 - 28
13
29-38
17
275
Глава 10 Что касается использования, то тип данных decimal должен вообще использоваться экономно. Нет ничего плохого с типом вообще, но он требует немного большей обработки, чем целые или вещественные данные, и, следовательно, имеется помеха в выполнении. Вы должны использовать этот тип, когда имеете определенные значения, которые желательно хранить, не теряя точности. Дополнительно с темой потери точности мы более подробно будем иметь дело в разделе "Приближенные числовые данные". Денежные величины Денежные типы данных — обычно целые числа с десятичной точкой, помещенной в число. Так как это всегда имеет одну и ту же точность и масштаб, процессор может работать с такой величиной подобно обычному целому числу, а затем механизм SQL Server может вставить десятичную точку назад, как только математические функции будут выполнены. Имеются две разновидности денежных переменных: •
money (денежный) — величины от -922 337 203 685 477.5808 до 922 337 203 685 477.5807 с точностью одной десятитысячной денежной единицы. Как можно было бы ожидать, это, очевидно, целое число из 64 бит и требует восьми байт хранения.
•
smallmoney (малый денежный) — величины от -214 748.3648 до +214 748.3647 с точностью одной десятитысячной денежной единицы. Это требует четырех байтов хранения.
Типы данных money и smallmoney — превосходное средство хранения для денежных величин практически с любыми требованиями, если только не требуется хранить наши денежные величины с более чем четырьмя десятичными знаками, что обычно не встречается в большинстве ситуаций. Столбцы идентификации Для типов данных i n t или decimal (с масштабом 0) мы можем создавать автоинкрементный столбец, чьи значения гарантированно будут уникальными для таблицы. Столбец, который реализует идентификацию, должен быть также определен как NOT NULL (не равен NULL). CREATE TABLE testldentity ( identityColumn int NOT NULL IDENTITY (1, 2), value varchar(lO) NOT NULL
I Здесь создается таблица testldentity (проверка идентификации), состоящая из двух столбцов: первого столбца identityColumn (столбец идентификации) типа int, элементы которого не могут принимать значение NULL и использующего функцию IDENTITY (идентификация), и второго столбца value (значение) типа varchar (10), элементы которого не могут принимать значение NULL. В этом операторе CREATE TABLE я добавил функцию IDENTITY для столбца identityColumn. Дополнительные значения в круглых скобках известны как начальное значение и приращение. Начальное значение 1 указывает, что мы начнем с первого значения, равного 1, а приращение говорит, что второе значение будет 3, далее 5 и т. д.
276
Планирование и реализация основной физической структуры В следующем фрагменте мы вставляем три новых строки в таблицу t e s t l d e n t i t y : INSERT VALUES INSERT VALUES INSERT
INTO testldentity ('один') INTO testldentity ('два') INTO testldentity
(value) (value) (value)
VALUES ('три') SELECT * FROM t e s t l d e n t i t y
Это дает следующий результат: identityColumn 1 3 5
value один два три
Свойство идентификации фантастически удачно для создания первичного ключа типа указателя, который является и небольшим, и быстрым. (Следует помнить и другое — он не должен быть единственным ключом таблицы, или мы фактически не будем иметь никакой уникальности, за исключением этого произвольного значения!) Тип данных i n t требует только четырех байтов и очень хорош для большинства таблиц, которые мы создаем, если они будут иметь меньше чем 2 миллиарда строк. Одно замечание. Идентифицирующие значения могут иметь пропуски в последовательности. Если происходит ошибка при создании новой строки, идентифицирующее значение, которое должно быть использовано, будет потеряно. Аналогично, если строка удаляется, удаленная величина не будет повторно использоваться. Следовательно, вы не должны использовать столбцы идентификации, если не можете допустить это ограничение на значения в вашей таблице. Если вы должны гарантировать уникальность по всем таблицам или даже нескольким БД, рассмотрите создание столбца с типом данных uniqueidentifier (уникальный идентификатор) и с отказом от автоматического формирования величин. Мы обсудим эту реализацию позже в разделе, посвященном типам данных.
Приближенные числовые данные Приближенные числовые значения содержат десятичную точку и размещаются в формате, которым можно быстро манипулировать, но они точны только до 15-го десятичного знака. Приближенные числовые значения имеют некоторые очень важные преимущества, как мы увидим позже в этой главе. Приближенный — неприятный термин, но фактически правильный. Он означает типы данных r e a l (вещественный) и f l o a t (с плавающей точкой), которые соответствуют стандарту IEEE 75454 величин с плавающей точкой одинарной и двойной точности. Причина, почему они называются величинами с плавающей точкой, связана со способом их хранения. В основном, число хранится как 32- или 64-битовая величина с четырьмя частями: •
знак — определяет, имеет ли оно положительное или отрицательное значение; 277
Глава 10 •
показатель — показатель мантиссы по основанию 2;
•
мантисса — хранит фактическое число, которое умножается на показатель;
•
смещение — определяет, положительным или отрицательным является показатель.
Полное описание того, как эти типы данных фактически сформированы — вне возможностей этой книги, но может быть получено из содержания IEEE Bwww.ieee.org для формального понимания. Имеются две разновидности типов данных с плавающей точкой, доступных в SQL Server: •
f l o a t [ (N) ] — величины в диапазоне от -1.79Е +308 до 1.79Е +308. Тип данных f l o a t позволяет нам определять конкретное число бит, используемых в мантиссе — от 1 до 53. Мы определяем это число бит величиной N.
•
r e a l — величины в диапазоне от -3.40Е +38 до 3.40Е +38. r e a l — синоним для типа данных f l o a t (24).
Размещение и точность для типов данных f l o a t следующие:
N (число бит мантиссы) 1 -=- 24 25 + 53
Точность
Размер для хранения
7
4 байта
1 5
8
б а й т
Следовательно, мы способны представить большинство значений от -1.79Е +308 до 1.79Е +308 с точностью 15 значащих цифр. Это — не так уж и много значащих цифр по сравнению с числовыми типами данных, но, несомненно, достаточно почти для любого научного приложения. Обратите внимание, что в предыдущем абзаце мы сказали "представить большинство значений". Это — проблема, которую мы должны понять. Имеются некоторые значения, которые просто не могут быть размещены в формате с плавающей точкой. Классический пример этого — значение 1/10. Нет никакого способа хранить это значение точно. Когда мы выполним следующий фрагмент кода: Щ
SELECT C A S T ( 1 AS f l o a t ( 5 3 ) )
/ C A S T ( 1 0 AS f l o a t ( 5 3 ) )
AS
funny_result
то ожидаем, что результат будет точно равен 0.1. Однако, выполнив оператор, получим: funny_result 0.10000000000000001 Даже если мы изменим ввод на: Щ
SELECT CAST(.l AS float) AS funny_result
мы получим тот же неправильный результат. Различие интересно, но несущественно для всех вычислений, кроме высокоточных. Типы данных f l o a t и r e a l — встроенные типы данных, с которыми можно работать, используя сопроцессор для вещественных чисел. Сегодня во всех процессорах этот сопроцессор — часть главного центрального процессора, и хотя это несколько более дорого, чем математика целых чисел, которая может быть 278
Планирование и реализация основной физической структуры
выполнена непосредственно в регистрах центрального процессора, но неизмеримо лучше, чем типы данных точных нецелых чисел. Недостаток их в том, как организовано хранение данных с плавающей точкой. Любое дальнейшее обсуждение недостатков размещения величин с плавающей точкой — вне возможностей этой книги. Приближенные числовые типы данных очень важны для хранения измерений, когда требуются вычисления. Происходящее округление несущественно, поскольку пока очень немного устройств, которые могут производить измерения с 15 значащими цифрами, и, поскольку вы будете, вероятно, делать большое количество математических вычислений с измеренными величинами, результат будет значительно лучше по сравнению с использованием десятичного типа. При кодировании значений с плавающей точкой вообще лучше всего стараться избегать сравнивать эти значения, используя для сравнения операцию =. Поскольку значения приблизительны, не всегда возможно ввести точное значение, которое было размещено. Если вам нужно искать точные значения, то лучше использовать числовые типы данных.
Дата и время-дата Если вы вспомните, мы не первый раз обсуждаем типы данных d a t e t i m e и s m a l l d a t e t i m e . Первый раз мы столкнулись с ними при обсуждении нарушений 1НФ. В этом разделе мы рассмотрим характеристики типов d a t e t i m e , а затем обсудим некоторые альтернативы. smalldatetime Этот тип (укороченная дата-время) представляет данные даты и времени с 1 января 1900 года до 6 июня 2079 года. Тип s m a l l d a t e t i m e имеет точность в одну минуту. Это требует четырех байтов хранения. Тип s m a l l d a t e t i m e — лучший выбор, когда вы должны хранить дату и, возможно, время некоторого события, где точность в минуту — не проблема. Если требуется более высокая точность, то, вероятно, лучше выбрать тип d a t e t i m e (дата-время). datetime Этот тип представляет данные даты и времени с 1 января 1753 года по 31 декабря 9999 года. Размещение типов данных s m a l l d a t e t i m e и d a t e t i m e интересно. Тип d a t e t i m e размещается в два 4-байтовых целых числа. Первое целое число хранит число дней до или после 1 января 1990 года. Второе хранит число миллисекунд после полуночи конкретного дня. Значения s m a l l d a t e t i m e размещаются сходным образом, но используют 2-байтовые целые числа вместо 4-байтовых. Первое — 2-байтовое целое число без знака, а второе используется, чтобы хранить число минут после полуночи. Тип d a t e t i m e имеет точность 0.03 секунды. Использование восьми байт, однако, требует значительного объема памяти. Используйте d a t e t i m e или когда вам нужен расширенный диапазон, или когда вам нужна высокая точность. Редко бывает, когда вам нужна такая точность. Единственное приложение, которое приходит на ум — столбец временных меток (не путайте с типом данных timestamp, который мы обсудим в одном из последующих разделов этой главы), который используется, чтобы точно обозначить время операции. Нет ничего удивительного, если мы хотим получить информацию о времени, например, время в секундах, прошедшее между двумя действиями, или если мы хотим использовать значение datetime в механизме управления параллельным выполнением операций. Мы рассмотрим глубже тему управления параллельным выполнением операций позже в этой главе.
279
Глава 10 Использование определяемых пользователем типов данных для управления датами и временами Одно слово относительно хранения значений даты и времени. Использование типов данных datetime, когда вам нужна часть, связанная только с датой, или только со временем, — сплошные мучения. Возьмем следующий пример кода. Мы хотим найти каждую запись, где дата равна 12 июля 1967 года. Если мы закодируем это самым очевидным образом: SELECT * FROM employee WHERE birthDate - 'July 12, 19 67'
I
Здесь employee — служащий, b i r t h D a t e — дата рождения. Мы получим совпадение для каждого служащего, где b i r t h D a t e точно соответствует "Июль 12, 1967 00:00:00.000". Однако если дата имеет вид "Июль 12, 1967 10:05:23.300", как это могло бы быть в соответствии с проектом (то есть записано точное время рождения), или по ошибке (то есть, управление датой могло послать текущее время по умолчанию, если мы забыли очистить это) — мы можем замучиться необходимостью писать запросы подобно следующему, чтобы ответить на вопрос, кто был рожден 12 июля. Чтобы сделать это, мы были бы должны переписать наш запрос следующим образом:
I
SELECT * FROM employee WHERE birthDate >- 'July 12, 1967 0:00:00.000' AND birthDate < 'July 13, 1967 0:00:00.000'
Заметьте, что мы не использовали оператор BETWEEN в этой операции. Если бы мы хотели использовать этот оператор, то получили бы:
I
WHERE birthDate BETWEEN 'July 12, 1967 AND 'July 12, 1967 23:59:59.997'
0:00:00.00'
... сначала исключив всякие даты 13 июля, а затем избегая любые округления типа данных datetime. Так как точность — 0.03 секунды, то когда будет оцениваться величина 'July 12, 1967 23:59:59.997', она будет округлена до 'July 13, 1967 0:00:00.00'. Все это не только неприятно и тяжело, но и может быть неэффективно. В случае, где нам не нужна часть типа данных datetime, связанная с датой, или часть, связанная со временем, лучше всего подойдет создание собственного типа данных. Например, возьмем типичный случай хранения времени, когда кое-что происходит, без учета даты. Имеются две возможности разобраться с этой ситуацией. Обе — намного лучше, чем просто использование типа данных d a t e t i m e , но каждая имеет свой собственный набор проблем. Q
280
Использование нескольких столбцов — каждый просто для часов, минут, секунд и отдельных долей секунд, если это вам нужно. Проблема здесь состоит в том, что не просто сформировать запрос. Вам нужно, по крайней мере, три столбца, чтобы запросить нужное время, и вам потребуется проиндексировать все столбцы, используемые в запросе, чтобы получить хорошее выполнение на больших таблицах. Размещение будет лучше, поскольку мы можем использовать для отдельных столбцов тип t i n y i n t . Если вы хотите посмотреть на то, что происходило в течение десятого часа дня в течение недели, то вы могли бы использовать этот метод.
Планирование и реализация основной физической структуры •
Использование единственного столбца, который содержит число секунд (или частей секунд) между полуночью и текущим временем. Однако в этом случае нельзя сформировать разумный запрос по всем значениям, которые возникают в течение, скажем, десятого часа, и если бы мы определили число секунд, это было бы всякое значение между 36000 и 39600. Однако это идеально, если мы используем данные в некотором виде внутренней процедуры, с которой пользователи-люди не должны взаимодействовать.
При использовании этих столбцов мы могли бы создать определяемые пользователем функции, чтобы преобразовывать наши типы данных к удобочитаемым значениям. В качестве другого примера мы могли бы создать наш собственный тип даты, который просто реализован как целое число. Мы могли бы добавить определяемый пользователем тип, названный intDateType (большее освещение этой темы будет дано позже в этой главе): EXEC sp_addtype Stypename = intDateType, @phystype = i n t e g e r , @NULLtype = 'NULL', @owner = ' d b o ' GO
И затем формируется определяемая пользователем функция для преобразования значений переменной с нашими данными в реальную переменную типа d a t e t i m e , чтобы использовать в вычислениях даты или просто, чтобы показать клиенту. CREATE FUNCTION i n t D a t e T y p e $ c o n v e r t T o D a t e t i m e ( @dateTime intDateType ) RETURNS d a t e t i m e AS BEGIN RETURN(dateadd(day, @ d a t e t i m e , ' j a n 1, 2 0 0 1 ' ) ) END GO
Из этого основного примера должно быть довольно очевидно, что определяемая пользователем функция будет чрезвычайно важной частью нашего кодирования приложений SQL Server. Мы будем использовать ее несколько раз в этой главе, чтобы иллюстрировать то или иное положение, но мы рассмотрим ее более полно в следующей главе, где будем ее использовать для защиты наших данных. Мы увидим ее снова в главе 11, когда будем управлять данными. В данный момент просто важно обратить внимание, что эти особенности существуют. Чтобы проверить нашу новую функцию, мы могли бы начать с просмотра второго дня года, преобразовывая дату в переменную типа d a t e t i m e : §Ц SELECT d b o . i n t D a t e T y p e $ c o n v e r t T o D a t e t i m e ( 1 ) AS c o n v e r t e d V a l u e
что возвращает: convertedValue 2001-01-02 00:00:00.000 281
Глава 10 Мы должны были бы построить несколько других функций ввода данных, чтобы взять переменную типа d a t e t i m e и разместить ее как новый тип данных. Хотя мы преднамеренно не рассматривали многие детали определяемых пользователем функций, я предложил диапазон возможностей решения проблемы даты или времени. Тип данных d a t e t i m e представляет проблемы для программистов, с которыми вы должны иметь дело. Только будьте осторожны.
Двоичные данные Двоичные данные позволяют нам размещать последовательность бит с диапазоном от одного байта (восемь бит) до примерно двух гигабайт бит (величина 2г - 1 или 2 147 483 647 байт). Концепция, которую мы начнем рассматривать в связи с двоичными типами данных — использование данных переменной длины. В основном это означает, что размер хранилища данных для каждого экземпляра столбца или переменной этого типа данных может изменяться в зависимости от того, сколько данных размещается. Во всех предыдущих типах данных хранилище было фиксировано, потому что размещались представления величин фиксированной длины, которые интерпретировались, используя некоторую формулу (например, целое число в 32 бита или дата — число единиц времени, начиная с некоторого момента времени). Любой тип данных, который допускает значения переменной длины, обычно связан с вопросом размещения значений вместе, подобно буквам в слове. Данные переменной длины обычно включают две части — значение, определяющее, какова длина данных, и данные сами по себе. Имеется некоторый верхний предел для размещения данных переменной длины, но это обычно лучше, чем размещение данных в столбцах фиксированной длины со значительным потраченным впустую местом. Меньшие данные означают большее количество записей на страницу, что уменьшает время ввода/вывода, а операции ввода/вывода гораздо более дорогостоящи, чем расположение полей в записи. Двоичные данные размещаются в трех различных вариантах: •
b i n a r y (двоичный) — данные фиксированной длины с максимальной длиной 8000 байт. Используйте только тип binary, если вы знаете, насколько велики будут ваши данные. Хранилище будет эквивалентно числу байт, которое мы объявим для переменной.
•
v a r b i n a r y (переменный двоичный) — для двоичных данных с переменной длиной и максимальной длиной 8000 байт.
•
image (изображение) — для действительно больших данных изображений 3 переменной длины с максимальной длиной 2 - 1 (2 1.47 483 647) байт.
Одно из ограничений двоичных типов данных — то, что они не поддерживают побитовые операторы, которые позволили бы нам создавать конкретное очень мощное хранилище битовой маски, позволяющее сравнивать два двоичных столбца, чтобы видеть не только то, что они отличаются, но и как они отличаются. Общая идея двоичных типов данных состоит в том, что они хранят последовательности бит. Побитовые операторы могут работать с целыми числами, которые физически размещаются как биты. Причина этой несогласованности фактически довольно ясна с точки зрения внутреннего процессора запросов. Побитовые действия — фактически действия, которые выполняются в процессоре, в то время как двоичные типы данных — специфические особенности SQL Server.
282
Планирование и реализация основной физической структуры Дополнительная концепция, которую мы должны обсудить — то, как SQL Server хранит большие объемы данных, таких как тип image, обычно называемый как BLOB (Binary Large Objects) — большие двоичные объекты. Когда тип данных должен хранить данные, которые являются потенциально больше по размеру, чем страница SQL Server, требуется специальная обработка. Вместо хранения фактических данных на странице с записью, размещается лишь указатель на другое физическое место памяти. Указатель — 16-байтовая двоичная величина (тот же самый рассмотренный нами тип данных), которая указывает на другую страницу в БД. Страница может хранить значение одного или нескольких BLOB, и BLOB может занимать много страниц. Использование полей binary, особенно полей image, довольно ограничено. В основном они могут применяться для хранения любых двоичных значений, с которыми SQL Server не работает. Мы можем хранить текст, изображения в формате JPEG и GIF (JPEG — стандартный алгоритм сжатия изображений, GIF — формат графического обмена) и даже документы Word и электронные таблицы Excel. Так почему использование ограничено? Простой ответ — функционирование. Вообще-то плохо использовать тип данных image, чтобы хранить очень большие файлы, потому что их долго восстанавливать из БД. Когда нужно хранить данные изображения в БД, мы будем обычно хранить имя файла совместного доступа. Программа доступа будет просто использовать имя файла, чтобы обратиться к внешнему хранилищу файла. Файловые системы создаются именно для хранения и обслуживания файлов, так что мы используем их по прямому назначению.
Строки символов Большинство данных, размещаемых в SQL Server, использует символьные типы данных. Действительно, обычно достаточно большое количество данных размещается в символьных типах данных. Часто символьные поля используются, чтобы хранить несимвольные данные типа чисел и дат. Хотя это фактически и допустимо, но это — не идеал. Для систем инициализации, чтобы хранить число с восемью цифрами в символьной строке, требуется, по крайней мере, восемь байт, в то время как целое число требует четырех байтов. Поиск на целых числах гораздо легче, потому что 1 всегда предшествует 2, в то время как в символьной строке 11 предшествует 2. Кроме того, целые числа размещаются в формате, которым можно управлять, используя внутренние функции процессора в противоположность специальным функциям SQL Server для работы с данными. char Тип данных char (символьный) используется для символьных данных фиксированной длины. Вы должны выбрать размер данных, которые желаете хранить. Каждое значение будет размещено в одно и то же число символов, максимум до 8060 байт. Хранилище — точное число байт в соответствии с определением столбца независимо от фактически размещенных данных; любое остающееся место справа от последнего символа заполняется пробелом. Обратите внимание что в версиях SQL Server, ранее чем 7.0, реализация столбцов char отличается. Установка ключа ANSI_PADDING точно определяет, как это фактически происходит. Если этот ключ установлен, таблица точно такая, как мы описали; если нет, данные будут размещаться так, как мы рассмотрим в разделе ниже, посвященном типу varchar. Обычно лучше задавать установку набора значений ANSI для большей стандартизации (или даже большего ограничения), поскольку эти установки помогут вам избежать проблем с модернизацией SQL Server в вашем кодировании, которые имеются при переходе от версии к версии. 283
Глава 10 Максимальный предел для типа char — 8060 байтов, но очень маловероятно, что вы когда-либо доберетесь до этого предела. Для символьных данных, где ожидается большее число символов, вы использовали бы один из других символьных типов. Тип данных char следует использовать только в случаях, где гарантируется одно и то же число символов в каждой строке, и каждая строка не содержит значение NULL. По этой причине мы используем этот тип очень редко, так как для большинства данных не справедливо соглашение о равной длине. Имеется один пример, где тип char мог бы использоваться довольно часто — это идентификационные числа. Они могут содержать смесь алфавитных и числовых символов типа номеров транспортных средств (VIN — Vehicle Identification Number). Обратите внимание, что это — фактически составной атрибут, поскольку вы можете определить многое относительно автомобиля по его номеру. Другой пример, где поле char обычно используется — номера социального обеспечения. varchar Для типа данных v a r c h a r (переменная строка символов) вы выбираете максимальную длину данных, которые желаете разместить, — до 8000 байтов. Тип данных v a r c h a r гораздо более полезен, чем char, поскольку данные не должны иметь одну и ту же длину, и SQL Server не дополняет излишки памяти пробелами. Однако имеются некоторые дополнительные издержки в хранении данных переменной длины. Используйте тип данных varchar, когда ваши данные обычно небольшие, скажем, несколько сотен байт или меньше. Положительный момент относительно столбцов varchar состоит в том, что независимо от того, насколько большим вы задаете максимум, размещенные данные — это текст в столбце плюс немного дополнительных байт, которые определяют длину данных. Мы будем вообще стараться выбирать максимальный предел для нашего типа данных, который является подходящим значением, достаточно большим, чтобы обеспечить большинство ситуаций, но не слишком большим, чтобы быть непрактичным для наших приложений и отчетов. Например, возьмем имена людей. Они, очевидно, требуют типа varchar, но какой длины мы должны позволить быть данным? Имена обычно имеют максимум пятнадцать символов, хотя вы могли бы определить двадцать или тридцать символов для маловероятных исключений. Данные v a r c h a r — наиболее распространенный тип размещения для неключевых значений, который мы будем использовать. text Тип данных t e x t (текст) используется, чтобы размещать большое количество символьных данных. Он может хранить максимум 2 147 483 647 (2 - 1) символов, или 2 Гб. Размещение типа t e x t во многом подобно типу данных image. Однако дополнительная особенность позволяет нам обходиться без указателя и размещать данные t e x t непосредственно на странице. Это возможно в SQL Server, если выполняются следующие условия: •
величина столбца меньше, чем 7000 байт;
•
данные во всей записи не будут превышать одной страницы данных;
•
опция таблицы Text In Row установлена, используя процедуру s p _ t a b l e o p t i o n , примерно следующим образом:
§§ EXECUTE s p _ t a b l e o p t i o n ' T a b l e N a m e ' , ' t e x t 284
in row',
'1000'
Планирование и реализация основной физической структуры В этом случае любые столбцы текста в таблице TableName будут хранить свой текст со строкой вместо выделения их в другие страницы, если только столбец не достигает 1000 символов или полная строка не слишком велика, чтобы уместиться на странице. Если строка становится слишком большой, чтобы уместиться на странице, один или большее количество столбцов текста будет удалено из строки и помещено на отдельных страницах. Эта установка позволяет нам использовать значения текста разумным образом, так, что когда размещаемое значение маленькое, функционирование в основном то же самое, что и для столбца varchar. Следовательно, мы можем теперь использовать тип данных t e x t , чтобы иметь символьные данные, где длина может изменяться от десяти символов до 50 Кб текста. Это очень важная особенность для столбцов типа примечаний, когда пользователи хотят иметь место для неограниченных текстовых примечаний, но часто помещают только десять или двадцать символов в каждой строке. Другая проблема с полями t e x t — то, что они не могут быть индексированы, используя обычные методы индексации. Как рассмотрено в предыдущей главе, если вы должны выполнять сложный поиск в текстовых столбцах, то можете использовать возможности полнотекстового поиска в SQL Server 2000. Следует также отметить, что даже если бы мы могли индексировать текстовые столбцы, это вряд ли была хорошая идея. Так как столбцы типа t e x t или даже большие столбцы типа v a r c h a r могут быть настолько большие, то невозможно их использовать для индексов, имеющих предел в 900 байт. Строки символов Unicode До сих пор типы символьных данных, которые мы обсудили, касались хранения обычных ASCII-данных. В SQL Server 7.0 (и NT 4.0) фирма Microsoft реализовала новый стандартный формат символов, называемый Unicode. Он определяет 16-битовый формат символов, который может хранить символы не только латинского алфавита. В ASCII-системе символов из семи бит (с восемью битами для латинских расширений) мы были ограничены 256 различными символами, которых было достаточно для большинства англоговорящих людей, но недостаточно для других языков. Восточные языки имеют символ для каждого отдельного слога и эти символы не являются алфавитными; языки Среднего Востока используют несколько различных символов для одной и той же буквы в зависимости от ее положения в слове. По этой причине был создан 16-битовый стандарт символов, позволяющий нам иметь 65536 различных символов. Для этих данных мы имеем типы данных nchar, nvarchar и ntext. Они — точно те же самые, что и типы, имеющие подобные названия (без п), которые мы уже описали, если бы не одна вещь: использование Unicode удваивает число байт для хранения информации, так что требуется вдвое больше места; это уменьшает наполовину число символов, которые могут быть размещены. Один небольшой совет. Если вы хотите определить значение Unicode в строке, вы добавляете символ N к началу строки, наподобие следующего: Ц SELECT N'Unicode Value'
Вариантные данные Вариантный тип данных позволяет вам хранить почти любой тип данных, который был рассмотрен. Это позволяет нам создавать столбец или переменную, где мы не знаем точно, какие данные будут размещены. Это новая особенность SQL Server 2000. Название нового типа данных — s q l _ v a r i a n t (вариантный тип для SQL), и он позволяет нам хранить значения любого поддерживаемого SQL Server типа данных, кроме t e x t , n t e x t , timestamp и s q l _ v a r i a n t (может показаться странным, что вы не можете хранить вариантный тип в вариантном типе, но все это говорит о том, что тип данных s q l _ v a r i a n t фактически не существует, a SQL Server выбирает лучший тип хранения для размещения вашего значения). 285
Глава 10 Преимущества вариантного типа Тип данных s q l _ v a r i a n t позволит нам создавать определяемые пользователем таблицы типа "набора свойств", которые помогут нам избежать очень длинных таблиц со многими столбцами, которые могут быть, а могут и не быть заполнены. Возьмем таблицу e n t i t y P r o p e r t y (свойство сущности) в следующем примере: entity entityld: int Attribute!: varchar(20) Attribute2: varchar(20) AttributeN: varchar(20)
entityProperty entityProperty Id: int entityld; int propertyValuel: int propertyValue2: datetime propertyValue3: varbinary(6) propertyValue4: varchar(60) propertyValue5: varchar(2000) propertyValue6: varchar(30) propertyValue7: nchar(20)
i |
|
propertyValueN: varchar(30) Здесь entity — таблица "сущность"; entityld — идентификатор сущности; AttributeN— N-й атрибут; entityProperty — таблица "свойство сущности"; entityPropertyld — идентификатор свойства сущности; propertyValueN — N-e значение свойства. В этом примере мы имеем N столбцов с N типами данных, которые могут принимать значения NULL, что позволяет пользователю хранить любое из этих (или несколько) значений. Этот тип таблицы обычно называется разреженной таблицей. Проблема с этим примером состоит в том, что, если пользователь придумывает новое свойство, мы будем вынуждены изменить таблицу, пользовательский интерфейс и любые программы, которые связаны со столбцами таблицы. С другой стороны, если мы реализуем таблицу следующим образом: entity entityld: int Attribute!: varchar(20) Attribute2: varchar(20) AttributeN: varchar(20)
entityProperty entityPropertyld: int entityPropertyTypeld: int entityld: int propertyValue: sqLvariant •
entityPropertyType entityPropertyTypeld: int name: varchar(20) Здесь дополнительно entityPropertyType — таблица "тип свойства сущности"; entityPropertyTypeld — идентификатор типа свойства сущности; name — название типа свойства сущности.
286
Планирование и реализация основной физической структуры ... то каждое из свойств, которое мы реализовали в предыдущем примере, добавляя столбец в таблицу e n t i t y P r o p e r t y , будет теперь добавлено как экземпляр в таблице e n t i t y P r o p e r t y T y p e , и значение может быть размещено в столбцеpropertyValue. Какой бы тип данных ни понадобился для свойства, он будет размещен как s q l _ v a r i a n t . Таблица e n t i t y P r o p e r t y T y p e могла бы быть расширена для включения многих других свойств, при этом пользователю не нужно производить серьезные изменения в БД. И если мы реализуем наше решение формирования отчетов таким способом, чтобы наши новые отчеты знали относительно любых изменений, нас не будут смущать никакие новые свойства. Недостатки вариантного типа Если данные размещены в столбце типа s q l _ v a r i a n t , ими нелегко управлять. Я оставлю это читателю, который может полностью прочитать информацию в SQL Server Books Online относительно вариантных данных. Проблемы возникают в следующих случаях: •
Назначение данных из столбца типа s q l _ v a r i a n t в более строгий тип данных — мы должны быть очень осторожны, поскольку правила перевода переменной из одного типа данных в другой сложны и могут вызвать ошибки, если данные не могут быть преобразованы. Например, величина типа varchar(lO) 'He дата' не может быть преобразована к типу данных d a t e t i m e . Такие проблемы действительно станут актуальны, когда вы начинаете восстанавливать вариантные данные из типа данных s q l _ v a r i a n t и пытаться управлять ими.
•
Считается, что величины NULL типа s q l _ v a r i a n t не имеют никакого типа данных — следовательно, вы должны будете обращаться со значениями NULL в s q l _ v a r i a n t по-разному для разных типов данных.
•
Сравнения вариантных величин с другими типами данных могут вызвать ошибки программирования, потому что если только одна из величин имеет тип s q l _ v a r i a n t , компилятор будет знать, что вы запускаете оператор с двумя несравнимыми типами данных, например, @intVar = @varcharVar. Однако если эти две переменные были определены как sql__variant, и типы данных не соответствуют, то значения не будут соответствовать из-за несовместимости типов данных.
Имеется функция, которая определяет тип значения, находящегося в вариантном столбце:
I
DECLARE @varcharVariant s q l _ v a r i a n t SET @varcharVariant = '1234567890' SELECT @varcharVariant AS varcharVariant, SQL_VARIANT_PROPERTY(@varcharVariant, 'BaseType') as baseType, SQL VARIANT PROPERTY(@varcharVariant, 'MaxLength') as maxLength Здесь определяется переменная varcharVariant вариантного типа, в которую помещается строка ' 1234567890', после чего определяется содержимое переменной varcharVariant, его базовый тип (baseType) и максимальный размер (maxLength), используя функцию SQL_VARIANT_PROPERTY. Этот запрос возвращает: varcharVariant
baseType
maxLength
1234567890
varchar
10 287
Глава 10 Другие типы данных Следующие типы данных несколько менее часты, но они все же очень полезны timestamp
(или
rowversion)
Тип данных timestamp (время создания/обновления) или rowversion (версия строки) — уникальное число в рамках БД. Когда вы имеете столбец типа timestamp, значения его изменяются с каждой корректировкой каждой строки. Гарантируется, что значения в столбцах типа timestamp будут уникальными по всем таблицам. Он имеет довольно странное название, поскольку не имеет никакого отношения ко времени, это просто уникальное значение, чтобы сообщить вам, что ваша строка изменилась. Столбец таблицы типа timestamp (вы можете иметь только один столбец) обычно используется как данные для механизма "оптимистической блокировки". Мы обсудим это позже в данной главе, когда будем рассматривать служебные столбцы. Тип данных timestamp — сомнительное благо. Он размещается в восьмибайтовой величине типа v a r b i n a r y . С двоичными значениями не всегда легко иметь дело, и они зависят от того, какой механизм вы используете для доступа к вашим данным. При использовании ODBC или OLE DB и ADO они могут быть на первых порах коварными. Это связано с тем, что они являются двоичными значениями, и для восстановления их вы должны использовать механизмы формирования фрагментов BLOB-данных. SET NOCOUNT ON CREATE TABLE t e s t T i m e s t a m p ( v a l u e v a r c h a r ( 2 0 ) NOT NULL a u t o _ r v t i m e s t a m p NOT NULL ) INSERT INTO t e s t T i m e s t a m p
(value)
values
('Ввод')
SELECT v a l u e , a u t o _ r v FROM t e s t T i m e s t a m p UPDATE t e s t T i m e s t a m p SET v a l u e = ' П е р в о е и з м е н е н и е ' SELECT v a l u e , auto__rv from t e s t T i m e s t a m p UPDATE t e s t T i m e s t a m p SET v a l u e = ' П о с л е д н е е и з м е н е н и е ' SELECT v a l u e ,
a u t o _ r v FROM t e s t T i m e s t a m p
Здесь создается таблица testTimestamp (проверка timestamp), содержащая два столбца: value (значение) типа varchar (20) и auto_rv (автоматическая фиксация изменения строки) типа timestamp. Далее в столбец value помещаются поочередно значения'Ввод', 'Первое изменение' и 'Последнее изменение', а в промежутках между вводом данных проверяется содержимое столбцов value и auto rv.
288
Планирование и реализация основной физической структуры Возвращаются следующие значения: value
auto_rv
Ввод
0x0000000000000089
Value
auto_rv
Первое изменение
0х000000000000008А
Value
auto_rv
Последнее изменение
0х000000000000008В
Мы не трогали переменную auto_rv, и все же она дважды увеличилась. Однако вы не можете рассчитывать, что значение переменной типа timestamp будет последовательным, поскольку корректировки других таблиц будут изменять это значение. Лучше также не считать, что величина является числом изменений, выполненных вашим кодом. То, как конкретно реализован тип timestamp, — деталь, которая может быть изменена в будущем (из SQL Server 2000 Books Online: "Определение timestamp в будущих версиях SQL Server будет изменено, чтобы совместить с определением timestamp в SQL-99."). Если будет найден лучший метод задания уникальной величины в пределах БД, который окажется хоть на волос быстрее, он будет, вероятно, использоваться. Вы можете создавать переменные типа timestamp для получения значения timestamp, и можете восстановить последнее используемое значение timestamp через глобальную переменную @@dbts. Тема "оптимистической блокировки" будет рассмотрена более детально в главе 12. uniqueidentif ier Глобальный уникальный идентификатор (GUID — Globally Unique IDentifier) быстро становится оплотом вычислений Microsoft. Название говорит, что все они глобально уникальны. Учитывая то, как глобальные уникальные идентификаторы формируются, имеется чрезвычайно малый шанс, что когда-либо будет дублирование их значений. Они создаются с помощью формулы, которая включает использование идентификационного номера сетевой карты (если он существует), текущей даты и времени, уникального числа таймера центрального процессора и некоторого дополнительного "магического числа". В SQL Server 2000 u n i q u e i d e n t i f i e r (уникальный идентификатор) имеет очень важную цель. Когда вам нужно иметь уникальный ключ, который гарантирует, что будет уникальным во всех БД и сервере, то, например: DECLARE SguidVar uniqueidentifier SET @guidVar = new()
I
SELECT @guidVar as guidVar
Здесь задается переменная @guidVar типа uniqueidentif значение, а потом это значение проверяется.
ier, которой присваивается
289
Глава 10 ... возвращает значение: guidVar 6C7119D5-D48F-475C-8B60-50D0C41B6EBF
Они фактически хранятся как шестнадцатибайтовые двоичные значения. Обратите внимание, что это не точно непосредственно шестнадцатибайтовое двоичное значение. Вы не можете помещать просто любое двоичное значение в столбецuniqueidentif i e r , поскольку значение должно удовлетворять критериям генерации, которые не задокументированы по очевидным причинам. Если вы должны создать автогенерирующий столбец THnauniqueidentif i e r , имеется свойство, которое вы можете устанавливать в операторе создания таблицы (или изменить таблицу подобным же образом). Это — свойство rowguidcol (заполнение столбца глобальных идентификаторов для строк) и оно используется так: CREATE TABLE guidPrimaryKey ( guidPrimaryKeyld u n i q u e i d e n t i f i e r NOT NULL r o w g u i d c o l DEFAULT n e w l d ( ) , value varchar(lO) )
Здесь создается таблица guidPrimaryKey (первичный ключ в виде глобального уникального идентификатора), в которой формируются столбцы: guidPrimaryKeyld (идентификатор первичного ключа в виде глобального уникального идентификатора) типа uniqueidentif ier и value (значение) типа varchar (10). Мы ввели здесь пару новых моментов — свойство rowguidcol и значения по умолчанию, которые мы рассмотрим должным образом в следующей главе. Пока достаточно сказать, что если вы не формируете значение столбца в операции INSERT, операция по умолчанию сделает это. В нашем случае мы используем функцию newld (), чтобы получить новый u n i q u e i d e n t i f i e r . Так что, когда мы выполняем следующий оператор INSERT:
I
INSERT INTO g u i d P r i m a r y K e y ( v a l u e ) VALUES ( ' T e s t ' )
Здесь вводится только значение Test в столбец value. ... а затем выполняем следующий оператор, чтобы посмотреть введенные данные: SELECT *
I
FROM g u i d P r i m a r y K e y
... то получим guidPrimaryKeyld
value
8A57C6CD-7407-47C5-AC2F-E6A884C7B646 Test Свойство столбца rowguidcol, сформированное с пометкой u n i q u e i d e n t i f i e r , уведомляет систему, что оно аналогично значению столбца идентификаторов для таблицы —
290
Планирование и реализация основной физической структуры уникальный указатель на строку таблицы. Обратите внимание, что ни идентификатор, ни свойство rowguidcol не гарантируют уникальность. Чтобы обеспечить такую гарантию, мы должны реализовать их использование с уникальными ограничениями, которые рассмотрим позже в этой главе. Казалось бы, что u n i q u e i d e n t i f i e r будет лучшим способом реализации первичных ключей, так как, когда такие величины создаются, они уникальны по всем БД, серверам и платформам. Однако имеются две основные причины, почему мы не будем использовать столбец u n i q u e i d e n t i f i e r , чтобы реализовать все наши первичные ключи: •
Требования размещения — поскольку они размером в шестнадцать байт, то значительно больше по объему, чем обычное поле целого числа.
•
Возможность ввода — так как текстовая версия глобального уникального идентификатора содержит 36 символов, очень трудно поместить его значение в запрос, и нелегко его ввести.
cursor c u r s o r (курсор) — механизм, который позволяет выполнять действия над конкретной строкой вместо обычного способа работы с набором строк. Тип данных c u r s o r используется, чтобы хранить ссылку на курсор SQL Server в языке T-SQL. Вы не можете использовать тип данных c u r s o r как тип столбца в таблице. Их единственное использование — в коде T-SQL, чтобы хранить ссылку на курсор. Большее количество информации относительно курсоров см. в "Professional SQL Server 2000 Programming" Роба Виейра (Rob Vieira) (Wrox Press, ISBN 1-861004-48-6). table Тип данных t a b l e (таблица) несколько похож на тип данных cursor, но содержит ссылку на результирующий набор. Название типа данных на самом деле довольно неудачное, поскольку оно вынуждает функционального программиста думать, что он может хранить указатель на таблицу. На самом деле он используется, чтобы хранить набор результатов как временную таблицу. Фактически t a b l e подобен временной таблице при реализации. Следующий пример показывает синтаксис, необходимый для использования типа переменной t a b l e . DECLARE @tableVar TABLE ( id int IDENTITY, value varchar(lOO) ) INSERT INTO @tableVar (value) VALUES ('Это свежий тест') SELECT id, value FROM @tableVar
Здесь создается объект типа table с именем @tableVar (переменная-таблица) и столбцами id (идентификатор) типа int — автоинкрементный и value (значение) типа varchar (100). Затем в столбец value помещается значение 'Это свежий тест. Далее анализируется полученный в столбцах id и value результат. 291
Глава 10
Запрос возвращает следующее id
value
1
Это свежий тест
Как и с типом данных cursor, вы не можете использовать тип данных t a b l e как тип столбца в таблице, и он может только использоваться в коде T-SQL для указания на результирующий набор. Его основная цель — получить таблицу из определяемой пользователем функции, как в следующем примере: CREATE FUNCTION table$testFunction ( @returnValue varchar(100) ) RETURNS @tableVar table ( value varchar(100) ) AS BEGIN INSERT INTO @tableVar (value) VALUES (SreturnValue) RETURN END
Здесь создается функция table$testFunction (функция тестирования таблицы), которая возвращает величину типа varchar (100). Затем формируется объект QtableVar (переменная-таблица) типа table со столбцом value тоже типа varchar (100). В этот столбец помещается значение, сформированное функцией table$testFunction. После создания функции и таблицы можно использовать следующий синтаксис: I
SELECT * FROM dbo.table$testFunction('testValue')
... который вернет следующее: value testValue ТИПЫ
данных, определяемые пользователем
Мы коснулись этого ранее, когда обсуждали, как управлять типом данных d a t e t i m e . Основное назначение определяемых пользователем типов — позволить архитектору создать псевдонимы для часто используемых типов данных. В объявлении типа, определяемого пользователем, мы можем задать:
292
[_1
имя типа данных;
•
систему типа данных, включая информацию об их длине;
•
позволяет ли тип данных использовать значение NULL.
Планирование и реализация основной физической структуры Чтобы создавать определяемый пользователем тип для нашего общего серийного номера, мы могли бы сделать следующее:
I
EXEC sp_addtype @typename = @phystype = varchar(12), @NULLtype = 'not NULL', @owner = 'dbo' GO
serialNumber,
Здесь формируется новый тип данных с именем serialNumber (серийный номер), структурой, совпадающей с типом varchar (12), не допускающий использования значения NULL, владельцем которого является пользователь с именем dbo (см. выше). Когда этот тип будет применяться, мы можем изменить возможность использования значения NULL, определив это, но мы не можем изменить сам тип данных. Мы можем использовать этот тип подобно тому, как мы используем все типы данных SQL Server, как способ обеспечить детали реализации использования одного и того же типа. Мы можем объединить правила (чтобы ограничить значения, которые можно вводить при любом использовании) и значения по умолчанию (чтобы использовать значение по умолчанию, если конкретное значение не задано) для типа данных. Определяемые пользователем типы данных, поскольку они реализованы в SQL Server, имеют некоторые недостатки, которые делают их несколько неудобными для использования: •
сообщения об ошибках использования правил;
•
непоследовательное назначение переменных;
•
типы данных столбцов, определяемые пользователем, не будут изменяться, если изменится определяемый пользователем тип.
Сейчас мы рассмотрим эти проблемы. Сообщения об ошибках использования правил Можно создать определяемый пользователем тип данных, включающий правила и формирование ошибок. Создайте определяемый пользователем тип, создайте правило для него и свяжите это со столбцом. Теперь, когда создается таблица со столбцом, имеющим новый тип данных, правило будет использовано, как и ожидалось. Однако сообщения об ошибках, которые возникнут, ничего не стоят! Например, создадим очень полезный тип данных, который назовемsocialSecurityNumber, добавим правило и свяжем это со столбцом. - - создадим т и п , который н а з о в е м SSN типа v a r c h a r ( l l ) , имеющий р а з м е р - - номера с о ц и а л ь н о г о о б е с п е ч е н и я , включая символы тире EXEC s p _ a d d t y p e @typename - ' S S N ' , @phystype = ' v a r c h a r ( 1 1 ) ' , SNULLtype = ' n o t NULL' GO - - создадим п р а в и л о , к о т о р о е н а з о в е м SSNRule, со следующим шаблоном CREATE RULE SSNRule AS @value LIKE ' [0-9] [0-9] [ 0 - 9 ] - [ 0 - 9 ] [ 0 - 9 ] - [ 0 - 9 ] [0-9] [0-9] [ 0 - 9 ] ' GO
Продолжение кода на следующей странице
293
Глава 10 -- объединение правила с образцом типа EXEC sp_bindrule 'SSNRule', 'SSN' GO -- создание таблицы для проверки с новым типом данных CREATE TABLE testRule ( id int IDENTITY, socialSecurityNumber SSN ) GO
Здесь выполняются операции в соответствии с комментариями. Создаваемая таблица для проверки testRule (проверка правила) содержит два столбца: столбец id (идентификатор) автоинкрементного целого типа и socialSecurityNumber (номер социального обеспечения) типа SSN, созданного пользователем. Это создает нам таблицу со столбцом, который должен предотвратить ввод неправильного номера социального обеспечения. Так, в следующем фрагменте мы попробуем вставить данные в таблицу: -- ввод значений з таблицу INSERT INTO testRule (socialSecurityNumber) VALUES ('438-44-3343') INSERT INTO testRule (socialSecurityNumber) VALUES ('43B-43-2343') — заметьте, что В вместо 8 вызовет ошибку GO
Результат будет следующий: Server: Msg 513, Level 16, State 1, Line 1 A column insert or update conflicts with a rule imposed by a previous CREATE RULE statement. The statement was terminated. The conflict occured in database 'tempdb', table 'testRule', column 'socialSecurityNumber'. The statement has been terminated. (Сервер: сообщение 513, уровень 16, состояние 1, строка 1 Добавление или обновление столбца конфликтует с правилом, наложенным предыдущим оператором CREATE RULE. Оператор удален. Конфликт произошел в БД'tempdb', таблица 'testRule', столбец 'SocialSecurityNumber. Оператор удален.) Отсюда вы можете видеть, что сообщение об ошибке — бесполезно, поскольку оно даже не сообщает нам, какое правило было нарушено. Обратите внимание, что при нашем тестировании мы включаем опцию NOCOUNT (не вычислять), отключающую все соединения. Это позволит избежать сообщений, которые говорят нам, сколько строк изменено операцией. В данном случае это также сделано для более наглядного примера. Мы можем сделать это в коде, задав SET NOCOUNT ON (включить NOCOUNT).
294
Планирование и реализация основной физической структуры Противоречивая работа с
переменными
Сначала мы рассмотрим обработку значений NULL. Когда мы в разделе Сообщения об ошибках использования правил создавали тип в коде, то установили для типа значение NOT NULL. Теперь мы выполним следующее:
I
DECLARE @SSNVar SSN SET-@SSNVar = NULL SELECT @SSNVar
Здесь формируется переменная QSSNVar (переменная для номера социального обеспечения) типа SSN. Затем в нее помещается значение NULL и анализируется результат. Этот код показывает одну из непоследовательностей определяемых пользователем типов данных. При выполнении оператора мы могли бы ожидать, что произойдет ошибка, так как тип не допускает значение NULL. Однако мы получим следующее: SSNVar NULL Это, конечно, несущественно, но я предпочел бы, чтобы определяемый пользователем тип данных не позволял вводить значение NULL. Другая проблема, связанная с определяемыми пользователем типами данных, возникает, когда созданная переменная этого типа не удовлетворяет правилу, которое связано с этим типом. Тем не менее, возможно задать значения переменной, определенной с использованием заданного пользователем типа данных, причем эти значения не удовлетворяют добавленному правилу, но они не могут быть помещены в столбец, определенный тем же самым типом. Здесь мы покажем этот эффект: DECLARE @SSNVar SSN SET @SSNVar = ' B i l l ' SELECT @SSNVar AS SSNVar INSERT INTO t e s t R u l e ( s o c i a l S e c u r i t y N u m b e r ) VALUES (@SSNVar)
Здесь задается переменная SSNVar типа SSN, и ей присваивается значение 'ВИГ, после чего анализируется полученный результат. Далее введенный результат помещается в столбец socialSecurityNumber таблицы testRule (обозначения те же, что и в предыдущих примерах). Этот код приводит к следующей ошибке: SSNVar Bill Server: Msg 513, Level 16, State 1, Line 5 A column insert or update conflicts with a rule imposed by a previous CREATE RULE statement. The statement was terminated. The conflict occured in database'tempdb', table 'testRule1, column 'socialSecurityNumber'. The statement has been terminated.
295
Глава 10 {Сервер: сообщение 513, уровень 16, состояние 1, строка 5 Добавление или обновление столбца конфликтует с правилом, наложенным предыдущим оператором CREATE RULE. Оператор удален. Конфликт произошел в БД 'tempdb', таблица 'testRule, столбец 'SocialSecurityNumber. Оператор удален.) В следующей главе мы рассмотрим более подробно защиту наших данных от недопустимых значений, а в данный момент мы только должны понять, почему не используем средств наподобие полезного инструмента инкапсуляции. Обратите внимание, что правила не являются стандартным способом обеспечения таких защит — таким являются проверки ограничений. Однако для определяемого пользователем типа, чтобы быть полезными для нас, они должны быть объектом сами по себе. Для любого, кто читает эту книгу, и кто действительно любит использовать определяемые пользователем типы, я думаю, было бы важно указать некоторые из наиболее важных проблем, связанных с ними. Правила в SQL Server 2000 выглядят как особенность обратной совместимости, и как таковые не могут быть обойдены в следующей версии, по причине требований покупателей сохранить некоторые особенности. Мы будем использовать проверки ограничений, поскольку они связаны непосредственно со столбцом, а не типом данных. ТИПЫ,
определяемые пользователем, не могут быть изменены,
если они используются Типы данных столбцов, которые заданы с помощью определяемых пользователем типов, не будут изменяться, если определяемый пользователем тип изменяется. Это — самая неприятная проблема. Предположим, что мы должны изменить определяемый пользователем тип данных, скажем с char (1) на v a r c h a r (10) по какой-либо причине. Нет никакого средства "изменить" определяемый пользователем тип, и мы не можем сделать это, если он уже используется. Следовательно, мы должны изменить тип данных в каждом месте, где он используется, на базовый тип, изменить сам тип и повторно изменить тип. Не слишком приятно. По этим причинам мы не будем использовать в нашем учебном примере определяемые пользователем типы данных.
Необязательные данные В главе 12 мы потратим много времени, обсуждая причину проблем при использовании значений NULL, когда они используются при поиске данных. Однако мы должны посмотреть, почему нам приходится использовать столбцы, допускающие значения NULL. Одной из целей нормализации должно быть удаление стольких значений NULL, сколько возможно. Прежде, чем мы рассмотрим, как реализовать значения NULL, нам нужно кратко обсудить, что означает "NULL". В то время как можно сказать, что NULL означает необязательные данные, это — неполное определение. Значение NULL должно читаться как "неизвестное значение". В главе 12 мы представим таблицы истинности для сравнений, но пока будем говорить, что NULL означает параметр, который может быть заполнен в будущем. Для столбца создается предложение: Ш ... где мы просто изменяем на NULL, если допускается использование значения NULL, или NOT NULL, если значения NULL не допускаются. Например, 296
Планирование и реализация основной физической структуры
CREATE TABLE NULLTest ( NULLColumn varchar(lO) NULL, notNULLColumn varchar(lO) NOT NULL )
Здесь создается таблица NULLTest (тестирование значения NULL), состоящая из двух столбцов: NULLColumn (столбец, допускающий NULL) и notNULLColumn (столбец, не допускающий значения NULL), оба типа varchar (10). Здесь нет ничего удивительного. Если мы опустим спецификацию NULL вообще, используется вариант по умолчанию для SQL Server. Чтобы определить, каково текущее свойство для БД по умолчанию, мы выполняем следующий оператор: Щ EXECUTE s p _ d b o p t i o n
@dbname = ' t e m p d b ' ,
@optname = 'ANSI NULL
default'
Здесь выполняется стандартная процедура sp_dboption (определение опции БД) для БД с именем tempdb (временная БД), проверяющая состояние опции с именем ANSI NULL default (NULL no умолчанию) Этот оператор дает OptionName (название опции) ANSI NULL default
CurrentSetting (текущая установка) off
Чтобы задать значение по умолчанию для БД, вы можете использовать процедуру sp_dboption. (Я рекомендовал бы, чтобы эта установка всегда отключалась, если вы забываете устанавливать это явно; в этом случае вы не увязнете в столбцах, допускающих значение NULL и которые быстро заполнятся такими значениями, которые вы будете должны чистить.) Чтобы установить значение по умолчанию для сеанса работы, используйте следующую команду: Щ
SET ANSI__NULL_DFLT_ON OFF — или ON, если по умолчанию используется NULL
Пример: -- отключение использование по умолчанию NULL SET ANSI_NULL_DFLT_ON OFF -- создание таблицы для проверки CREATE TABLE testNULL ( id int
-- проверка значений EXEC sp_help testNULL
Здесь создается таблица testNULL (проверка значения NULL) с одним столбцом id (идентификатор) типа int, а затем проверяется возможность использования значения NULL.
297
Глава 10 Этот запрос возвращает Column_name (имя столбца)
[...]
Nullable (возможность NULL)
id
...
по (нет)
Обратите внимание, что значительная доля выходных величин sp_help была удалена по причинам недостатка места. Функция sp_help возвращает информацию о таблице, столбцах и ограничениях. С другой стороны, если взять: -- изменение установки по умолчанию SET ANSI_NULL_DFLT_ON ON -- удаление таблицы и повторное создание DROP TABLE testNULL GO CREATE TABLE testNULL ( id int ) EXEC sp_help testNULL
... то получим
.
Column_name
[...]
Nullable
id
...
yes
Вычисляемые столбцы Теперь, когда мы представили типы данных, кратко рассмотрим вычисляемые столбцы. Они — действительно свежая особенность, которая была добавлена в SQL Server 7.0. Странно, что они не были реализованы в программе Enterprise Manager, которая просто прекращает работать, когда вы делаете любые изменения в таблице, так что вы были должны использовать ее с оригиналом таблицы. В SQL Server 2000 эта особенность должным образом поддержана. Щ AS Вычисляемые столбцы могут использоваться, чтобы взять нечитабельные данные и перевести их в удобочитаемый формат в определении таблицы. В SQL Server 2000 эти столбцы теперь могут быть индексированы, так что они даже более полезны. Пример, удобный для СУБД, который я создал, предназначен для организации группировки по дням, месяцам и годам. В следующем коде мы имеем пример, который довольно близок к этому. Он группирует по секундам, чтобы сделать пример более легким для проверки: CREATE TABLE calcColumns ( dateColumn datetime, dateSecond AS datepart(second, dateColumn),
I
298
-- вычисляемый столбец
Планирование и реализация основной физической структуры DECLARE @i int SET @i = 1 WHILE (@i < 1000) BEGIN INSERT INTO calcColumns (dateColumn) VALUES (getdateO) SET @i = @i + 1 END SELECT d a t e S e c o n d , m a x ( d a t e C o l u m n ) AS d a t e C o l u m n , c o u n t ( * ) AS c o u n t S t a r FROM c a l c C o l u m n s GROUP BY d a t e S e c o n d ORDER BY d a t e S e c o n d
Здесь создается таблица calcColumns (подсчет столбцов), содержащая два столбца: dateColumn (столбец дат) типа datetime и dateSecond (секунды даты) — вычисляемый столбец, в который помещаются секунды из даты первого столбца. Организуется цикл размещения 1000 текущих дат (которые в данном случае отличаются только секундами и долями секунды). Далее данные группируются по значениям вычисляемого столбца и упорядочиваются по этим же значениям вычисляемого столбца. После этой группировки для каждой группы формируется величина секунд (dateSecond), максимальное значение даты (dateColumn) и число элементов в группе (countStar). Когда этот код выполняется, он возвращает следующее: DateSecond
dateColumn
countStar
20 21 22
2000-11-14 11:07:20.993 2000-11-14 11:07:21.993 2000-11-14 11:07:22.993
8 900 91
Довольно опасная особенность с вычисляемыми столбцами заключается в том, что они будут игнорироваться, когда вы вставляете данные, если при этом опускаете список вставляемых полей. SQL Server будет игнорировать любые вычисляемые столбцы и относиться к этим полям, как будто они не существуют. Например, мы создаем следующую таблицу: CREATE TABLE testCalc ( value varchar(10), valueCalc AS UPPER(value), value2 varchar(lO)
Здесь создается таблица testCalc (проверка вычислений) с тремя столбцами: value (значение) типа varchar (10), valueCalc (вычисляемое значение) — вычисляемое поле, в которое помещается значение первого поля, написанное прописными буквами, и третье поле value2 (значение 2) типа varchar (10). Далее мы создадим некоторые новые значения без использования списка ввода:
I
INSERT INTO testCalc -- мы должны здесь иметь (value, value2) VALUES ('test', 'test2')
299
Глава 10
При этом никаких ошибок не произойдет. Если мы выполним запрос: SELECT * FROM testCalc
I
... он возвратит следующие результаты: value
valueCalc
value2
test
TEST
test2
Независимо от вычисляемых полей плохо кодировать оператор INSERT без списка ввода.
Вычисляемые столбцы — чрезвычайно ценная особенность, и должны волновать любого, кто когда-либо должен был неоднократно печатать и перепечатывать вычисления столбцов.
Служебные столбцы Не каждый столбец будет или должен быть обозначен на логической модели данных. В этом разделе мы обсудим некоторые из причин, почему мы имеем столбцы, которые не соответствуют образцу столбца логической модели, но которые требуются в физической реализации. Основная особенность использования служебного столбца в том, что он не является атрибутом сущности, которую моделирует таблица. Другими словами, столбец требуется, прежде всего, для целей реализации. Имеется несколько ситуаций, когда такие столбцы необходимы.
Управление параллелизмом Когда имется БД с больше чем одним пользователем, мы должны реализовать некоторую форму механизма управления параллелизмом. Мы должны быть уверены, что как только пользователь получает запись, которую он хочет изменить, никакой другой пользователь не может сделать изменения в записи, иначе мы можем потерять информацию. Имеются два различных метода для выполнения этой задачи. •
300
Пессимистическая блокировка — она реализуется с помощью блокировки любых записей, которые пользователь читает с намерением модернизировать. Такая блокировка называется пессимистической, потому что предполагает, что другие пользователи будут, вероятно, пробовать редактировать ту же самую запись, что и текущий пользователь. Это — очень строгое условие обслуживания и обычно не нужно, так как в большинстве случаев никакие два пользователя, вероятно, не будут смотреть ту же самую запись в то же самое время. Мы бы использовали пессимистическую блокировку только тогда, когда полностью необходимо заставить других клиентов ждать, пока мы не закончим работать с записью перед тем, как разрешим им получить доступ к ней. Проблема состоит в том, что мы должны заблокировать запись таким образом, чтобы никто другой не мог просматривать ее, или любой другой пользователь мог обратиться к нашим изменениям только после того, как мы снимем нашу блокировку.
Планирование и реализация основной физической структуры •
Оптимистическая блокировка — это гораздо более популярное решение. Чтобы реализовать механизм оптимистической блокировки, мы просто добавляем столбец к каждой таблице, который изменяется каждый раз, когда изменяется строка в БД. Например, пользователь № 1 извлекает строку значений. Вскоре пользователь № 2 извлекает ту же самую строку. Пользователь № 1 обновляет запись, и значение столбца блокировки изменяется. Теперь, если пользователь № 2'попытается изменить строку, SQL Server проверяет значение столбца блокировки, видит, что оно изменилось, и предотвращает обновление пользователем № 2: Пользователь № 1 Модифицирует запись
Извлекает запись время---•>
I
Извлекает запись
1
Пытается модифицировать запись, но ему запрещено
Пользователь № 2
Реализация оптимистической блокировки — довольно простая задача, но заметьте, однако, что каждый процесс, который использует таблицы, должен твердо придерживаться правил оптимистической блокировки. Если один из процессов игнорирует это, весь процесс терпит неудачу. Имеется несколько способов осуществить оптимистическую блокировку: •
Использование столбца типа timestamp — как было ранее рассмотрено, столбец timestamp является специальным столбцом, который изменяется каждый раз, когда изменяется строка. Столбцы timestamp — идеальный способ для создания оптимистической блокировки.
•
Использование даты последнего обновления и/или последнего корректирующего пользователя — в основном это тот же самый метод, за исключением того, что мы добавляем столбец, чтобы хранить пользователя, который произвел последнюю модифицированную запись, и время корректировки. Мы делаем это с помощью триггера, работающего с операторами INSERT и UPDATE. Этот метод лучше в том смысле, что дает удобочитаемую для человека величину, но хуже, потому что требует дополнительное кодирование триггеров.
•
Использование всех столбцов в выражении WHERE во время модификаций — это метод, обеспечиваемый программными средствами, наподобие Microsoft Access, где нет столбцов типа timestamp. Обычно, когда мы выполняем оператор обновления, то включаем каждый столбец в таблицу в выражении WHERE. Если значения изменились с того момента, когда мы извлекли их, то это означает, что кто-то еще, вероятно, обратился к нашим данным при нас. Следовательно, оптимистическая блокировка зафиксирует отказ в работе.
301
Глава 10 Мы будем использовать столбец timestamp, так как его весьма легко реализовать, и он не требует абсолютно никакого кода. Итак, мы создадим следующую таблицу: CREATE TABLE t e s t O p t i m i s t i c L o c k ( id
i n t NOT NULL,
value varchar(30) NOT NULL autoTimestarnp timestamp NOT NULL, primary key (id)
-- оптимистическая блокировка — задает первичный ключ в столбце id
Здесь создается таблица testOptimisticLock (проверка оптимистической блокировки) с тремя столбцами: id (идентификатор) — типа int, он же является и первичным ключом, value (значение) типа varchar (30) и аиtoTimestamp (автоматическая отметка времени) типа timestamp, который является оптимистическим ключом. Значения всех столбцов не могут быть NULL. Далее мы выполним следующий фрагмент. Первым шагом является создание новой строки в столбце и получение значения оптимистической блокировки для строки. INSERT INTO t e s t O p t i m i s t i c L o c k VALUES ( 1 , 'Тест 1')
(id,
value)
DECLARE @timestampHold timestamp SELECT @timestampHold • autoTimestamp FROM t e s t O p t i m i s t i c L o c k WHERE v a l u e
-
'Тест
1'
- - сначала будем работать/ UPDATE t e s t O p t i m i s t i c L o c k SET v a l u e ш 'Новое значение' WHERE i d = 1 AND autoTimestamp = @timestampHold IF @@rowcount - 0 BEGIN r a i s e r r o r 50000 'Строка изменена другим пользователем' END SELECT i d ,
value
FROM t e s t O p t i m i s t i c L o c k
- - затем установим отказ в работе UPDATE t e s t O p t i m i s t i c L o c k SET v a l u e - 'Второе новое значение' WHERE i d = 1 AND autoTimestamp = @timestampHold IF @@rowcount = 0 BEGIN r a i s e r r o r 50000 'Другой пользователь изменил строку' END SELECT i d , v a l u e FROM t e s t O p t i m i s t i c L o c k
Здесь в таблице testOptimisticLock создается строка с параметрами id** I, value = = "Тест 1". Далее объявляется переменная @timestampHold (фиксация значения типа timestamp) и в нее помещается значение поля autoTimestamp, для введенной строки таблицы testOptimisticLock (она содержит в поле value значение "Тест 1"). Далее выполняется 302
Планирование и реализация основной физической структуры часть запроса, которая имитирует нормальную работу. Здесь в строку (единственную) таблицы testOptimistocLock, у которой id = 1, в поле value помещается значение "Новое значение", если только поле autoTimestamp не изменило свое значение (а оно в данном случае не изменило своего значения, потому что строка не изменялась). Если бы изменение поля value не произошло (а в данном случае оно произошло), значение глобального параметра @@rowcount, определяющего число измененных строк, стало бы равным нулю (что возможно, если параметр autoTimestamp изменит свое значение, означающее, что строку изменил другой пользователь в течение рассматриваемого промежутка времени), будет выдано сообщение "Строка изменена другим пользователем". Этот фрагмент завершается выводом значений полей id и value для их контроля. Наконец, выполняется часть запроса, которая имитирует отказ в обновлении строки. Эта часть аналогична только что рассмотренному фрагменту, но в этом случае в поле value помещается значение "Второе новое значение", а в случае неудачи с изменением строки выводится сообщение "Другой пользователь изменил строку". В этом фрагменте реализуется именно этот случай, так как строку не удается скорректировать, потому что до этого в предыдущем фрагменте было изменено значение поля autoTimestamp ввиду успешного обновления строки. И этот фрагмент завершается выводом значений полей id и value. Этот запрос возвратит id
value
1
Новое значение
Server: Msg 50000, Level 16, State 1, Line 38 Другой пользователь изменил строку id
value
1
Новое значение
Устройства блокировки записей Созданный мной служебный столбец, который важен во многих приложениях, является устройством блокировки записей. Например, возьмем таблицу домена, который определяет тип некоторой записи, скажем, contactType (тип партнера), как на следующем рисунке: •
c
o
c
n
t
o
n
a
n
a
c
t
t
a
m
T
c
y
t
p
T
y
e
p
c
e
l
d
e
o
n
c
o
n
c
o
n
t
t
t
a
a
a
c
c
c
t
t
l
t
d
T
y
p
e
l
d
(
F
K
)
•
d
i
s
a
b
l
e
F
I
Здесь contactType — таблица "тип партнера"; contactTypeld — идентификатор типа партнера; name — название типа; disableFI — флаг запрета; contact — таблица "партнер"; contactld — идентификатор партнера. В таблице contactType мы имеем столбец типа b i t , названный d i s a b l e F I . Если этот столбец имеет значение 1, то мы больше не сможем использовать это значение как тип партнера. Нам не нужно удалять или изменять любые существующие значения партнера, чтобы установить этот флаг. (Обратите внимание, что это решение будет требовать, чтобы триггер обеспечил данное правило для проверки значения contactType . d i s a b l e F I , так как это — правило, установленное между таблицами.) 11-1868
3 0 3
Глава 10 Почему нам может потребоваться это сделать? Имеются три очевидных проблемы, которые мы должны решить: •
Если бы мы имели только 10 партнеров, не было бы проблемы физически удалить любые строки в таблице c o n t a c t , где используется конкретный экземпляр contactType. Однако если мы имеем 1 000 000 партнеров, все партнеры должны быть проверены. Это могло бы быть чересчур много.
Q
Так как пользователь может и не предвидеть любое дальнейшее использование конкретного значения, нет никакой причины полностью удалять запись из БД.
•
Могут существовать записи-потомки, которые мы не хотим удалять из БД.
Обратите внимание, что когда список значений contactType представляется пользователю, они должны быть отфильтрованы следующим образом: SELECT name FROM contactType WHERE disableFl = 0
I
Здесь из таблицы contactType выбираются записи (а в этих записях поля с именем name), в которых флаг disableFl равен нулю. Это подразумевает некоторый компромисс с точки зрения использования запроса, так как в таких случаях нам нужно балансировать затраты на удаление со стоимостью запроса. Мы могли бы также реализовать флаги удаления и запрещения записи, не допуская параллелизм для нашей БД и удаляя строки позже ночью, когда все спокойно. Следует упомянуть, что d i s a b l e F l — один из тех атрибутов, который требует удовлетворения 4НФ, поскольку мы будем, вероятно, хотеть знать не только о самом факте запрета, но и, например, когда появился этот запрет. Было бы можно, конечно, добавить таблицу c o n t a c t T y p e S t a t u s (состояние типа партнера), где мы контролируем предыдущее и текущее состояние. На практике это редко необходимо, но должно, конечно, быть рассмотрено в каждом случае отдельно, когда предварительно вы спрашиваете, когда и почему атрибут contactType, имеющий значение "Активный", был запрещен, но не можете получить простой ответ и находитесь в затруднении.
Сопоставление (порядок сортировки) Последовательность сопоставления для SQL Server определяет, как символьные данные будут организованы при хранении, как они будут при необходимости сортироваться, и как будут сравниваться. SQL Server и Windows обеспечивают огромное число типов сопоставления, из которых можно выбирать. Чтобы увидеть текущий тип сопоставления для сервера и БД, вы можете выполнить следующие команды: I
SELECT serverproperty ('collation') SELECT databasepropertyex('master',
'collation'
В большинстве систем, установленных для англоязычных стран, тип сопоставления по умолчанию — SQL_LATIN1_GENERAL_CP1_CI_AS, где Latinl_General представляет нормальный латинский алфавит, СР1 — обращение к кодовой странице 1252 (установленный по умолчанию набор символов Latin I ANSI для SQL Server), и последние части представляют нечувствительность к регистру и чувствительность к ударению соответственно. Полный перечень всех типов сопоставления можно найти в документации по SQL Server 2000. 304
Планирование и реализация основной физической структуры Для России используется значение Cyrillic_General для первого параметра. Прим. перев. Чтобы просмотреть список всех параметров сортировки, установленных на данном SQL Server, вы можете выполнить следующий оператор: I
SELECT * FROM ::fn_helpcollations()
На компьютере, который я использую для испытаний, этот запрос возвращает около 700 строк, но обычно нам не нужно будет изменять значения по умолчанию, которые администратор БД первоначально выбрал. Имеется несколько важных причин задавать различные сопоставления. •
Чувствительность к регистру — в зависимости от приложения может иметься потребность обращаться к коду и данным как чувствительным к регистру. Это может потребоваться от случая к случаю. Обратите внимание, что это делает ваш код и имена объектов также чувствительными к регистру. Помните, что чувствительность к регистру вызывает трудности при поиске. Например, при поиске всех имен, начинающихся с буквы "А", мы должны искать все элементы, начинающиеся с "А" или "а". Мы могли использовать функции преобразования к верхнему регистру для столбцов или переменных, но это нарушает индексацию и обычно — не лучший вариант.
•
Сортировка иностранных символов — другая причина — это иностранные символы. Используя Unicode, вы можете использовать любой символ любого языка в мире. Однако очень немного языков используют тот же самый A-Z набор символов, который используют англоязычные страны. Фактически, почти все другие европейские языки используют акцентированные символы, которые не являются частью набора 7-битовых ASCII-символов. Возможно использование различных типов сравнения при реализации столбцов на различных языках. Мы рассмотрим пример этого позже в данном разделе.
В предыдущих версиях SQL Server весь сервер имел единственный порядок сортировки. Это подразумевало, что в каждой БД, в каждой таблице, в каждом поле, в каждом запросе вы были вынуждены использовать один и тот же порядок сортировки. Это приводит к интересным трудностям, например, при попытке реализовать сортировку, чувствительную к регистру. Однако в SQL Server 2000 мы можем задавать сопоставление как для нашего сервера и БД, что упомянуто ранее, так и для столбца, и даже для оператора ORDER или SELECT. Чтобы установить последовательность сопоставления для столбцов типа char, varchar, t e x t , nchar, nvarchar или n t e x t при создании таблицы, вы задаете ее, используя оператор COLLATE в определении столбца, подобно следующему: CREATE TABLE o t h e r C o l l a t e ( i d i n t e g e r IDENTITY, name n v a r c h a r ( 3 0 ) NOT NULL, frenchName n v a r c h a r ( 3 0 ) COLLATE French_CI_AS_WS NULL, spanishName n v a r c h a r ( 3 0 ) COLLATE Modern_Spanish_CI_AS_WS NULL )
Здесь создается таблица otherCollate (другие сравнения), содержащая четыре столбца: автоинкрементный столбец id (идентификатор) типа integer; name (имя) типа nvarchar (30), который не может использовать значение NULL; frenchName (французское 305
Глава 10 имя) типа nvarchar (30), использующий французское сравнение, допускающий значение NULL, и spanishName (испанское имя) типа nvarchar (30), использующий модернизированное испанское сопоставление, допускающий значение NULL. Теперь, когда мы сортируем столбец f renchName, то сортировка будет нечувствительной к регистру, но организует строки в соответствии с французским набором символов. То же самое используется с испанским набором относительно столбца spanishName. В следующем примере мы рассмотрим другое свежее использование ключевого слова COLLATE. Можно использовать его, чтобы воздействовать на сравнения, выполняемые в выражении. Мы делаем это, изменяя одно или оба значения с обеих сторон выражения в двоичном сопоставлении. Создадим таблицу, называемую c o l l a t e T e s t (тестирование упорядочивания): CREATE TABLE collateTest ( name varchar(20) COLLATE SQL_Latinl_General_CPl_CI_AS ) INSERT INTO collateTest(name) VALUES ('BOB') INSERT INTO collateTest(name) VALUES ('bob')
NOT NULL
Здесь создается таблица collateTest, содержащая один столбец с именем name (название) типа varchar (20), в которую помещаются две записи 'ВОВ1 и 'bob'. Обратите внимание, что для целей демонстрации оператор COLLATE, который я включил, используется по умолчанию для моего сервера. Это, вероятно, будет и вашим сопоставлением, если вы используете сопоставление по умолчанию (мы примем это сопоставление для остальной части книги). Тогда можно выполнить следующий оператор над БД:
I
SELECT name FROM collateTest WHERE name = 'BOB'
... который возвратит обе строки: name bob BOB Однако если мы изменим сопоставление для символов "ВОВ" и выполним его:
I
SELECT name FROM c o l l a t e T e s t WHERE name = 'BOB' COLLATE L a t i n l General BIN
то оператор вернет только одну строку, которая точно соответствует символам 'ВОВ' name ВОВ
306
Планирование и реализация основной физической структуры Вы должны были заметить, что мы приводим только скалярное значение Ъов" для бинарного сопоставления. Определение сопоставления в первой части сравнения может быть хитрым вопросом, но мы не будем это рассматривать здесь. Вообще, лучше добавить функцию COLLATE к обеим сторонам выражения при выполнении такого действия. В нашем случае вместо применения сопоставления только к скалярной величине мы написали бы это следующим образом:
I
SELECT name FROM c o l l a t e T e s t WHERE name COLLATE L a t i n l G e n e r a l BIN = 'BOB'
COLLATE L a t i n l
G e n e r a l BIN
He будем глубже вникать в предмет сопоставления. В документации по SQL Server имеется большое количество информации относительно сопоставления, включая правила для сопоставления предшествующего элемента.
Ключи Как было рассмотрено в главах, посвященных логическому моделированию, определение ключей — одна из наиболее важных задач в проектировании БД. В этом разделе мы не будем рассматривать, почему мы определили выбранные ключи, а скорее, как мы реализуем их. Имеется несколько различных типов ключей, которые мы уже рассмотрели: Q
Первичный — содержит первичный указатель на строку в таблице.
•
Дополнительный — содержит дополнительные указатели на строку в таблице, обычно любые уникальные условия, которые, как мы можем удостовериться, представляют один или большее количество столбцов таблицы.
•
Внешний — внешние ключи — указатели на первичные ключи в других таблицах.
Первичные и дополнительные ключи — гибридные объекты — частично ограничения, частично индексы. Ограничения объявляют, что для объекта должен быть истинным некоторый фактор. Для ключей это означает, что значения в таблице должны быть уникальны. Объявления ограничений ключей реализуются так же, как индексы. Если вы не понимаете, как SQL Server хранит данные и реализует индексы, пожалуйста, прочитайте следующий раздел. Это жизненно важно для вашего понимания физического моделирования и вашего использования индексов и ключей, не говоря уже о понимании того, почему мы нормализуем наши БД. Снова много информации содержится в "Professional SQL Server 2000 Programming" Роба Виейра (Rob Vieira) (Wrox Press, ISBN 1-861004-48-6).
Индексы Любое обсуждение того, как строить ограничения, следует начать с обсуждения основ индексов и структур таблиц. Индексы — некоторые из наиболее важных объектов, входящих в физическое моделирование. Мы используем их, чтобы реализовать первичные ключи и вторичные ключи (уникальные ограничения), и можем использовать их, чтобы улучшить характеристики БД. Построение индексов будет рассмотрено в главе 14. Понимание, как работают индексы, даст нам основу, чтобы определить, какие виды индексов использовать. Мы дадим только краткую трактовку того, как SQL Server реализует индексы, поскольку эта сложная тема вне возможностей данной книги. 307
Глава 10 Основные структуры индексов Индексы ускоряют поиск строк в таблице, и обычно, хотя и не всегда, задают структуры, связанные с таблицей. Они строятся, используя один или большее количество столбцов таблицы и создавая специальную структуру для таблицы на основе этих выбранных ключевых столбцов. Имеются два различных класса индексов: •
Кластерные — упорядочивают физическую таблицу в соответствии с порядком индекса. Некластерные — полностью отдельные структуры, которые просто ускоряют доступ.
•
Мы можем иметь уникальный тип индексов любого класса. Уникальные индексы используются не только, чтобы ускорить доступ, но и обеспечить уникальность элементов столбца или группы столбцов. Причина того, что они используются для этой цели, заключается в том, что для определения уникальности значения мы должны просмотреть его в таблице, и так как индекс используется для ускорения доступа к данным, он отлично подойдет. Имеются другие назначения, но они — вне возможностей этой книги (просмотрите тему Create Index (создание индекса) в SQL Server 2000 Books Online, где создание индексов рассмотрено в деталях). Я здесь представлю только основные параметры, которые подходят для проектирования физических структур. Дополнительные настройки, связанные с функционированием, как и некоторыми другими особенностями, хотя и являются удобными, но вне наших возможностей. Индексы реализуются, используя структуру сбалансированного дерева, также называемую "В-дерево" (В-Tree). На следующей диаграмме мы даем основную структуру типа В-дерева, которую SQL Server использует для индексов.
__—-—"
.
Р
«1s
i»
М
11
—
_ — - — -
+ — — —
А
(
С
1
G
ii
J
II
К
г
р
«
S
i
т
«
^
Узлы-листья
Каждый из внешних прямоугольников — страница индекса объемом в 8 К, которую SQL Server использует для любого хранилища данных. Первая страница показывается наверху диаграммы: A i1
J I
р
1 Г
3 0 8
1
Планирование и реализация основной физической структуры Мы имеем три значения, которые являются ключами индекса. Чтобы решать, по какому пути следовать к более низкому уровню индекса, мы должны решить, между какими ключами находится значение: от А до I, от J до Р или больше, чем Р. Если значение — между А и I, мы идем по первому маршруту до следующего уровня:
Мы продолжаем эту процедуру до узлов-листьев индекса. Узлы-листья будут рассмотрены, когда мы перейдем к обсуждению деталей кластерных и некластерных индексов. Эта структура характерна для реализации всех индексов SQL Server. Однако следует отметить одну важную деталь. Мы сказали, что каждая из этих страниц — 8 K B размере. В зависимости от размера вашего ключа (определяемого суммой длин данных столбцов в ключе, максимально до 900 байтов), вы можете иметь где-то от восьми входов до более чем одной тысячи на одной странице. Следовательно, эти деревья вообще не растут в глубину, если разработаны должным образом. В нашем примере мы использовали упрощенный набор значений индекса, чтобы показать, как дерево индексов разворачивается. В следующем примере построим основную таблицу и основные индексы. Мы будем использовать эту таблицу в оставшейся части наших примеров данного раздела. CREATE TABLE testIndex ( id int IDENTITY, firstName varchar(20), middleName v a r c h a r ( 2 0 ) , lastName varchar(30) ) CREATE INDEX Xtestlndex ON t e s t l n d e x ( i d ) CREATE INDEX Xtestlndexl ON testlndex(firstName) CREATE INDEX Xtestlndex2 ON testlndex(middleName) CREATE INDEX Xtestlndex3 ON testlndex(lastName) CREATE INDEX Xtestlndex4 ON t e s t l n d e x ( f i r s t N a m e , middleName, lastName) Здесь создается таблица testlndex (тестирование индекса), содержащая четыре столбца: автоинкрементный столбец id (идентификатор) типа int, firstName (имя) типа varchar (20), middleName (отчество) типа varchar (20) и lastName (фамилия) типа varchar (30). Далее создаются 5 индексов с именами Xtestlndex, Xtestlndexl * Xtestlndex4. Первый содержит столбец id, второй — столбец firstName, третий — столбец middleName, четвертый — столбец lastName и пятый — столбцы firstName, middleName и lastName. Если все нормально, оператор будет успешно выполнен. Чтобы удалить индекс, вы используете следующий оператор: •
DROP INDEX testlndex.Xtestlndex
309
Глава 10 Кластерные индексы У злы-листья кластерного индекса фактически являются страницами данных. Следовательно, может быть только один кластерный индекс для таблицы. Точная реализация страниц данных — не особенно важная деталь; скорее, достаточно понять, что данные в таблице физически упорядочены в порядке индекса. При выборе, какие поля использовать как базу для кластерного индекса, лучше принять удобочитаемый для человека индекс. Другими словами, если ваша таблица имеет характерное упорядочивание, которое будет иметь смысл с точки зрения человека, это — хороший кандидат для кластерного индекса. Возьмем, например, словарь. Каждое слово в словаре можно рассматривать как строку в БД. Тогда эта БД кластеризована словами. Наиболее важная причина этого — то, что мы часто должны просматривать группу слов и их значений, чтобы видеть, нашли ли мы то, что нужно. Так как все данные сортируются в соответствии со словами, мы можем просматривать слова на страницах без каких-либо затруднений. Мы используем почти такую же логику, чтобы решить, нужно ли использовать кластерный индекс. Кластерные индексы должны использоваться для: •
• •
ключевых наборов, которые содержат ограниченное число различных значений — так как если данные сортируются ключами, все просматриваемые величины при поиске по значениям индекса мы видим немедленно; запросов диапазонов — получение всех данных в последовательности обычно имеет смысл, когда вы должны получить диапазон, скажем, от "А" до " F " ; данных, к которым обращаются последовательно, — очевидно, что если вам нужен доступ к данным в этом порядке, то имея уже отсортированные данные, выполнение будет существенно улучшено;
•
запросов, которые возвращают большие наборы результатов, — этот момент будет иметь больше смысла в сравнении с некластерным индексом, но расположение данных непосредственно на узлах-листьях структуры уменьшает непроизводительные издержки;
•
наборов ключей, к которым часто обращаются с помощью запросов, использующих операторы JOIN или GROUP BY, — часто они могут быть столбцами внешнего ключа, но не обязательно. Операторы GROUP BY всегда требуют, чтобы данные были отсортированы, отсюда следует, что наличие отсортированных данных так или иначе улучшит работу.
Так как кластерный индекс физически упорядочивает таблицу, добавление новых значений будет обычно вынуждать вас помещать новые данные в середину таблицы — единственное исключение из этого, если вы используете кластерный индекс с автоинкрементным типом полей. Если новая строка не будет умещаться на странице, SQL Server должен разбить страницу (что происходит, когда страница полная, так что половина данных отделяется на новую страницу — следовательно, происходит и добавление имени). Разбиения страницы — довольно дорогостоящие действия и вредят работе, так как данные не будут расположены на последовательных страницах, не давая возможность синхронного чтения дисковой подсистемой. Если вы не имеете набора ключей, которые удовлетворяют одному из критериев кластерного индекса, обычно лучше всего не строить его. Также важно не использовать кластерный индекс на столбцах, которые часто изменяются. Так как таблица упорядочена по значениям в столбце, SQL Server, вероятно, будет вынужден перестраивать строки на своих страницах, что является дорогостоящим действием. Это особенно важно, если вы должны модифицировать значения ключа, которые в свою очередь изменяют большое количество различных строк. 310
Планирование и реализация основной физической структуры Другой важный момент, на который следует обратить внимание, — то, что вы должны иметь кластерных ключей как можно меньше, так как они будут использоваться в каждом некластерном индексе (как мы увидим дальше). Так что, если вы изменяете кластерный ключ, вы добавляете работу и для всех некластерных индексов также. Для кластерной таблицы локатор строк (см. ниже) — всегда кластерный индексный ключ. Если кластерный ключ не уникален, SQL Server добавляет случайное значение, чтобы сделать его уникальным. Некластерные индексы Некластерный индекс аналогичен индексу в тексте книги, когда книга индексируется номером страницы. Как и в индексе книги, некластерный индекс полностью отделен от строк данных. Узел-лист структуры некластерного индекса содержит только указатель на строку данных, содержащую основное значение. Указатель из некластерного индекса на строку данных называется локатором строк. Структура локатора строк зависит от того, имеет ли таблица кластерный индекс или нет. Верхние уровни индекса — те же самые, что и у кластерного индекса. Мы рассмотрим диаграммы, показывающие различные локаторы строк, несколько позже после обсуждения основ некластерных индексов. Мы обсудили таблицы с кластерными индексами — они называются кластерными таблицами. Если нет кластерного индекса, таблица называется "кучей". Мое собственное определение "кучи" — "группа вещей, помещенных или положенных друг на друга". Это хороший способ объяснить, что происходит в таблице, если нет кластерного индекса. SQL Server просто помещает каждую новую строку в конец таблицы. Обратите также внимание, что, если вы производите удаление, SQL Server просто выбросит строку, не используя повторно место. Однако если вы проводите обновление, имеются две возможности: • •
обновление на месте — значения в строке просто изменяются; удаление и вставка — вся строка удаляется из таблицы и затем повторно вставляется.
Имеется несколько критериев, которые SQL Server использует, чтобы определить, действительно ли следует выполнить обновление строки. Два наиболее частых включают: •
Наличие у таблицы триггера AFTER, выполняемого после обновления, — когда вы имеете триггер для таблицы, который работает после того, как строка обновлена; строка сначала удаляется, а затем вставляется.
•
Попытка вставить большее количество данных, чем может разместиться на странице, — если вы имеете данные переменной длины в вашей таблице, и пытаетесь увеличить размер размещенных данных, SQL Server должен будет удалить строку и повторно вставить ее где-нибудь еще, возможно, на другой странице. Эта процедура отличается от случая, когда имеется кластерный индекс, потому что теперь нет никакой причины разбивать страницу в середине структуры, — быстрее и легче переместить данные к концу "кучи".
Теперь нам нужно рассмотреть два различных типа таблиц и как реализуются их некластерные локаторы строк.
311
Глава 10 "Куча" Для таблицы типа "кучи" без кластерного индекса локатор строк является указателем на страницу, которая содержит строку. Предположим, что мы имеем таблицу по имени itemTable (таблица элементов), с кластерным индексом, состоящим из столбца d e s c r i p t i o n (описание), и некластерным индексом, состоящим из столбца itemld. В следующей диаграмме мы видим, что для того чтобы найти itemld 3 в таблице, нужно взять левую ветвь дерева, потому что 3 находится между 1 и 20, затем следовать вниз по пути между 1 и 10. Как только мы добираемся до страницы, то имеем указатель на страницу, которая содержит нужное значение. Предположим, что этот указатель состоит из номера страницы и смещения на странице (страницы нумеруются с нуля, а смещения нумеруются с 1). Наиболее важный момент относительно этого указателя заключается в том, что он приводит непосредственно к строке на странице, которая содержит значения, которые мы ищем. Это не так, как в случае, когда мы работаем с кластерной таблицей, и важно понять различие. 1 20 | 20 23 28 39
Индекс в столбце Itemld таблицы ItemTable Номер строки, itemld смещение
Таблица ItemTable Страница 0
1
1.2
itemld
description
2
0.1
2
Chicken Soup
3
0.2
-• 3
7
1.1
8
8
0.3
Broccoli Lentils
Страница 1 itemld
description
7
Milk
1
Popcorn
Некластерные индексы в кластерной таблице В этой ситуации, когда вы прошли некластерный индекс и достигли его узла-листа, вместо получения указателя, который приводит вас непосредственно к данным, вы получаете кластерный индекс, который вы должны пройти, чтобы получить данные. На следующем графике нижний левый прямоугольник — некластерный индекс, как в нашем предыдущем примере, за исключением того, что указатель изменился от определенного местоположения к значению кластерного ключа. Далее мы используем кластерный ключ, чтобы пройти кластерный индекс и добраться до данных. 312
Планирование и реализация основной физической структуры
Кластерный индекс в столбце description Таблица Item Другие страницы Страница О itemld
description Milk
Узлы-листья некластерного индекса в столбце Itemld item Id
Mustard Popcorn
Кластерный ключ
1
Popcorn
2
Chicken Soup
3
Rutabagas
7
Milk
8
Lentils
9
Playas
''Страница 1 itemld
description Red Meat Rutabagas Rye Bread
Другие страницы
Издержки этой операции минимальны, и она "теоретически" лучше, чем использование прямых указателей на таблицу, потому что требуется лишь минимальная реорганизация для любой корректировки кластерного индекса или кластерного ключа. Когда корректировка выполнена, указатели не перемещаются. Вместо этого, в таблице оставляется указатель, который указывает на новую страницу, где теперь расположены данные. Все существующие индексы, которые имеют старые указатели, просто указывают на старые страницы, затем следует новый указатель на страницу с новым размещением данных. Уникальные индексы Уникальный индекс — второй наиболее важный момент, который вы определяете при реализации кластерного индекса. Вы можете создавать уникальные индексы как для кластерных, так и для некластерных индексов. Это гарантирует, что среди индексных ключей не будет каких-либо двойных значений. Например, если мы создали следующие индексы в нашей таблице t e s t l n d e x : I
CREATE UNIQUE INDEX Xtestlndexl ON testlndex(lastName) CREATE UNIQUE INDEX Xtestlndex2 ON testlndex(firstName, middleName, lastName)
(обозначения см. в предыдущих примерах) ... мы не могли бы ни вставить двойные значения в столбец lastName, ни создать вхождения, которые имеют то же самое сочетание значений firstName, middleName и lastName. Мы обычно не используем уникальные индексы, чтобы обеспечить уникальность вторичных ключей. SQL Server имеет механизм, известный как ограничение UNIQUE, а также ограничение PRIMARY KEY, которые мы будем использовать. Используйте уникальные индексы, когда вам нужно создать индекс для улучшения работы. 313
Глава 10 Следует также отметить, что для функционирования ваших систем очень важно, чтобы вы использовали уникальные индексы всюду, где только возможно, поскольку это увеличивает возможности оптимизатора SQL Server (SQL Server optimizer) предсказывать, сколько строк будет возвращено запросом, который использует индекс. В нашем примере, если мы попробуем выполнить следующий запрос: INSERT VALUES INSERT VALUES
INTO testlndex (firstName, middleName, lastName) ('Louis', 'Banks', 'Davidson') INTO testlndex (firstName, middleName, lastName) ('James', 'Banks', 'Davidson')
... то получим следующую ошибку:
I
Server: Msg 2601, Level 14, State 1, Line 3 Cannot insert duplicate key row in object 'testlndex' with unique index Xtestlndexl'. The statement has been terminated.
(Сервер: Сообщение 2601, уровень 14, состояние 1, строка 3 Нельзя поместить строку с повторяющимся ключом в объект testlndex индексом Xtestlndexl. Оператор удален.)
с уникальным
Хотя сообщение об ошибке могло бы быть и лучше, ясно, что предотвращается помещение дубликата строки с той же фамилией. IGNORE_DUP_KEY Часто полезно задать уникальность индексов с помощью параметра IGNORE_DUP_KEY (игнорировать дубликаты ключей). Используя это, мы можем сообщить системе SQL Server, чтобы она игнорировала любые строки с дубликатами ключей, если мы так желаем. Если мы изменим наш: индекс в предыдущем примере:
I
DROP INDEX testlndex.Xtestlndex4 CREATE UNIQUE INDEX Xtestlndex4 ON testlndex(firstName, middleName, lastName) WITH IGNORE DUP KEY
Здесь сначала удаляется индекс Xtestlndex4 для таблицы testlndex этот же индекс, но уже с параметром IGNORE_DUP_KEY. ... а далее выполним тот же код, что и в предыдущем случае: INSERT INTO t e s t l n d e x (firstName, middleName, lastName) VALUES ( ' L o u i s ' , ' B a n k s ' , ' D a v i d s o n ' ) INSERT INTO t e s t l n d e x (firstName, middleName, lastName) VALUES ( ' J a m e s ' , ' B a n k s ' , ' D a v i d s o n ' )
мы получим следующий результат: I
Server: Msg 3604, Level 16, State 1, Line 3 Duplicate key was ignored.
(Сервер: Сообщение 3604, уровень 16, состояние 1, строка 3 Дубликат ключа проигнорирован.) 314
и заново создается
Планирование и реализация основной физической структуры Эта настройка, вероятно, не то, что вам нужно в большинстве случаев, но может оказаться удобной, когда вы строите копию некоторых данных. Вы можете в этом случае вставлять данные, не волнуясь о том, что было туда помещено прежде, поскольку индекс отбросит двойные значения. Обратите внимание, что это не работает при обновлениях, а только при вставке строк.
Первичные ключи Выбор первичного ключа — один из наиболее важных выборов, которые мы делаем для конкретной таблицы. Он является первичным ключом, который будет мигрировать в другие таблицы как указатель на конкретное значение. В логическом моделировании мы выбрали использование указателя в виде 4-байтового целого числа для первичных ключей сущностей, которые мигрируют к зависимым таблицам-потомкам следующим образом: parent parentld
grandchild childld (FK) parentld (FK) grandChildld
Здесь parent — таблица "предок"; parentld — идентификатор предка; child — таблица "потомок"; childld — идентификатор потомка; grandchild — таблица "потомок потомка"; grandchildld — идентификатор потомка потомка. В физическом моделировании это заставляет реализованные ключи становиться довольно большими, не столько в смысле размера данных, сколько в смысле большого числа значений, которые мы должны использовать. Другая проблема с реализацией первичных ключей, таким образом, заключается в том, что мы обычно забываем тот факт, что ключ таблицы не должен состоять только из служебных столбцов. Мигрирующие ключи хороши, поскольку они представляют значения таблицы, из которой они пришли, но, добавляя уникальное значение, мы по существу сделали его служебным ключом (так как независимо от того, что вы добавляете к служебному значению, вы будете всегда получать служебное значение). В логическом моделировании это представление таблиц-предков и таблиц-потомков показало нам, что вы не могли иметь потомка потомка (внука) без потомка (ребенка) или предка (родителя), и мы всегда моделировали вторичный ключ, чтобы было ясно, что понимается под вторичным ключом. Однако теперь, находясь на физической стадии моделирования, мы будем следовать несколько иной стратегии. Каждая таблица будет иметь единственный служебный первичный ключ, и мы реализуем вторичные ключи в наших таблицах, как показано в следующей диаграмме: parent parentld otherParentAttForAltKey (AK1.1)
child childld parentld (FK)(AK1.1) otherChildAttributeForAltKey (AK1.2)
grandchild grandChildld childld (FK)(AK1.1) otherGrandChildAttribateForAIIKey (AK1.2)
Здесь дополнительно OtherParentAttForAltKey — другой атрибут предка для вторичного ключа; OtherChildAttributeForAltKey — другой атрибут потомка для вторичного ключа; otherGrandChildAttributeForAltKey — другой атрибут потомка потомка для вторичного ключа. 315
Глава 10 Мы выберем этот метод по следующим важным причинам: • •
•
•
Мы никогда не должны волноваться относительно того, что делать, когда значение первичного ключа изменяется. Каждая таблица имеет единственный столбец указателя первичного ключа, и в этом случае намного легче разрабатывать приложения, использующие этот указатель, потому что каждая таблица будет иметь единственное значение для первичного ключа. Наш индекс первичного ключа будет очень мал, и таким образом операции, которые используют его для доступа к таблице, будут быстрее. Большинство операций обновления и удаления будут, вероятно, изменять данные, обращаясь к данным с помощью первичных ключей, использующих этот индекс. Соединения таблиц будет более просто проектировать, так как все мигрирующие ключи будут содержать единственное поле.
Имеются неудобства в этом методе, типа того, что при соединении таблиц всегда нужно выяснять, что фактически означает первичный ключ, не говоря уж о других соединениях, которые необходимы для доступа к данным. Так как цели нашей OLTP-системы состоят в том, чтобы иметь небольшие ключи, ускорять корректировки и иметь согласованные данные, эта стратегия не только приемлема, но и благоприятна. Реализовать первичный ключ таким образом очень просто:
I
CREATE TABLE nameTable id integer NOT NULL IDENTITY PRIMARY KEY NONCLUSTERED )
Здесь создается таблица с именем nameTable (имя таблицы) с единственным автоинкрементным столбцом id типа integer, который не может иметь значений NULL, являющимся некластерным первичным нулем. Обратите внимание, что вы не можете использовать столбцы, допускающие значения NULL в первичном ключе, даже если вы можете иметь столбцы, допускающие значения NULL в уникальном индексе. Причина этого на самом деле довольно проста. Первичный ключ — идентификатор записи, a NULL не обеспечивает никакую форму идентификации записи. Обратите также внимание, что мы явно определили NONCLUSTERED (некластерный). Если мы опустим эту спецификацию, ограничения первичного ключа будут реализованы, используя кластерный индекс; мы часто не вспоминаем этот факт, пока не будет слишком поздно. Следующий оператор будет также работать и в случае, если вам нужно определить больше чем один столбец для первичного ключа. CREATE TABLE nameTable i d i n t e g e r NOT NULL IDENTITY, pkeyColumnl i n t e g e r NOT NULL, pkeyColumn2 i n t e g e r NOT NULL PRIMARY KEY NONCLUSTERED ( i d )
Здесь создается таблица с именем nameTable, содержащая три столбца: автоинкрементный столбец id типа integer, являющийся некластерным первичным ключом, и два дополнительных столбца первичного ключа pkeyColumnl и pkeyColumn2 (первый столбец первичного ключа и второй столбец первичного ключа). 316
Планирование и реализация основной физической структуры Обычно, когда мы реализуем первичный ключ как указатель в виде целого числа, мы не заинтересованы тратить на это впустую наш кластерный индекс. С первичным ключом этого вида нельзя обращаться как с каким-либо упорядоченным ключом. Причина этого в том, что вам редко будет нужно обращаться к строкам в порядке, определяемом первичным ключом, или рассматривать диапазон строк на основе первичного ключа. Если требуется упорядочивание, лучше добавить дополнительный столбец, потому что если потребуется переупорядочить записи, вы не сможете изменить значение идентифицирующего столбца; кроме того, вы не должны будете часто изменять первичный ключ по каким-либо причинам. Как упомянуто выше, если вы должны гарантированно обеспечить ключ, который будет уникальным по всей БД или даже по всему серверу, то можно использовать столбец типа u n i q u e i d e n t i f i e r со свойством ROWGUIDCOL, подобно следующему:
I
CREATE TABLE nameTable ( id uniqueidentifier NOT NULL ROWGUIDCOL DEFAULT newid(), PRIMARY KEY NONCLUSTERED (id)
Обратите внимание, что ROWGUIDCOL не обеспечивает ни уникальности, ни значения по умолчанию, как в идентифицирующем столбце. Только использование при необходимости типа u n i q u e i d e n t i f i e r , поскольку он использует шестнадцать байтов вместо четырех для целого числа, дает это.
Вторичные ключи Формирование вторичных ключей — очень важная задача физического моделирования. При реализации вторичных ключей мы имеем два выбора: •
уникальные ограничения;
•
уникальные индексы.
В качестве примера возьмем таблицу nameTable и расширим ее, включив имя и фамилию. При этом мы хотим обеспечить правило, что необходимо иметь уникальные полные имена в таблице. Мы могли бы создать индекс, подобно следующему: CREATE TABLE nameTable ( id integer NOT NULL IDENTITY PRIMARY KEY NONCLUSTERED, firstName varchar(15) NOT NULL, lastName varchar(15) NOT NULL ) CREATE UNIQUE INDEX XnameTable ON nameTable(firstName, lastName)
Или можно использовать уникальное ограничение типа: CREATE TABLE nameTable ( id integer NOT NULL IDENTITY PRIMARY KEY, firstName varchar(15) NOT NULL, lastName varchar(15) NOT NULL, UNIQUE (firstName, lastName)
317
Глава 10 Хотя фактически оба они основаны на уникальных индексах, уникальные ограничения — более предпочтительный метод реализации вторичного ключа и обеспечения уникальности. Это потому, что ограничения семантически предназначены для обеспечения ограничений на данные, а индексы предназначены для ускорения доступа к данным. Фактически не имеет значения, как реализована уникальность, но мы, конечно, должны иметь в одном месте или уникальные индексы или уникальные ограничения. При реализации ограничений есть одно дополнительное преимущество — возможность просматривать ограничения на таблицу, выполняя следующую хранимую процедуру: | sp_helpconstraint
'nameTable'
Она возвращает набор результатов, сокращенный вариант которого имеет вид: constraint_type
constraint_name
[...]
constraint_keys
PRIMARY KEY(clustered) UNIQUE (non-clustered)
PK_nameTable_1466F737 UQ_nameTable_155B1B70
... ...
id firstName, lastName
Хранимая процедура будет использоваться для просмотра всех ограничений, добавляемых к нашим таблицам, в следующих главах.
Обозначение Обозначение ключей не столь существенно для общей структуры таблицы. Однако все же важно давать ключам некоторые распознаваемые названия для использования в сообщениях об ошибках. SQL Server не требует от нас давать названия нашим индексам. Если мы не даем названий объявлениям ограничений, как поступали в предыдущих разделах, SQL Server назначает названия за нас. Например, следующие названия были выбраны, когда мы запускали код предыдущего примера: PK_nameTable_1 ED998B2 UQ_nameTable_1 FCDBCEB UQ_nameTable_20C1 E124 UQ_nameTable_21B6055D Они немного говорят читателю. Хотя и можно их рассматривать с помощью программных средств, наподобие Enterprise Manager, вероятно, лучше сделать их как-то более понятными. Стандарт обозначения, который мы будем использовать в этой книге, довольно прост: Щ Для первичного ключа тип был бы РК (Primary Key — первичный ключ), а для вторичного ключа мы будем использовать АК (Alternate Key — вторичный ключ). Что касается описания, мы опустили бы его для первичного ключа, потому что у нас может быть только единственный экземпляр первичного ключа. Для описания вторичных ключей используйте или название столбца для единственного столбца, или краткое описание вторичного ключа, включающего больше одного столбца. Так, для нашего примера мы получили бы следующее: CREATE TABLE n a m e T a b l e i d i n t e g e r NOT NULL IDENTITY CONSTRAINT PKnameTable PRIMARY KEY,
318
Планирование и реализация основной физической структуры f i r s t N a m e v a r c h a r ( 1 5 ) NOT NULL CONSTRAINT AKnameTable_firstName UNIQUE, l a s t N a m e v a r c h a r ( 1 5 ) NULL CONSTRAINT AKnameTable_lastName UNIQUE, CONSTRAINT AKnameTable_fullName UNIQUE ( f i r s t N a m e , l a s t N a m e )
Это дало бы нам следующие названия, делая их более легкими для работы с ограничениями: PKnameTable AKnameTable_fullName AknameTableJastName AknameTable_firstName
Другие индексы О любых индексах, которые мы добавляем к индексам, обеспечивающим уникальность, нужно позаботиться на стадии настройки функционирования. Это — очень важный момент, на который я буду обращать внимание много раз. Индексы делают доступ к данным некоторым образом быстрее, но они также связаны с издержками. Каждый раз, когда происходят любые изменения в индексируемых столбцах, индекс также должен быть изменен. При единственном изменении это время действительно мало, но чем более активна система, тем больше это затронет функционирование. В нашей OLTP-системе только в очевидных случаях я бы защищал использование "догадок" — добавление индексов прежде, чем возникает потребность — вместо использования "настроек", где мы реагируем на известные проблемы функционирования. Только в этом случае мы будем способны решить, как "настройка" функционирования затронет остальную часть БД.
Отношения Мы уже рассмотрели в некоторой степени отношения, так что не будем говорить слишком много относительно причин их использования. В этом разделе мы просто обсудим, как реализовать отношения. Первое, что нужно сделать — ввести новый оператор. Это оператор ALTER TABLE: | ALTER TABLE
Оператор ALTER TABLE позволяет нам изменять и добавлять столбцы, проверять ограничения, а также позволять и запрещать работу триггеров. В этом разделе мы рассмотрим, как добавить ограничения к таблицам. Обратите внимание, что вы можете это сделать с помощью оператора CREATE TABLE. Однако так как мы часто создаем все наши таблицы сразу, лучше использовать команду ALTER TABLE для уже существующих таблиц, нежели создавать таблицы в порядке, когда таблицы-предки создаются перед зависимыми таблицами-потомками.
Внешние ключи Внешний ключ — фактически тот же первичный ключ, мигрирующий в таблицу-потомок, но другой таблицы, которая представляет сущность, из которой он берется. Реализация внешних ключей — довольно простая задача в SQL Server 2000. Имеется несколько проблем, с которыми мы сталкиваемся при формировании отношений: •
Каскадированные удаления (если удалена строка предка, то удаляются любые связанные значения потомков, которые обращаются к ключу удаленной строки).
•
Отношения, которые охватывают разные БД. 319
Глава 10 Основной синтаксис оператора ALTER TABLE для добавления ограничений внешнего ключа довольно прост (команда ALTER TABLE будет использоваться неоднократно в этой и последующей главах, чтобы добавить ограничения): ALTER TABLE ADD CONSTRAINT [] FOREIGN KEY REFERENCES ()
I
Обратите внимание, что этот код демонстрирует добавление ограничения. Относительно других действий, которые вы можете выполнить, используя ALTER TABLE, см. документацию SQL Server 2000. •
— таблица-потомок в отношении.
•
— таблица-предок в отношении.
•
— разделенный запятыми список столбцов в таблице-потомке в том же порядке, что и столбцы в первичном ключе таблицы-предка.
Если вам нужно реализовать необязательное отношение (где мигрирующий ключ может принимать значение NULL) подобно следующему: parent parentld: int NOT NULL parentAttribute: varchar (20) NULL
child ^~^_ имеет
childld: int NOT NULL parentld: int NULL childAttribute: varchar(20) NOT NULL
... то это реализуется почти так же, как в предыдущем случае. Оператор ALTER TABLE — тот же самый, но вы можете заметить, что столбец c h i l d , p a r e n t Id может принимать значение NULL. Когда ключ ссылки допускает значение NULL, SQL Server знает, что вы хотите, чтобы он был необязательным. Вы не должны иметь NULL в первичном ключе, потому что, как мы выяснили, первичный ключ не может иметь атрибут, допускающий значение NULL. Это столь же просто, как и защитить отношения предок-потомок, которые мы установили в нашем проекте. Вскоре будут представлены примеры отношений и каскадированных удалений, а также краткое обсуждение отношений между БД.
Обозначение При выборе имен для наших объектов мы обычно создавали имена, которые позволяли пользователю взаимодействовать с объектами. Для ограничений и индексов нам нужны имена, которые указывают на использование объекта. То же самое справедливо и для имен отношений; хотя возможно при некоторых обстоятельствах выбрать одно и то же имя для двух отношений, мы не сможем это реализовать в SQL Server. Следовательно, мы должны рассмотреть назначение имен для отношений. Стандартные обозначения для отношений, которые мы будем использовать в этой книге, берут для имени объекта глагол от логической модели и включают имена таблицы-предка и таблицы-потомка, чтобы создать уникальное имя, а также чтобы легче определить любые ограничения, когда просматривается список имен.
320
Планирование и реализация основной физической структуры Мы используем следующий шаблон для нашего имени: | $$ Например, рассмотрим в качестве примера пару таблиц.
company companyld name
sells
product productld companyld (FK) name
Здесь company — таблица "компания"; companyld — идентификатор компании; name — имя; sells — продает; product — таблица "товар"; productld — идентификатор товара; companyld — идентификатор компании. Чтобы реализовать это отношение, мы можем определить имя company$sells$product и так же как и с именами внешних ключей реализовать следующий синтаксис:
I
ALTER TABLE p r o d u c t ADD CONSTRAINT c o m p a n y $ s e l l s $ p r o d u c t FOREIGN KEY
REFERENCES p a r e n t (companyld)
Обратите внимание на использование знака доллара в имени. Вспомнив наши стандарты идентификаторов, которые мы обсуждали ранее в главе, можно прийти к выводу, что нет никаких действительно хороших значений для разграничения или разделения отдельных частей имени, корме знака доллара. Кроме букв и цифр мы можем использовать только три специальных символа: знак доллара, амперсанд и знак подчеркивания. Знак подчеркивания часто используется внутри имен объектов (вместо способа "верблюда", который мы используем). Амперсанд часто используется для переменных, а отношение таковым не является. Следовательно, для ясности мы будем использовать знак доллара между именами. Мои имена объектов, по-видимому, будут удивительными для некоторых, но это лишь мое собственное соглашение относительно имен, и я использую знак доллара, чтобы разделять части имени объекта, потому что это не имеет никакого значения для SQL Server, и я могу использовать это соглашение независимо от выбранного стандарта обозначений. Это — другая область, где играют роль личные вкусы. Например, если мы имеем следующие таблицы в нашей модели: parent parentld: int NOT NULL parentAttribute: varchar(20) NULL
child имеет
childld: int NOT NULL parentld: int NOT NULL childAttribute: varchar(20) NOT NULL
... можно реализовать, используя следующий фрагмент создания таблиц:
I
CREATE TABLE parent parentld int IDENTITY NOT NULL CONSTRAINT PKparent PRIMARY KEY,
Продолжение кода на следующей странице
321
Глава 10 parentAttribute varchar(20) NULL ) CREATE TABLE child ( childld int IDENTITY NOT NULL CONSTRAINT FKchild PRIMARY KEY, parentld int NOT NULL, childAttribute varchar(20) NOT NULL )
Мы можем реализовать отношение, используя следующий синтаксис:
1
ALTER TABLE child ADD CONSTRAINT child$has$parent FOREIGN KEY (parentld) REFERENCES parent
Если мы создали это ограничение, то можем проверить его, пытаясь ввести данные в таблицу-предок, а затем в таблицу-потомок: INSERT INTO parent(parentAttribute) VALUES ('parent') -- получение последнего идентифицирующего значения, помещенного в текущий контекст DECLARE @parentld int SELECT @parentld = scope_identity() INSERT INTO child (parentld, childAttribute) VALUES (@parentld, 'child') DELETE FROM parent
Это вызывает следующую ошибку в БД при выполнении: Server: Msg 547, Level 16, State 1, Line 1 DELETE statement conflicted with COLUMN REFERENCE constraint'child$hasSparent'. The conflict occurred in database 'tempdb', table 'child', column 'parentld'. (Сервер: сообщение 547, уровень 16, состояние 1, строка 1 Оператор DELETE конфликтует с ограничением COLUMN REFERENCE (ссылка на столбец) 'child$has$parent'. Конфликт возник в БД 'tempdb', таблица 'child', столбец 'parentld'.)
Каскадирование удалений и обновлений В нашем предыдущем примере БД предотвращала нас от удаления записи в таблице-предке, если существовала запись в таблице-потомке, имеющая отношение к таблице-предку как к атрибуту. В большинстве случаев это хорошо, но имеются случаи, когда данные в таблице-потомке так объединены с данными таблицы-предка, что когда запись родительской таблицы изменена или удалена, SQL Server будет всегда стремиться изменить или удалить запись в таблице-потомке без какого-либо дальнейшего взаимодействия с пользователем.
322
Планирование и реализация основной физической структуры Рассмотрим случай таблицы товаров и таблицы, которая хранит описательную спецификацию: product productld
prod uctSpecif ication productSpecificationld имеет описательную информацию в
name
productld (FK) name value
Здесь product — таблица "товар"; productld — идентификатор товара; name — имя; productSpecif ication — таблица "спецификация товара"; productSpecificationld — идентификатор спецификации товара; value — величина. В этом случае, если бы мы хотели удалить запись из таблицы товаров, маловероятно, что мы хотели бы сохранить соответствующую запись в таблице product Specif i c a t i o n . Способность выполнять автоматически некоторое неявное изменение в таблице-потомке, как результат явного действия в таблице-предке, известна как каскадирование изменений. Для удаления вы можете установить ограничение, которое автоматически удалит потомков отношения при некоторых действиях на таблице-предке. В случае обновления, если вы изменяете значение первичного ключа таблицы-предка, то изменение каскадировало бы значение внешнего ключа таблицы-потомка. Имеется другое менее используемое действие каскадирования, которое SQL Server фактически не осуществляет — тип каскадируемых действий, когда запись из таблицы-потомка полностью не удаляется, но ее ключевая ссылка устанавливается в NULL. Это может быть реализовано только на необязательных отношениях, так как если внешний ключ не допускает значение NULL, данную операцию выполнить не удастся. Обсуждение того, как осуществить эту операцию, будет проведено в следующей главе, поскольку она реализуется с помощью триггеров. Две наиболее захватывающие новые особенности SQL Server 2000 — это возможность иметь каскадированное удаление в объявленной ссылочной целостности и каскадированное обновление. В предыдущих версиях SQL Server операция каскадирования требовала триггера. Это было неприятно, потому что мы не могли при этом использовать отношения с помощью внешнего ключа и средства, которые могли бы использовать объявленные отношения. Синтаксис для осуществления операций каскадирования прост. Мы просто добавляем ON DELETE CASCADE или ON UPDATE CASCADE к нашему объявлению ограничения: ALTER TABLE ADD CONSTRAINT FOREIGN KEY REFERENCES [ON DELETE | ] [ON UPDATE | ]
()
Использование опции NO ACTION для любого объявления устанавливает отношения в "нормальный" запрещающий случай, когда в случае наличия записи-потомка и изменения ключа или удаления, выполняемых с записью предка, возникает ошибка.
323
Глава 10 Может показаться странным так использовать NO ACTION, но что делать, если операция удаления не удалась, — мы просто берем NO ACTION и завершаем операцию. Для продолжения нашего примера с ограничением c h i l d $ h a s $ p a r e n t отметим, что после создания записи-потомка мы не могли удалять исходную запись-предка. Модифицируем этот пример, чтобы позволить каскадированные удаления. -- заметим, что здесь нет команды constraint в операторе alter ALTER TABLE child DROP CONSTRAINT child$has$parent ALTER TABLE child ADD CONSTRAINT child$has$parent FOREIGN KEY (parentld) REFERENCES parent ON DELETE CASCADE SELECT * FROM parent SELECT * FROM child
Этот запрос возвращает parentld
parentAttribute
2
parent
childld
parentld
childAttribute
2
2
child
Далее запустим удаление и снова выполним операции SELECT:
I
DELETE from parent SELECT * FROM parent SELECT * FROM child
... что возвратит: parentld childld
parentAttribute parentld
childAttribute
Просто реализовать таким же способом и пример каскадирования обновления. Обратите внимание, однако, что первичный ключ не может быть реализован как столбец идентификации, чтобы обеспечить каскадирование обновления, так как идентифицирующие значения не могут быть изменены.
Взаимные отношения БД Основная трудность с внешними ключами, основанными на ограничениях — то, что таблицы, участвующие в отношениях, не могут охватывать различные БД. Когда возникает эта ситуация, мы должны реализовать наши отношения через триггеры. 324
Планирование и реализация основной физической структуры Вообще-то плохо проектировать БД с взаимными отношениями между ними. БД должна рассматриваться как совокупность связанных таблиц, которые всегда синхронизированы. Когда мы проектируем БД, которые распространяются на различные БД или даже серверы, мы распространяем ссылки на данные, находящиеся не в пределах БД, и не можем гарантировать их существование. Однако бывают случаи, когда взаимные отношения БД неизбежны.
Распределение деталей вашей БД между разработчиками На стадии логического моделирования проекта мы потратили много часов, вводя определения наших таблиц, столбцов и других объектов. В этом разделе мы рассмотрим некоторые средства, которые должны дать нашим клиентам понимание того, что мы создали для них. Эти средства обеспечат отчеты по всем объектам БД и их свойствам, и реализованным, и информационным. Однако построение этих отчетов, поддержание их в актуальном состоянии и распределение их может быть серьезной задачей. На протяжении остальной части книги мы собираемся лишь исследовать средства, которые SQL Server 2000 предоставляет нам. Имеется несколько методов, помещенных в SQL Server: •
информационная схема и системные хранимые процедуры;
•
новые наглядные средства;
а
сервисы метаданных.
Мы детально рассмотрим первые два метода из этого списка, но опустим сервисы метаданных. Они очень ценны, но их непросто использовать. Эти сервисы, прежде всего, используются для обмена метаданными между средствами моделирования — DTS и OLAP и не совсем уместны с точки зрения тематики этой книги. Если вы интересуетесь, то можете выяснить детали относительно сервисов метаданных (Meta Data Services) в SQL Server 2000 Books Online.
Информационная схема и системные хранимые процедуры Чтобы пользователям можно было просто рассматривать структуры ваших таблиц, SQL Server предлагает нам информационную схему (Information Schema) и системные хранимые процедуры (system stored procedures). Информационная схема — набор двадцати представлений, основанных на стандартном (ANSI SQL-92) определении механизма формирования стандартного набора метатаблиц, обеспечивающего разработку с использованием нескольких платформ. Они определяют многие из объектов SQL Server — все, которые понятны для стандарта SQL-92 — и обеспечивают быстрый доступ к структурам наших таблиц. В этих представлениях мы используем несколько отличающийся, но достаточно понятный набор семантических представлений для общих объектов: Название в SQL Server
Название в информационной схеме (SQL-92)
database (БД) owner (владелец) user-defined data type (тип данных, определяемый пользователем)
Catalog (каталог) Schema (схема) Domain (домен)
325
Глава 10 Следующая таблица дает список процедур метаданных и соответствующих представлений информационной схемы (INFORMATION_SCHEMA). Объяснение, которое дается в третьем столбце таблицы, — просто краткое примечание, для чего используются различные процедуры и представления. Для любого программиста БД желательно хорошо понимать, как использовать эти объекты, чтобы запросить метаданные модели. Лучший способ для этого — посмотреть полную информацию в SQL Server 2000 Books Online. Таким образом, SQL Server предлагает два совершенно разных способа просмотра тех же самых системных данных. Сравнивая два различных метода, можно выделить два момента, которые важны для меня. Системные хранимые процедуры — сильно ориентируемы на реализацию в SQL Server. Представления информационной схемы дают разработанный общими усилиями взгляд, который может подойти не для каждого. Однако определения, которые они дают, хотя и специфические, но все-таки очень полезны. По моему мнению, лучше использовать представления информационной схемы всегда, когда это возможно, потому что они основаны на стандарте SQL 92 и будут, вероятно, улучшены в будущем. Кроме того, так как каждая БД, созданная на основе SQL 92, будет в конечном счете иметь идентичный набор реализованных представлений, использование этих представлений должно облегчить понимание других систем БД. Системная хранимая процедура
Соответствующее представление информационной схемы
sp s e r v e r info (информация сервера)
отсутствует
sp d a t a b a s e s (базы данных)
SCHEMATA (схемы)
•
sp t a b l e s (таблицы)
326
TABLES, VIEWS (таблицы, представления)
Объяснение
Дает список настроек и информацию, связанную с сервером. Информация о сервере не может быть помещена в информационную схему. Оба дают список БД и серверов. Список представления SCHEMATA содержит более полезную информацию о создателе БД, в то время как sp databases дает размеры БД. Дает список таблиц и представлений в системе. sp t a b l e s содержит системные таблицы, таблицы пользователя и представления. Как ни странно, TABLES • содержит перечень таблиц и представлений, a VIEWS — только представления. Оно содержит расширенную информацию относительно представлений, такую как обновляемость, определения, контролируемые параметры и некоторые другие характеристики.
Планирование и реализация основной физической структуры
Системная хранимая процедура
Соответствующее представление информационной схемы
Объяснение
sp columns (столбцы)
COLUMNS (столбцы)
sp s t o r e d procedures (хранимые процедуры)
ROUTINES (подпрограммы)
В то время как процедура sp colunms требует параметра, характеризующего таблицу, для которой она используется, представление COLUMNS этого не требует; оба содержат полезную и интересную информацию относительно столбцов. Оба перечисляют хранимые процедуры и функции, но sp s t o r e d procedures имеет несколько столбцов, которые еще не были реализованы, a ROUTINES — все. Оба этих метода имеют подсказку, как войти в SQL и SQL Server.
sp sproc columns (параметры хранимых процедур)
PARAMETERS (параметры)
Оба перечисляют параметры хранимых процедур и функций.
Отсутствует
ROUTINE_COLUMNS (столбцы подпрограмм)
Перечисляет столбцы таблиц, возвращаемых функциями, конечно, когда они возвращают таблицы.
VIEWS, ROUTINES (представления, подпрограммы)
sp h e l p t e x t — одна из тех процедур, которая должна быть понятна любому администратору. Используя эту процедуру, вы можете получать текст любой процедуры, значения по умолчанию, расширенной хранимой процедуры (которая возвращает используемую DLL) или функции, находящейся в системе. VIEWS и ROUTINES похожи, поскольку они также возвратят характеристики представления или подпрограммы, наподобие справочного текста.
sp helptext (справочный текст)
'
Продолжение таблицы на следующей странице 327
Глава 10
328
Системная хранимая процедура
Соответствующее представление информационной схемы
Объяснение
sp column p r i v i l e g e s (права доступа к столбцам)
COLUMN_PRIVILEGES (права доступа к столбцам)
Оба перечисляют права доступа к столбцам таблицы.
sp s p e c i a l columns (специальные столбцы)
Отсутствует
Возвращает первичный ключ таблицы.
sp s t a t i s t i c s (статистика)
Отсутствует
sp s t a t i s t i c s отображает информацию об индексах таблицы. В информационной схеме нет информации об индексах.
sp fkeys (внешние ключи)
TABLE_CONSTRAINTS, CONSTRAINT_TABLE_USAGE, REFERENTIAL CONSTRAINTS, KEY COLUMNJJSAGE (ограничения таблицы, использование ограничений таблицы, ссылочные ограничения, использование ключевых столбцов)
sp_f keys дает список ограничений за счет внешних ключей таблицы. TABLE CONSTRAINTS и CONSTRAINT_TABLE_USAGE перечисляют все ограничения таблицы, первое — возможные, а второе — действительные. REFERENTIAL CONSTRAINTS перечисляет все ограничения БД за счет внешних ключей. KEY_COLUMN_USAGE перечисляет столбцы в первичном и внешних ключах, обеспечивающих ограничения.
sp_pkeys (первичные ключи)
KEY_COLUMN_USAGE, TABLE_CONSTRAINTS (использование ключевых столбцов, ограничения таблицы)
sp pkeys перечисляет первичные ключи таблицы. KEY_COLUMN USAGE и TABLE_CONSTRAINTS у ж е упоминались в предыдущем разделе sp fkeys.
sp h e l p c o n s t r a i n t (справка об ограничениях)
REFERENTIAL_CONSTRAINTS, CHECK_CONSTRAINTS, TABLE_CONSTRAINTS, CONSTRAINT_COLUMN_USAGE, CONSTRAINT_TABLE_USAGE (ссылочные ограничения, проверка ограничений, ограничения таблиц, использование ограничений столбцов, использование ограничений таблиц)
sp h e l p c o n s t r a i n t дает перечень ограничений таблицы. Все элементы информационной схемы уже были обсуждены.
Планирование и реализация основной физической структуры
Системная хранимая процедура
Соответствующее представление информационной схемы
Объяснение
sp table privileges (права доступа к таблицам)
TABLE_PRIVILEGES (права доступа к таблицам)
sp table privileges дает список прав доступа для данной таблицы, которые можно предоставлять. TABLE_PRIVILEGES отображает по одной строке на каждое право доступа, предоставленное пользователю. sp t a b l e p r i v i l e g e s и TABLE PRIVILEGES связаны, но — фактически несут разную информацию. sp table_privileges говорит о том, что можно предоставлять, в то время к а к TABLE_PRIVILEGES связано с тем, что было предоставлено.
sp datatype info (информация о типах данных)
DOMAINS (домены)
sp datatype info перечисляет все типы данных, которые используются в системе и ее свойствах, в то время как DOMAINS отображает только типы данных, определяемые пользователем.
Отсутствует
DOMAIN CONSTRAINTS, COLUMN_DOMAIN_USAGE (ограничения доменов, использование доменов столбцов)
Два дополнительных представления добавлены для доменов и могут быть действительно удобны. DOMAIN CONSTRAINTS перечисляет все ограничения (правила), которые были добавлены к домену. COLUMN DOMAIN_USAGE перечисляет все таблицы, где использовался домен.
Имеется некоторая информация в этой группе функций, которую мы не обсудили, в первую очередь связанная с функциями, обеспечивающими права доступа и которые мы будем рассматривать в главе 12.
329
Глава 10 Оставим читателю разобраться, как реализовать выполнение процедур, просмотр таблиц и изучение SQL Server 2000 Books Online, раздел System Stored Procedure (Системные хранимые процедуры) (особенно catalog procedures — процедуры каталога, но лучше изучить все), а также раздел Information Schema Views (Представления информационной схемы). С • комбинацией процедур каталога, объектов информационной схемы и функций T-SQL мы можем просмотреть практически любую часть метаданных, содержащихся в БД, которую создали.
Новые описательные свойства Во время нашего моделирования мы создали описания, записи и различные наборы данных, которые являются чрезвычайно полезными для разработчика, чтобы понять все "почему" и "для чего" использования таблиц, которые мы создали. В предыдущих версиях SQL Server было очень трудно фактически использовать эти данные. В SQL Server 2000 фирма Microsoft ввела расширенные свойства, которые позволяют нам размещать определенную информацию об объектах. Это действительно хорошо, потому что позволяет нам расширить метаданные наших таблиц способами, которые могут использоваться нашими приложениями с помощью простых SQL-операторов. Создавая эти свойства, мы можем формировать хранилище информации, которое прикладные разработчики могут использовать для следующих целей: • •
чтобы понять, какие данные используются в столбцах; чтобы разместить информацию, используемую в приложениях, например: •
заголовки, чтобы показать их форму при отображении столбцов;
•
сообщения об ошибках, выводимые при нарушении ограничений;
•
правила форматирования для отображения вводимых данных.
Расширенные свойства размещаются как столбцы THnasql_variant, так что они могут содержать любые типы данных кроме полей типа t e x t или image. С целью подключения расширенных свойств объекты в SQL Server классифицируются на три уровня, называемые схемой. Следующее дерево иллюстрирует эту иерархию: Уровень 0
Уровень 1
Уровень 2
User (пользователь)
Table(таблица)
Column (столбец) Index (индекс) Constraint (ограничение) Trigger (триггер)
View
(представление)
Column (столбец) Index (индекс — только схемносвязанные представления) Trigger (триггер — только INSTEAD OF
Stored procedure (хранимая процедура)
Parameter (параметр)
Rule (правило) Default (значение по умолчанию) Function (функция)
Column (столбец) Constraint (ограничение) Parameter (параметр)
3 3 0
Планирование и реализация основной физической структуры
Обратите внимание: в этой диаграмме мы упомянули новый термин: схемно-связанный. Связывание схемы означает механизм, который является новым в SQL Server 2000: когда вы создаете схемно-связанное представление или схемно-связывающую функцию, определяемую пользователем, то можете проинструктировать SQL Server не допускать никаких изменений основных таблиц, которые оно (представление) или она (функция) использует. Мы рассмотрим это более детально в следующей главе.
Только для этих объектов мы можем определять расширенные свойства, поскольку SQL Server проверит имена, которые мы используем для этих свойств. Схема присвоения имен довольно проста. Мы можем добавить свойство пользователю, что не требует никакой другой информации, но чтобы добавить свойство к таблице, мы сначала должны иметь ссылку на пользователя, который владеет таблицей. Чтобы добавить свойство к столбцу, мы должны знать пользователя, который владеет таблицей, а также имя таблицы и имя столбца. Чтобы обслуживать расширенные свойства, мы имеем следующие функции и хранимые процедуры: •
sp_addextendedproperty — используется для добавления нового расширенного свойства;
•
sp_dropextendedproperty — используется для удаления существующего расширенного свойства;
Q
sp_updateextendedproperty — используется для корректировки существующего расширенного свойства;
•
fn l i s t e x t e n d e d p r o p e r t y — системная функция, которая может использоваться, чтобы сформировать список расширенных свойств.
В следующем примере мы рассмотрим конкретный синтаксис каждой команды, когда будем использовать ее, но важно понять основной путь, в соответствии с которым они работают. Каждая имеет следующие параметры: •
@name — имя определяемого пользователем свойства;
•
@value — какое задать значение при создании или изменении свойства;
•
QlevelOtype — или тип пользователя, или определяемый пользователем тип данных;
•
@ l e v e l 0name — имя объекта того типа, который определен параметром @levelOtype;
•
@levelltype — имя типа объекта, который находится на 1-м уровне ветви дерева непосредственно под параметром user, типа Table, View и т. д.;
331
Глава 10 •
@levellname — название объекта, имеющего тип, который задан в параметре Qlevelltype;
•
@level2type — имя типа объекта, который находится на 2-м уровне ветви дерева под значением, заданным параметром Qlevelltype; например, если @levelltype Table, то @level2type может быть Column, Index, Constraint или Trigger;
•
@level2name — имя объекта, имеющего тип, который задан в параметре @level2type.
Например, пусть в следующем фрагменте м ы создаем таблицу, которую назовем person (человек): CREATE TABLE person ( personld int NOT NULL IDENTITY, firstName varchar(40) NOT NULL, lastName varchar(40) NOT NULL, socialSecurityNumber varchar(lO) NOT NULL
Чтобы сделать это, мы собираемся добавить к таблице и столбцам свойство по имени MS_DESCRIPTION. Почему MS_DESCRIPTION? Потому что это — имя, которое выбрано фирмой Microsoft, чтобы использовать в Enterprise Manager для размещения описаний. Обратите внимание, что это имя может меняться. Я нашел имя свойства, используя SQL Profiler, чтобы проследить, как SQL Server сохранил свойство, а затем сформировал свою собственную версию сценария. Профилирование таким же образом, как это делает Enterprise Manager, является быстрым способом определить, как описать задачи, которые, казалось бы, возможно сделать, используя только средства графического пользовательского интерфейса (GUI— Graphical User Interface). Итак, мы выполняем следующий фрагмент после создания таблицы: -- GUI-описание таблицы dbo.person (таблица person владельца dbo) EXEC sp_addextendedproperty @name = 'MS_Description', @value = 'Пример расширенных свойств в таблице person', @levelOtype = 'User', @levelOname - 'dbo', @levelltype = 'table', @levellname = 'person' -- GUI-описание dbo.person.personld (атрибут personld -- таблицы person владельца dbo) EXEC sp_addexendedproperty @name = 'MS_Description', @value • 'указатель первичного ключа на экземпляр записи в таблице person', OlevelOtype = 'User', @levelOname = 'dbo', glevelltype • 'table', @levellname = 'person', @level2type = 'column', @Level2name = 'personld' —
332
GUI-описание dbo.person.firstName
(атрибут firstName
Планирование и реализация основной физической структуры -- таблицы person владельца dbo) EXEC sp_addexendedproperty @name = 'MS_Description', @value = 'Имя человека', SlevelOtype'User', @levelOname = 'dbo', @levelltype = 'table', @levellname = 'person', @level2type • 'column', @Level2name = 'firstName' — GUI-описание dbo.person.lastName (атрибут lastName -- таблицы person владельца dbo) EXEC sp_addexendedproperty @name = 'MS_Description', @value = 'Фамилия человека', @levelOtype = 'User', dlevelOname = 'dbo', @levelltype = 'table', @levellname = 'person', @level2type = 'column', @Level2name = 'lastName' — GUI-описание dbo.person.socialSecurityNumber (атрибут — socialSecurityNumber таблицы person владельца dbo) EXEC sp_addexendedproperty @name = 'MS_Description', @value = 'описание столбца с номерами социального обеспечения' @levelOtype = 'User', @levelOname = 'dbo', @levelltype = 'table', @levellname = 'person', @level2type = 'column', @Level2name = 'socialSecurityNumber' Теперь войдем в Enterprise Manager, щелкнув правой клавишей м ы ш и на нашей таблице и выбрав Design Table (схема таблицы); м ы увидим наше описание следующим образом: •ж SQL Server Enterprise Manager - [3:Design Table 'perso. 2 Console
I
Window
Column Name
Help
Data Type
I Length j Allow Nulls
Ь personld
f irstName ; varcbar lastName varchar socialSecurityNumber varcbar
ID
Columns Description
primary key pointer to a person instance
Identity Identity Seed Identity Increment
Yes 1 1
Is ' • ,u..i . ... Formula: •
r:> '
333
Глава 10
А взглянув на свойства таблицы (щелкнув иконку справа от Save — сохранить) мы заметим, что теперь имеем описание: Properties Tabe l s j Relationships ] In «d e* s/Key$ Check Constraints j 13
Selected table: Qwner; Tablename:
jgdbo ; person
: Table:identity Cou l mn: personld
zl
Tabe l ROWGU D I Cou l mn:
d
Tabe l Ftfegroup:
PRM I ARY
d
Text Filegroug: ;
PRM I ARY
zl
Description:
Exampe l of extended properties on the J person table J
Co i se
Help
Очевидно, это не совсем то, что м ы обычно делаем, но если м ы имеем усовершенствованные возможности в наших средствах моделирования данных, то должны быть способны выводить эти данные, используя Enterprise Manager, который намного более полезен как средство для наших программистов. Чтобы изменить свойство, м ы могли бы использовать следующий тип сценария: -- смотрим, существует ли свойство IF EXISTS (SELECT * FROM : :FN_LISTEXTENDEDPROPERTY('MS_Description' , 'User', 'dbo', 'table', 'person', 'column', 'socialSecurityNumber') ) BEGIN -- если свойство уже существует EXEC sp_updateextendedproperty @name = 'MS_Description', @value = 'государственный идентификационный номер человека', @levelOtype = 'User', SlevelOname = 'dbo', @levelltype = 'table', @levellname = 'person', @level2type = 'column', @level2name = 'socialSecurityNumber' END ELSE BEGIN -- в противном случае создать его EXEC sp_addexendedproperty @name = 'MS_Description', @value = 'государственный идентификационный номер человека'.
334
Планирование и реализация основной физической структуры @levelOtype = 'User', @levelOname • 'dbo', @levelltype = 'table', @levellname = 'person', @level2type = 'column', @level2name = 'socialSecurityNumber'
I
END Объект fn_listExtendedProperty (список расширенных свойств) является системной функцией, которую мы рассмотрим позже в главе 12.
Наше последнее дополнение к этому примеру должно добавить расширенное свойство к таблице, которое процессы пользователя могут использовать при выполнении программы. Для этого мы добавим свойство, представляющее собой маску для ввода в таблицу, чтобы мы могли создать общий метод для ввода данных в столбец.
I
EXEC sp_addexendedproperty @name = 'Маска ввода', @value = ' * * * - * * - * * * * ' , @levelOtype • ' U s e r ' , @levelOname = ' d b o ' , @levelltype = ' t a b l e ' , @levellname = ' p e r s o n ' , @level2type = 'column', @level2name = 'socialSecurityNumber'
Заметьте, что мы можем создавать любое расширенное свойство, которое пожелаем. Названия должны вписаться в тип данных sysname (системное имя), и могут включать пробелы, неалфавитно-цифровые символы или даже двоичные значения. Эта новая особенность документации имеет некоторые интересные возможности для создания приложений и использования большей части метаданных, которые мы хранили в различных местах, даже не используя особенно важную документацию. Дополнительную информацию можно получить в SQL Server 2000 Books Online, раздел Using Extended Properties on Database Objects (Использование расширенных свойств в объектах баз данных).
Учебный пример Возвращаясь к нашему учебному примеру, мы теперь должны заняться созданием физических структур, чтобы воплотить теорию в практику. Будем следовать нашей протоптанной дорожке, когда мы определяем ситуации, которые могут быть слишком трудными для реализации, и устраняем их, затем выбираем первичные ключи и типы данных и, наконец, представляем физическую модель, которую мы создадим. Будем использовать макроязык T-SQL для реализации нашей БД, включая добавление описательной информации из нашего исходного анализа.
Очистка модели Первым шагом должна быть корректировка любых мест в модели, которые являются слишком трудными для реализации. В некоторых случаях нам придется опустить большие области реализации, которые м ы хотели бы выполнить, естественно, получая при этом не лучшее решение. В зависимости от ситуации, средств или даже общих стандартов нам, вероятно, придется изменять пути реализации отдельных частей, хотя мы должны, в конце концов, получить решение, которое является функционально эквивалентным логической модели. Каждое преобразование, которое мы делаем в модели во время стадии физического моделирования, не должно изменить набор данных, которые размещаются в нашей БД, если только м ы не обнаружим в дальнейшем атрибутов, которые следует добавить к логической модели. 12 1868
335
Глава 10
Подтипы Одна из наиболее трудных ситуаций для реализации в SQL Server — это наличие подтипов. В логической модели (которую мы закончили создавать в конце главы 8) имеется пример как раз такого случая: подтип сделки. Мы моделировали его как полный подтип, то есть, все возможные случаи были представлены в диаграмме. transaction bankld: PrimaryKey(FK) (AK1.1) accountld: PrimaryKey (FK)(AK1.2) transactionld: PrimaryKey date: Date number: AlphaNumerlcNumber (AK1.3) description: Description amount: Amount type: Code deposit | bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) transactionld: PrimaryKey (FK)
Type
directWithdra wal bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) transactionld: PrimaryKey (FK) userld: PrimaryKey (FK)
check bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) transactionld: PrimaryKey (FK) payeeld: PrimaryKey (FK) usageType: Code ^signature: Blob
checkUsage имеет использование, определенное в
checkllsageType
userid: Primarykey user name: UserName ofirstName: FirstName middleName: Md i de l Name lastName: LastName
bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) transactionld: PrimaryKey (FK) checkllsageTypeld: PrimaryKey (FK)
задает категории чека с помощью
checkUsageTypeld: PrimaryKey name: Name description: Description parentCheckUsageTypeld: PrimaryKey (FK)I
(Обозначения см. в главах 5 и 7.) Однако мы не определили никаких дополнительных атрибутов для сущностей d e p o s i t (депозит) и directWithdrawal (прямое изъятие), чтобы добавить их к модели. В этом случае, мы могли просто заменить наш подтип на неполный вариант (где не всякое значение подтипа фактически реализовано таблицей) и удалить все, что связано с понятиями "депозит" и "прямое изъятие". В зависимости от того, насколько велик подтип (один или два атрибута или больше плюс связанные таблицы), мы можем пожелать назначить эти атрибуты предку отношения. Именно это мы сделаем в нашем примере. Основная причина здесь — практичность, так как каждый из подтипов очень близок друг к другу. Позже, мы должны будем написать некоторый специальный код, чтобы защитить данные от неправильного ввода в таблицы подтипа. Основываясь на значении дискриминатора, в подтипе довольно очевидно, какие поля действительно имеют силу (в этом случае дискриминатор — тип сделки, потому что он определяет, в какой таблице-потомке мы должны искать значения подтипа), но как только мы берем все атрибуты всех сущностей подтипа и возвращаем их предку, это может стать менее очевидным. В нашем случае мы имеем три атрибута, которые можно использовать: 336
Планирование и реализация основной физической структуры •
payee (получатель платежа) — чеки всегда имеют получателей платежа. Прямые изъятия могут их иметь, но депозиты никогда не имеют получателей платежа. Мы могли бы увеличить наши таблицы, чтобы разместить информацию о том, откуда поступают деньги на депозит, но не будем делать этого, потому что хотим ограничить размер проекта и потому что такое изменение нарушило бы планы реализации, которые были предварительно одобрены клиентом.
•
s i g n a t u r e (подпись) — только чеки имеют подписи. Этот атрибут кажется несколько бессмысленным, но мы оставим его как пример. Если бы мы эту систему реализовывали полностью, данный атрибут, вероятно, соответствовал бы большому дереву доменов и принадлежал бы депозитам, прямым изъятиям и чекам. Однако мы будем использовать логику предыдущего пункта и скажем, что, поскольку клиент не просил об этом, мы не будем делать это. Мы также изменим эту величину до строки, потому что будем только хранить полное имя человека, который подписал чек, а не изображение подписи. Конечно, если бы это была реальная система, мы, видимо, спросили бы клиента, являются ли эти корректировки приемлемыми и, может быть, приняли бы другое решение.
•
checkUsageType (тип использования чека) — этот атрибут довольно прост; мы изменим его на TransactionAllocationType (тип размещения сделки), где пользователь может установить использование/размещение для каждого типа сделки.
Итак, мы реконструируем таблицы следующим образом: payee transaction воздействует на фонды payeeld: PrimaryKey bankld: PrimaryKey (FK)(AK1.1) через accountld: PrimaryKey (FK)(AK1.2) • - о name: Name(AK1.1) transactionld: PrimaryKey date: Date информацию number: AlphaNumerc i Number (AK1.3) имеет о размещении в description: Description amount: Amount type: Code __ T I I 1 transactionAllocation i модифицирует баланс счета через согласование с i 1
Л userid: Primarykey user name: UserName firstName: RrstName middleName: Md i de l Name lastName: LastName
transactionAllocationTypeld: PrimaryKey (FK) bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) transactionld: PrimaryKey (FK) allocationAmount: Amount
задает категории чека
transactionAllocationType transactionAllocationTypeld: PrimaryKey name: Name description: Description parentCheckUsageTypeld: PrimaryKey (FK) V
t- — 'i I
1 является
I I I
Здесь дополнительно transactionAllocation — таблица "размещение сделки", transactionAllocationTypeld — идентификатор типа размещения сделки, bankld идентификатор банка, accountld — идентификатор счета, transactionld — идентификатор сделки, allocationAmount — сумма размещения.
—
337
Глава 10 Нужно отметить следующий момент. Таблица сделок не может быть названа t r a n s a c t i o n (сделка), потому что это зарезервированное слово, и SQL Server не позволяет использовать его в коде. В английском языке transaction — и банковская сделка, и транзакция операция). Прим. перев.
(групповая
Мы имеем несколько вариантов, как поступить в этом случае: •
дать таблице другое имя, например, x a c t i o n (х-операция);
•
всегда обращаться к таблице с названием в скобках: [ t r a n s a c t i o n ] .
Ни один из них не очень хорош, и t r a n s a c t i o n (сделка) было бы лучшим именем для таблицы и обращающихся к ней таблиц. Мы выберем имя t r a n s a c t i o n и использование скобок при обращении к таблице. Это сохранит имя таблицы более понятным для клиента. Однако будем ссылаться на таблицу с помощью [ t r a n s a c t i o n ] только тогда, когда используем это в коде. Посмотрим, как реализовать конкретные правила формирования данных, которые мы будем использовать в следующей главе.
Упрощение сложных отношений В некоторых случаях мы будем моделировать объекты, выбранные на стадии логического проектирования, которые являются несколько сложными для практической реализации. В нашей логической модели мы имеем одну ситуацию, которую могли бы посчитать слишком трудной для реализации в таком виде, как мы ее разработали. Чтобы иметь уникальный почтовый адрес, мы должны просмотреть две таблицы. В нашей модели таблицы address (адрес) и addressLine (строка адреса) имеют вид: address addressld: PrimaryKey cityld: PrimaryKey (FK) stateld: PrimaryKey (FK) zipCodeld: PrimaryKey (FK)
addressLine имеет дополнительную информацию в
addressld: PrimaryKey (FK) addressLineld: PrimaryKey addressLine: AddressDescriptiveLine sortOrderld: SortOrderld J
Они учитывают адрес неограниченного размера и структуры, что часто не является необходимым для физической реализации. Так что мы просто реорганизуем таблицу address следующим образом: address addressld: PrimaryKey addressLine: AddressDescriptiveLine (AK1.1) cityld: PrimaryKey (FK) (AK1.2) stateld: PrimaryKey (FK) (AK1.3) zipCodeld: PrimaryKey (FK) (AK1.4) Поле addressLine (строка адреса) используется и для названия улицы, и почтового ящика, и т. д., размер которого мы определим, когда выберем типы данных.
338
Планирование и реализация основной физической структуры Таблицы доменов В таблице t r a n s a c t i o n (сделка) мы имеет столбец type (тип), чтобы задать тип сделки. transaction 'bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) transactionld: PrimaryKey date: Date number: Number description: Description amount: Amount type: String signature: String ijDayeeld: PrimaryKey (FK) Обычно мы не будем использовать поле ввода данных в свободной форме, когда нам нужно группировать поля, или, как в случае столбца type таблицы t r a n s a c t i o n , где мы должны будем, вероятно, использовать значение домена для специальной обработки в нашем пользовательском интерфейсе. Мы хотели бы даже добавить характерную для реализации информацию к нашей таблице, содержащей тип адреса, наподобие расположения имен файлов с иконками, так, чтобы пользовательский интерфейс мог выполнить некоторую проверку типа чека, кредитной карточки при прямом изъятии и т. д. В нашей таблице t r a n s a c t i o n T y p e (тип сделки) мы добавляем столбцы name (имя) и d e s c r i p t i o n (описание). Таким образом мы можем включить большее количество информации, чтобы объяснить, что каждое значение в таблице означает. Снова каждый из этих атрибутов следовало бы показать клиенту и, возможно, добавить к логической модели. Мы видим эти изменения плюс три других поля логического типа в следующей диаграмме: transaction 'bankld: PrimaryKey (FK) (AK1.1) ? accountld: PrimaryKey (FK)(AK1.2) transaction^: PrimaryKey date: Date number: AlphaNumericNumber (AK1.3) description: Description amount: Amount signature: String payeeld: PrimaryKey (FK) userld: PrimaryKey (FK) statementld: PrimaryKey (FK) .statementltemld: PrimaryKey (FK)
классифицирует
transactionType transactionType Id: PrimaryKey name: Name description: Description requiresSignature:FI: Fl requiresPayeeFI: Fl allowsPayeeFI: Fl
у
Здесь в дополнение к предыдущим обозначениям transactionType — таблица "тип сделки", transactionTypeld — идентификатор типа сделки, name — название (сделки), description — описание, requiresSignatureFl — флаг необходимости подписи, requiresPayeeFI — флаг необходимости иметь получателя платежа, allowsPayeeFI — флаг возможности иметь получателя платежа. Теперь мы можем писать код, который вынуждает заполнять поле s i g n a t u r e (подпись) для некоторого типа сделки и может определить также, обязателен ли получатель платежа. 339
Глава 10 Построение пользовательского интерфейса будет более легким, потому что мы имеем данные, которые сообщат нам, когда требуется значение, и изменение требований не будет приводить к написанию дополнительного кода. Каждое из этих полей флагов, вероятно, лучше рассматривать как логические поля, потому что они поддерживают реализацию информации уже на текущей стадии проектирования. Мы будем также использовать эту таблицу как наш тип сделок для таблицы s t a t e m e n t l t e m (элемент отчета) (хотя в этом случае мы не будем использовать поля флагов). Так как наши типы сделок будут характерны для банка, это оградит нас от наличия двойных данных в таблице, плюс это будет обеспечивать соответствие элементов во время балансирования счета намного более простым, но мы не будем, конечно, требовать, чтобы типы сделок совпадали.
Удаление ненужных сущностей После дальнейшего просмотра мы замечаем, что таблицаcheckRegister на самом деле не будет реализована как таблица вообще, так что будет только электронный регистр чека, как только будет создана БД. Поэтому мы удалим ее из нашей физической модели, хотя она все еще имеет значение в логической модели, поскольку это — фактическая сущность, с которой нам, вероятно, придется иметь дело. checkRegister checkRegisterld: PrimaryKey registerType:
Здесь checkRegister — сущность "Проверка регистра"; checkRegisterld — идентификатор проверки регистра; registerType — тип регистра.
Коррекция ошибок в модели Когда мы выполняем этот процесс, то найдем ошибки в модели, даже если мы используем лучшие средства моделирования и работаем не спеша. Например, в исходной модели мы имели следующие отношения: bank bankld: PrimaryKey name: Name (AK1.1) предлагает account bankld: PrimaryKey (FK) (AK1.1) имеет баланс счета в accountld: PrimaryKey number: AlphaNumericNumber (AK1.2) accountReconcile bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) statementld: PrimaryKey (FK) reconcileDate: Date periodEndDate: Date
3 4 0
Используется для согласования счета с помощью
bankld: PrimaryKey (FK)(AK1.1) statementld: PrimaryKey type: Code previousBalance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.2) totalDebits: Amount totalCredits: Amount
Планирование и реализация основной физической структуры Здесь ошибка в отношении между таблицами bank (банк) и s t a t e m e n t (отчет): "банки посылают отчеты". Отношение должно быть "банки, посылают отчеты для счетов" и поэтому при реализации отношение между bank и s t a t e m e n t должно быть заменено отношением между account (счет) и s t a t e m e n t . Мы перемещаем столбец periodEndDate (период до последней даты) в таблицу s t a t e m e n t , поскольку это будет часть отчета, а не значение, которое мы размещаем, когда отмечаем, что отчет выверен. Мы также опустим отношение между таблицами account и accountReconcile (согласование счета), так как будем отмечать отчет как выверенный. bank bankld: PrimaryKey name: Name(AK1.1) предлагает account bankld: PrimaryKey (FK) (AK1.1) accountld: PrimaryKey number: AlphaNumericNumber (AK1.2)
совпадает с данными записей в классифицирует
statementTypei statementTypeld: PrimaryKey name: Name description: Description
statement statementid: PrimaryKey bankld: PrimaryKey (FK) (AK1.2) accountld: PrimaryKey (FK) statementTypeld: PrimaryKey (FK) previousBalance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.1) totalDebits: Amount totalCredits: Amount используется для согласования счета с помощью accountReconcile bankld: PrimaryKey (FK) accountld: PrimaryKey (FK) statementid: PrimaryKey (FK) reconcileDate: Date periodEndDate: Date
Один из интересных моментов этой модели заключается в том, что для выяснения, когда счет был последний раз выверен, мы должны пройти через таблицу s t a t e m e n t к таблице accountReconcile. Хотя это и может казаться странным непосвященным в программирование БД с использованием нормализации, однако это является одним из контрольных признаков хорошо разработанной БД. В большей системе вам пришлось бы пройти пять, десять или двадцать таблиц, чтобы выяснить информацию, подобную этой. Принцип наличия одной части данных, размещенной в одном месте, может делать БД более сложными для прослеживания данных, но, с другой стороны, устранит избыточные данные и аномалии корректировки — если номер счета размещен более чем в одном месте, и потребуется его изменить, то изменение должно быть сделано в каждом месте, иначе ваши данные станут противоречивыми. Как мы увидим в следующей главе, чтобы не было возможности получения случайного беспорядка наших данных, придется выполнить дополнительное внутреннее кодирование. Вы не должны рассматривать обнаружение ошибок, наподобие этой, в вашей модели как неудачу. Очень часто мы будем пропускать материал, когда выполняем стадию проектирования. Часто наши клиенты будут изменять свои требования к БД, с которыми мы должны иметь дело. Ошибки не могут быть исключены, поскольку каждый архитектор БД — человек. Часто они незаметны, пока вы не закончили первый набросок проекта и можете определять более сложные проблемы и рассматривать их решение. Иногда одно проектное решение может определять или изменять несколько других. Проектирование БД всегда является итерационным процессом.
341
Глава 10
Подготовка модели для реализации Как только мы разобрались с логической моделью так, как только что отметили, можно заняться подготовкой наших таблиц для создания. Здесь мы должны предпринять шаги, чтобы сделать систему легкой для создания, добавляя столбцы для оптимистической блокировки и запрета работы с записью (и что-нибудь еще, удовлетворяющее ваши специфические потребности). Затем мы добавим первичные ключи, уникальные ключи и, наконец, выберем типы данных. Как только мы завершим эти заключительные задачи, мы будем готовы к написанию наших объектов БД и реализации БД на SQL Server.
Служебные столбцы Кратко напомним, что служебные столбцы не относятся ни к каким физическим объектам БД. Они просто включены в БД, чтобы сделать кодирование более легким. Два примера, которые мы ранее рассмотрели, можно учесть в следующей таблице: ! payee payeeld: PrimaryKey name: Name (AK1.1) date: disableDate autoTimestamp: autoTimestamp В этой таблице мы имеем столбец типа d i s a b l e D a t e (дата с возможностью запрета) в противоположность d i s a b l e F l (флаг запрета), который мы ранее обсудили. Параметр типа d i s a b l e D a t e устанавливает дату и время, после которого получатель платежа больше не может получать суммы денег по сделкам в нашей БД. Мы могли бы также добавить таблицы, чтобы хранить причины этого и историю состояния получателя платежа, но мы не будем здесь это делать, чтобы упростить задачу. Параметр типа d i s a b l e D a t e работает так же, как d i s a b l e F l , за исключением того, что вместо простого флага мы реализуем дату, так что мы можем видеть, какие будущие дату и время мы устанавливаем. Мы также добавили столбец autoTimestamp (автоматическое время создания/обновления) к каждой таблице модели, который, как мы предварительно обсудили, формирует основы механизма оптимистической блокировки. Они — исключительно служебные столбцы, которые мы будем добавлять к нашей текущей БД.
Первичные ключи Столбцы первичного ключа — фактически тоже служебные столбцы, так как мы никогда не будем показывать их пользователю, и они существуют прежде всего для нужд нашей реализации, хотя мы и представили их и на логической, и на физической моделях. Возьмем следующий фрагмент модели: 342
Планирование и реализация основной физической структуры
bank bankd i : Prm i aryKey name: Name(AK1.1)
предлагает
bankid: PrimaryKey (FK) (AK1.1) accountld: PrimaryKey number: AlphaNumericNumber (AK1.2)
совпадает с данными
statementld: PrimaryKey bankid: PrimaryKey (FK) (AK1.2) accountld: PrimaryKey (FK)
имеет элементы в
statementTypeld: PrimaryKey (FK) previousBalance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.1) totalDebits: Amount totalCredits: Amount
statementltem statementld: PrimaryKey (FK) bankid: PrimaryKey (FK) accountld: PrimaryKey (FK) statementltemld: PrimaryKey date: Date number: AlphaNumericNumber description: Description amount: Amount
Указатель bankid (идентификатор банка) мигрирует из таблицы bank (банк) всюду до таблицы s t a t e m e n t l t e m (элемент отчета). Это прекрасно для логического моделирования, но ключи в физических таблицах, состоящие из нескольких частей, вызывают больше неприятностей, чем имеют достоинств. Главная причина здесь заключается в том, что нам может потребоваться два, три или даже большее количество столбцов, чтобы уникально идентифицировать записи таблицы. Если вам нужно присоединить десять таблиц, чтобы найти связанную информацию, то это будет неприятно. Таким образом, если наша БД была реализована указанным выше образом, и мы задали вопрос — "В каких городах в Первом Национальном Банке мы потратили большую часть денег на текстиль?", то этот запрос включил бы каждую таблицу, если бы мы использовали все ключи, состоящие из нескольких частей, и запрос оказался бы неуправляем. Другая причина для создания первичного ключа, состоящего из служебного столбца, состоит в том, что если вы изменяете состав логического первичного ключа для объекта, то не затрагиваете реализованный первичный ключ объекта. Это уменьшит влияние изменения на любой зависимый объект. В нашем примере возьмем набор отношений 'bank - account - s t a t e m e n t statementltem". Если мы решили, что таблица bank больше не нужна, и удалили таблицу из модели, таблицы account, statement и statementltem изменят свои первичные ключи: совпадает с данными bankid: PrimaryKey (FK) (AK1.1) записей в accountld: PrimaryKey number: AlphaNumericNumber (AK1.2) statement statementld: PrimaryKey имеет элементы в bankid: PrimaryKey (FK) (AK1.2) accountld: PrimaryKey (FK) statementTypeld: PrimaryKey (FK) statementltem statementld: PrimaryKey (FK) previousBalance: Amount bankid: PrimaryKey (FK) previousBalanceDate: Date accountld: PrimaryKey (FK) currentBalance: Amount statementltemld: PrimaryKey statementDate: Date (AK1.1) totalDebits: Amount date: Date totalCredits: Amount number: AlphaNumericNumber description: Description amount: Amount
343
Глава 10 Это — не оптимальное решение нашей проблемы кодирования, потому что весь код, который использовал эти сущности, будет теперь нарушен. Однако если бы каждая таблица имела первичный ключ, состоящий из одного столбца, только таблицу account нужно будет изменить. Содержание объектов изменилось бы, так как они больше не будут определяться соответствующим банком, но для программиста фактические значения первичных ключей были бы все еще теми же самыми. Счет бы все еще содержал отчеты и сделки. bank bankld: PrimaryKey name: Name (AK1.1)
предлагает
account
i
4
accountld: PrimaryKey bankld: PrimaryKey (FK) (AK.1.1) number: AlphaNumericNumber (AK1.2)
совпадает сданными записей в I I I statement * statementld: PrimaryKey accountld: PrimaryKey (FK) statementTypeld: PrimaryKey (FK) previousBalance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.1) totalDebits: Amount totalCredits: Amount
имеет элементы в I I statementltem • statementltemld: PrimaryKey statementld: PrimaryKey (FK) date: Date number: AlphaNurrsricNumber description: Description amount: Amount
Теперь посмотрим, что произойдет, если мы удалим таблицу bank из этой модели: account совпадает с_данными accountld: PrimaryKey записей в I bankld: PrimaryKey (FK) (AK1.1) I number: AlphaNumericNumber (AK1.2) I statement * statementld: PrimaryKey accountld: PrimaryKey (FK) statementTypeld: PrimaryKey (FK) previousBalance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.1) totalDebits: Amount totalCredits: Amount
имеет элементы в I I statementltem • statementltemld: PrimaryKey statementld: PrimaryKey (FK) date: Date number: AlphaNumericNumber description: Description amount: Amount
Таблицы s t a t e m e n t и s t a t e m e n t l t e m не изменяются. Следовательно, программы, осуществляющие доступ к ним, также не будут изменяться. Только программы, связанные с таблицей account, должны будут измениться, чтобы отразить, что в таблице больше нет поля bankld, — незначительное изменение по сравнению с тем, что могло бы потребоваться. Обратите внимание, что, как только мы изменили ключи, реализация таблиц изменилась, хотя то, что таблицы представляют, не изменилось.
344
Планирование и реализация основной физической структуры Один момент относительно новых таблиц — каждая из них имеет уникальный ключ, который содержит мигрирующую часть предыдущего первичного ключа. Например, перед внесением изменений таблица s t a t e m e n t выглядела следующим образом: statement statementld: PrimaryKey bankld: PrimaryKey (FK) (AK1.2) accountld: PrimaryKey (FK) statementTypeld: PrimaryKey (FK) previousBaiance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.1) totalDebits: Amount totalCredits: Amount Эта таблица имеет не простой для определения естественный ключ, так как мы предварительно не задавали никаких вторичных ключей в нашем проекте. Если повнимательнее посмотреть, то можно заметить, что имеется возможный ключ, который мы можем использовать — значение даты (вероятно, со временем будет удалено). Мы должны сформировать программу, позволяющую выполнять балансирование счета в диалоговом режиме, которое мы могли бы делать ежедневно, но не более часто (проконсультируйтесь с клиентом перед принятием таких решений). Итак, мы могли бы использовать дату как часть вторичного ключа, что представлено в следующей диаграмме: statement statementld: PrimaryKey accountld: PrimaryKey (FK) statementTypeld: PrimaryKey (FK) previousBaiance: Amount previousBalanceDate: Date currentBalance: Amount statementDate: Date (AK1.1) totalDebits: Amount totalCredits: Amount Обычно мы не используем даты в наших ключах, даже вспомогательных, если можем обойтись без этого — особенно потому, что мы реализуем дату как полную восьмибитовую величину даты-времени. Имеются две очень важных причины избегать этой практики: •
Простота обращения к данным — когда мы должны использовать ключ, чтобы получить доступ к конкретной строке в таблице, то если наш ключ определен как значение даты, может быть довольно трудно ввести вручную фактическое значение даты. Например, значение d a t e t i m e содержит день, месяц, год, час, минуту, секунду и части секунды (тип s m a l l d a t e t i m e включает значения вплоть до минут). Это может быть довольно тяжело, особенно для среднего пользователя.
•
Точность — в большинстве случаев точность переменных, содержащих даты, больше, чем требуется для среднего ключа. Обычно, когда вы хотите поместить дату в ключ, вы просто задаете день и год.
В нашем случае мы будем использовать тип d a t e t i m e , вероятнее, s m a l l d a t e t i m e , чтобы иметь возможность выполнить отчет в различные времена в течение дня, и этот тип здесь наиболее удачен. Плюс к этому, мы будем удалять некоторые из значений времени. 345
Глава 10 Типы данных Во время моделирования мы выбрали логические домены для наших атрибутов. Теперь мы возьмем наш проект и преобразуем атрибуты в реальные поля и назначим реальные типы данных. Чтобы выбрать типы данных, мы создадим таблицу выбранных нами доменов и затем назначим типы данных и описания для них — выбор типа данных, возможность задания значения NULL и любая дополнительная информация относительно домена. Как только мы выполним эту задачу, то пройдем по каждой из наших таблиц, задавая типы данных значениям доменов, которые мы выбрали. Домены, которые мы должны использовать в нашей логической модели в течение этого упражнения, имеют форму: tableName pkeyAttributeld: PrimaryKey attributel: otherDomainName Здесь tableName — имя таблицы, pkeyAttributeld первичного ключа, attributel — первый атрибут.
— идентификатор атрибута
... где primaryKey (первичный ключ) и otherDomainName (имя другого домена) рассматриваются как домены для различных атрибутов. Следующий список содержит все домены, которые мы использовали по имени. Мы пройдем их и сделаем некоторые предположения относительно того, что представляют собой типы данных, а затем, когда закончим эту работу, будем использовать результаты первого просмотра для выбора наших типов данных. Мы найдем, что имеется очень хорошее совпадение, и нам не нужно будет многие из них изменять. Однако нам будет нужно пройти все таблицы, чтобы проверить, что они правильные (конечно, здесь мы рассмотрим только несколько таблиц). Название
Комментарий
Тип данных
Возможность использования NULL
addressDscriptive Line (строка описания адреса)
Используется для размещения информации об адресе.
varchar(900), чтобы размещать уникальные индексы размером до 900 байт
NOT NULL
addressStateCode (код штата в адресе)
Два символа кода штата, которые определяются почтовым ведомством США. Алфавитно-цифровое "число", которое обычно является значением вторичного ключа для записи в таблице. Пример — номер счета.
c h a r (2)
NOT NULL
varchar(20)
NOT NULL
alphaNumericNumber (алфавитно-цифровое число)
3
4
6
•
Планирование и реализация основной физической структуры
Название
Комментарий
Тип данных
Возможность использования NULL
amount (сумма)
Домен общего вида для денежных величин.
money
NOT NULL
areaCode (код региона)
Код, который определяет регион штата.
varchar(3)
NOT NULL
аиtoTimestamp (автоматическое определение времени создания/обновления)
Автоинкрементная величина оптимистической блокировки.
timestamp
' NOT NULL
countryCode (код страны)
Набираемый код, который определяет страну.
char(1)
NOT NULL
date (дата)
Используемые по умолчанию значения даты и времени. По умолчанию используются величины smalldatetime, так как нам редко нужна точность типа данных datetime.
smalldatetime
NOT NULL
description (описание)
Краткие неструктурируемые комментарии, описывающие элемент таблицы, чтобы иметь возможность передать это другим пользователям.
Varchar(100)
NOT NULL
disableDate
Используется, чтобы запретить работу с записью, не удаляя ее, например, для целей архивирования.
smalldatetime
NULL
exchange (номер коммутатора)
Код региона, который помещается перед номером телефона.
char(5)
NOT NULL
extension (расширение)
Строка, которая содержит ряд чисел, которые могут быть набраны после выполненного соединения.
varchar(20)
NULL
firstName (имя)
Используется для размещения имени человека.
varchar(60)
NOT NULL
(дата запрещения)
Продолжение таблицы на следующей странице
347
Глава 10
Название
Комментарий
Тип данных
Возможность использования NULL
F1 (флаг)
Обычно — логическая величина. Реализуется как столбец в бит, который не может содержать значение NULL и значение которого, равное нулю, означает False (ложь), а ненулевое значение — True (истина).
bit
NOT NULL
lastName(фамилия)
Используется для размещения фамилии человека.
varchar(60)
NOT NULL
middleName (отчество)
Используется для размещения отчества человека.
varchar(60)
NOT NULL
name (имя)
Имя, которое будет однозначно определять конкретную запись. Это — не имя человека, а скорее значение вторичного ключа для объекта.
varchar(60)
NOT NULL
number (число)
Обобщенная величина, являющаяся числом. По умолчанию целого типа.
int
NOT NULL
phoneNumberPart (собственно номер телефона)
Номер телефона.
char(4)
NOT NULL
primaryFl (первичный флаг)
Флаг в один бит, который используется, чтобы указать первичную запись группы.
bit
NOT NULL
primaryKey (первичный ключ)
Автоинкрементный столбец идентификации, используемый как указатель на элемент сущности.
int
NOT NULL, IDENTITY
Обобщенные строковые значения. Мы использовали ее по умолчанию, когда не знали, какую строку собираемся использовать. По умолчанию имеет значение NOT NULL.
varchar(20)
s t r i n g (строка)
348
(автоинкрементный) NOT NULL
Планирование и реализация основной физической структуры
Название
Комментарий
Тип данных
Возможность использования NULL
username (имя пользователя)
Используется для размещения имени пользователя в системе.
sysname (тип данных, определяемый пользователем в SQL Server, который используется для размещения имен пользователей). Мы будем размещать в SQL Server в этом типе данных имена, которые отображают реальные имена, так мы дадим права любому пользователю, который может вводить чек, просматривать сделки и/или счета, используемые в учебном примере. char(5) (Обратите внимание, что мы использовали zipCode, чтобы связаться с городом. Он представлен пятью символами почтового индекса. Мы игнорировали дополнительные 4 символа почтового индекса снова для простоты.)
NOT NULL
•
zipCode (почтовый индекс)
Пять цифр почтового индекса, определяющего почтовый регион, который может охватывать различные города, штаты, а может также просто идентифицировать часть города.
NOT NULL
Следует отметить, что все мои адреса и номера телефонов находятся в США. Это преследует одну цель — простоту.
349
Глава 10 Как только наш список будет закончен, мы просматриваем каждую из таблиц и рассматриваем каждый тип данных и спецификацию NULL для каждого поля, чтобы увидеть, как они работают в конкретной ситуации. Если мы выясняем, что некоторые из них не соответствуют спецификациям, отмеченным выше, и решим повторить этот процесс снова, то, вероятно, захотим создать новые домены для дальнейшего использования. Однако не будем это делать; вернее, будем делать только необходимые корректировки, если потребуется. Один дополнительный момент — мы будем также иметь желание завершить определения для наших атрибутов, поскольку добавили и изменили многие из них на протяжении проектирования. В каждом из следующих разделов мы будем брать одну из наших сущностей, подбирать имя домена из списка, определять значения по умолчанию для типов данных и затем обсуждать и корректировать тип данных, взятый по умолчанию. Начнем с сущности bank (банк): bank bankld PrimaryKey 1 name: Name (AK1.1) 1 •
bankld (идентификатор банка) priraaryKey, i n t NOT NULL IDENTITY — именно то, что необходимо. Определяется как: "Не читаемый пользователем указатель для идентификации конкретного банка".
•
name (название) name, v a r c h a r (60) — это должно быть достаточно для любого названия банка. Определяется как: "Уникальное название конкретного банка".
Обратите внимание, что мы используем очень формальные определения для атрибутов, так как они будут использоваться разработчиками, которые не участвовали на стадии проектирования, и это будет первый раз, когда они увидят спецификацию БД. На стадии логического моделировании (начиная с главы 4) мы определили атрибут "Bank Name" как "название банка, с которым мы имеем дело", что означает то же самое, но в более мягкой форме, и это будет понятно всем пользователям. При разработке систем с использованием средств моделирования БД вы должны будете скорректировать определения, которые мы выбрали. Наконец, мы завершаем следующим: bank bankld: int IDENTITY name: varchar(60) NOT NULL (AK1.1) autoTimestamp: timestamp NOT NULL Обратите внимание, что мы также добавили столбец autoTimestamp, определяемый как "Автоинкрементная величина оптимистической блокировки". Мы добавим это ко всем таблицам.
350
Планирование и реализация основной физической структуры Далее рассмотрим таблицу account (счет): account accountld: PrimaryKey bankld: PrimaryKey (FK) (AK1.1) number: AlphaNumericNumber (АКТ .2)
accountld (идентификатор счета) primaryKey, i n t NOT NULL IDENTITY — это точно то, что необходимо. Определяется как: "Не читаемый пользователем указатель для идентификации конкретного счета". bankld (идентификатор банка) primaryKey, i n t NOT NULL — это точно то, что необходимо, поскольку является значениями внешнего ключа, не допускающего значение NULL. Определяется как: "Не читаемый пользователем указатель для идентификации конкретного банка".
•
number (номер) alphaNumericNumber, v a r c h a r (20) NOT NULL — который должен быть достаточен почти для любого номера счета. Должны быть выполнены некоторые дополнительные исследования, чтобы определить, будет ли это годиться для всех значений, используемых клиентом. Определяется как: "Номер счета, который формируется банком, чтобы идентифицировать счет". Обратите внимание, что, хотя значение упомянуто как число, оно фактически является алфавитно-цифровой строкой, потому что может содержать ведущие нули или буквы.
Это приводит к: account accountld: int IDENTITY bankld: int NOT NULL (FK) (AK1.1) number: varchar(20) NOT NULL (AK1.2) autoTimestamp: timestamp NOT NULL Для нашей последней иллюстрации рассмотрим таблицу t r a n s a c t i o n (сделка): transaction transactionld: PrimaryKey accountld: PrimaryKey (FK) (AK1.1) date: Date number: AlphaNumericNumber (AK1.2) description: Description amount: Amount signature: String payeeld: PrimaryKey (FK userld: PrimaryKey (FK) statementltemld: PrimaryKey (FK) transactionTypeld: PrimaryKey (FK)
351
Глава 10 •
t r a n s a c t i o n l d (идентификатор сделки) primaryKey, i n t NOT NULL IDENTITY — это точно то, что необходимо. Определяется как: "Не читаемый пользователем указатель для идентификации конкретной сделки".
•
accountld (идентификатор счета) primaryKey, i n t NOT NULL — это точно то, что необходимо, поскольку является внешним ключом, не допускающим значение NULL. Определяется как: "Не читаемый пользователем указатель для идентификации конкретного счета".
•
number (номер) alphaNumericNumber, varchar (20) NOT NULL — который должен быть достаточен почти для любого номера чека, номера депозита или кредитной карточки. Должны быть выполнены некоторые дополнительные исследования, чтобы определить, будет ли это годиться для всех значений, используемых клиентом. Определяется как: "Номер, используемый в счете для идентификации сделки". Обратите внимание, что, хотя значение упомянуто как число, оно фактически является алфавитно-цифровой строкой, потому что может содержать ведущие нули или буквы.
•
d e s c r i p t i o n (описание) d e s c r i p t i o n , varchar (100) NOT NULL — в этом случае мы должны, вероятно, выделить дополнительное место, чтобы описать использование чека, кредитной карточки или депозита. Однако у нас также есть таблица checkAllocation, где использование детализируется. По этой причине мы изменим тип столбца описания на varchar (1000), чтобы дать свободу для описания деталей сделки, если потребуется. Определяется как: "Неструктурированные комментарии, описывающие конкретную сделку, которые в дальнейшем могут быть расширены для других возможностей пользователей, чтобы характеризовать сделку".
•
amount (сумма) amount, money NOT NULL — это будет достаточно, чтобы описать сумму сделки. Определяется как: "Количество денег, которые должны быть перемещены с помощью сделки".
•
signature (подпись) s t r i n g , v a r c h a r (20) NOT NULL — возможно, следует расширить строку, чтобы разместить полное имя. Мы также могли бы пожелать использовать тип данных image (изображение), чтобы хранить образец подписи. В наших таблицах мы просто изменим тип с v a r c h a r (20) на v a r c h a r (100), чтобы позволить пользователю вводить полное имя человека, кому принадлежит подпись. Определяется как: "Текстовое представление подписи, которая была использована в сделке". payee Id (идентификатор получателя платежа) primaryKey, i n t NOT NULL — это не совсем то, что необходимо, так как оно — необязательное отношение. Поэтому мы зададим столбцу характеристику NULL. Определяется как: "Не читаемый пользователем указатель для идентификации конкретного получателя платежа, который может быть заполнен лишь на основании значения allowsPayeeFl (флаг разрешения иметь получателя платежа) в связанной таблице t r a n s a c t i o n T y p e (тип сделки)".
•
•
352
u s e r l d (идентификатор пользователя) primaryKey, i n t NOT NULL — не является необходимым, так как это — необязательное отношение. Поэтому мы установим столбцу характеристику NULL. Определяется как: "Не читаемый пользователем указатель, используемый для идентификации пользователя, который отвечает за сделку".
Планирование и реализация основной физической структуры Q
statementltemld (идентификатор элемента отчета) primaryKey, i n t NOT NULL — не является необходимым, так как это — необязательное отношение. Поэтому мы установим столбцу характеристику NULL. Определяется как: "Не читаемый пользователем указатель, используемый для идентификации s t a t e m e n t l t e m (элемент отчета), который используется для конкретной сделки".
•
transactionTypeld (идентификатор типа сделки) primaryKey, i n t NOT NULL — это точно то, что необходимо, так как это обязательное поле. Определяется как: "Не читаемый пользователем указатель, используемый для идентификации записи типа сделки, который определяет тип конкретной сделки'
Завершим это следующей таблицей: transaction transaction^: int NOT NULL accountld: int NOT NULL (FK) (AK1.1) date: smallDateTime NOT NULL number: varchar(2O) NOT NULL (AK1.2) description: varchar(1000) NOT NULL amount: money NOT NULL signature: varchar(20) NOT NULL payeeld: int NULL (FK) userld: int NULL (FK) statementltemld: int NULL (FK) transactionTypeld: int NOT NULL (FK) autoTimeStamp: timestamp NOT NULL Таким же образом рассмотрим все таблицы, пока не определим все типы данных и формулировки. В следующих двух разделах мы покажем окончательную физическую модель, которую будем реализовывать, учитывая описания трех таблиц, которые мы создали в этом разделе. Полное описание схемы (и сопровождающий код) размещено в www.wrox.com. Прежде, чем мы продолжим, следует сделать примечание относительно сопоставления типов данных (наборы символов и упорядочивание, которые мы сформировали ранее). Задание индивидуальных сопоставлений для столбца — весьма продвинутая тема, и полезна для определенного набора ситуаций. В нашем учебном примере мы не будем использовать сопоставления, но я упоминаю их здесь снова, чтобы напомнить вам, что возможно установить сопоставление для отдельного столбца, если потребуется.
Физическая модель На данном этапе следует создать модель на бумаге для "проверки здравого смысла", предпочтительно кем-либо из сотрудников, если это возможно, чтобы можно было гарантировать, что ваша модель не только эффективна и годна к употреблению, но также и понятна кому-то еще. Итак, мы подходим к представлению окончательной физической модели нашей СУБД. Мы должны иметь в виду, что это модели, по которым мы создадим и реализуем сервер БД. 353
Глава 10
sstatementTypeld: tatementType int IDENTITY
bank b nkeld::va inrtchD IaE IYNOT NULL (AK1.1) naam r(N 60T)T autoTm i estamp: tm i estamp NOT NULL предлагает
name: varchar(60) NOT NULL (AK1.1) description: varchar(IOO) NOT NULL(AK2.1) autoTimestamp: timestamp NOT NULL
поддерживается согласованным с записями в
statementld: int IDENTITY
классифицирует
accountld: int NOT NULL(FK) (AK1.1) statementTypeld: int NOT NULL (FK) previousBalance: money NOT NULL previousBalanceDate: smallDateTime NOT NULL cunrentBalance: money NOT NULL date: smallDateTime NOT NULL(AK1.2) totalDebits: money NOT NULL totalCredits: money NOT NULL periodEndDate: smallDateTime NOT NULL autoTimestamp: timestamp NOT NULL
accountld: int IDENTITY bankld: int NOT NULL (FK) (AK1.1) number: varchar(20) NOT NULL (AK1.2) autoTimestamp: timestamp NOT NULL
используется для согласования счета с помощью
имеет элементы в statementltem
accountReconcile accountReconcileld: int IDENTITY
statementltemld: int IDENTITY
statementld: int NOT NULL (FK) (AK1.1) reconcileDate: smallDateTime NOT NULL (AK1.2) autoTimestamp: timestamp NOT NULL
statementld: int NOT NULL (FK)N(AK1.1) date: smallDateTime NOT NULL number: varchar(20) NOTNULL(AK1.2) description: varchar(IOO) NOT NULL amount: money NOT NULL transactionTypeld: int NOT NULL(FK) autoTimestamp: timestamp NOT NULL
transaction transactionld: int NOT NULL accountld: int NOT NULL (FK) (AK1.1) date: smallDateTime NOT NULL number. varchar(20) NOT NULL (AK1.2) •description: varchar{1000) NOT NULL amount: money NOT NULL signature: varchar(20) NOT NULL payeeld: int NULL (FK) userld: int NULL (FK) statementltemld: int NULL (FK) transactionTypeld: int NOT NULL (FK) autoTimestamp: timestamp NOT NULL f I I I корректирует балланс счета, запрашивая
классифицирует используется для согласования
transactionType transactionTypeld: int IDENTITY name: varchar(60) NOT NULL (AK1.1) description: varchar(IOO) NOT NULL (AK2.1) requiresSignatureFl: bit NOT NULL requiresRayeeFI: bit NOT NULL allowsPayeeFI: bit NOT NULL autoTimestamp: timestamp NOT NULL
классифицирует
имеет информацию о распределении, размещенную в
userid: int IDENTITY username: sysname NOT NULL (AK1.1) firstName: varchar(60) NOT NULL middleName: varchar(60) NOT NULL lastName: varchar(60) NOT NULL autoTimestamp: timestamp NOT NULL
transactionAllocationType transactionAllocationTypeld: int NOT NULL name: varchar(60) NOT NULL description: varchar(IOO) NOT NULL parentCheckUsage^peld: int NOT NULL (FK) autoTimestamp: timestamp NOT NULL
3 5 4
классифицирует чек с помощью
transactionAllocation transactionAllocationld: int IDENTITY transactionAllocationTypeld: int NOT NULL (FK) (AK1.1) transactionld: int NOT NULL (FK) (AK1.2) allocationAmount: money NOT NULL autoTimestamp: timestamp NOT NULL
Планирование и реализация основной физической структуры
papye e Pehe od n N uN m bm eO rT a yoe P n e ud b IU nLT iLD IE lhe :o In tbe N (N FN K .)(AK11 p h n e N u m r l : n ied trl:N N UT L)IY(AL (FKK1)2 . pu ho nm tO a toT ieN esua tm mbpa:r^pm itedl:esatInm pNOT NOTNUL NUL(FK) арактеризуется номеров телефона с помощью payeeld: «it IDENTTTY name: varchar(60) NOT NULL (AK1.1) date: smallDateTime NULL autoTlrnestamp: timestamp NOT NULL
phoneNumbeV rpe n ahem :N vnam rch a 6 0dl): n N U K 11 .) .) p osncerp u ba e-cfirr(c ph e i1O t0T D I)EN T ILYT (A d i o t i : 0 N O NU ULL auo tTm i esa tmvp :m itaer(satm p N O TN L (AK21 phoneNumberld: int IDENTITY countryCode:char(1) NOT NULL (AK1.1) areaCode: varchar(3) NOT NULL (AK1.2) exchange: char(5| NOT NUU(AK1.3) number char(4) NOT NULL(AKt .4) extension: varchar(20) NOT NULL (AK1.5) autoTlroestamp: timestamp NOT NULL
payeeAddressId: int IDENTITY
ара^теризуется адресом
payeeid: int NOT NULL (FK) (AK1.1) addressld: int NOT NULL(FK) (AK1.2) addressTypeld: int NOT NULL (FK) autoTimestamp: timestamp NOT NULL
характеризует*
addressld: Int IDENTITY addressiype addressTypeld: int IDENTITY name: varchar(60) NOT NULL (AK1.1) description: varchar(lOO) NOT NULL (AK2.1) autoTimestamp: timestamp NOT NULL
addressUr»: varehar(800) NOT NULL (AK1.1) cllyld: intN0TNULL(FK)(AK1.2) zipCodeld: inl NOT NULL (FK)(AK1.3) autoTimestamp: timestamp NOT NULL
првделяв!
stateld: int IDENTITY code: char(2) NOT NULL (AK1.1) autoTimestamp: timestamp NOT NULL
пределяет распо
регион zipCodeld: Int IDENTITY
1арактеризует( подходящим почтовым индексом
stateld: int NOT NULL (FK) (AK1.1) lame: varcnar(60) NOT NULL (AK1.2) utoTimestamp: timestamp NOT NULL
zipCodeCityReferenceld: int IDENTITY zipCodeld: int NOT NULL (FK) (AK1.1) oityld: int NOT NULL (FK) (AK1.2) primaryR: bit NOT NULL autoTimestamp: timestamp NOT NULL
характеризуете подходящим почтовым индексом с помощью
Полная версия исходного кода для создания объектов БД наряду с версиями PDF-файла (Package Definition File — файл определения пакета) моделей данных доступны в www.wrox.com. Полное описание даже такой маленькой БД — весьма длинное и во многом повторяющееся.
Резюме Да, это была длинная глава, охватывающая большое количество понятий. Она переполнена информацией, которая является очень важной для каждого читателя. Понимание, как строить таблицы и как они реализуются — основа знаний каждого проектировщика БД. Мы взяли нашу логическую модель и исследовали каждую сущность, чтобы определить, насколько возможно их реализовать. Особо были рассмотрены подтипы, поскольку с ними могут быть проблемы. Мы также рассмотрели возможные отклонения от наших строгих правил нормализации в особых случаях (хотя мы боролись с этим в максимально возможной степени), и очистили структуры нашей БД от ошибок, которые имеются в модели. После того, как мы удостоверились, что имеем именно ту модель, которую могли бы реализовать, мы попристальней взглянули на таблицы SQL Server, прошлись по ограничениям таблиц и рассмотрели синтаксис операторов CREATE TABLE и ALTER TABLE. Были рассмотрены также правила обозначения объектов, столбцов, индексов и ограничений внешнего ключа.
355
Глава 10 Два наиболее важных раздела этой главы были посвящены типам данных и индексам. Когда мы реализуем наши таблицы, очень важно понять концептуальный способ, которым наши данные будут фактически размещены в SQL Server. Закончен этот процесс определением первичных ключей и добавлением, по крайней мере, по одному вторичному ключу на таблицу и столбцу типа timestamp для целей оптимистической блокировки. Окончательная модель и описание, как реализовать систему, доступны на Web-сайте Wrox Press. В следующей главе мы завершим задачу реализации OLTP-системы, обеспечив бизнес-правила, необходимые, чтобы содержать данные в нашей БД настолько чистыми, насколько возможно.
356
Обеспечение целостности данных Введение В этой главе мы займемся предикатами и доменами, которые были определены во время проектирования. Когда мы обсуждаем целостность данных, то рассматриваем применение основных правил к данной таблице или набору таблиц, которые гарантируют, что размещенные данные всегда полноценны. Поддержание нашей БД в состоянии, когда данные всегда соответствуют нашим исходным техническим требованиям проекта, является основой большинства разработок. Независимо от того, насколько хорошо или плохо разработаны таблицы, как это воспринимает пользователь, БД всегда будут определяться тем, насколько хорошо защищены данные. При разработке стратегии целостности данных мы должны рассмотреть несколько различных сценариев: • • • •
пользователи, применяющие традиционные клиентские средства; пользователи, применяющие настраиваемые средства работы с данными типа Microsoft Access; программы, которые импортируют данные из внешних источников; необработанные запросы, выполняемые администраторами данных, чтобы выявить проблемы, связанные с ошибками пользователя.
Каждый из них обладает различными проблемами по отношению к нашей схеме целостности и, что более всего важно, каждый из этих сценариев (может быть, за исключением второго) составляет часть каждой СУБД, которую мы разрабатываем. Чтобы лучше обеспечить каждый сценарий, мы должны защитить данные, используя механизмы, которые работают независимо от пользователя и которых нельзя случайно обойти. Имеются четыре возможных пути, как мы можем использовать код, чтобы обеспечить непротиворечивые данные:
Глава 11 •
Типы данных и ограничения являются простыми непосредственными средствами, которые составляют неотъемлемую часть определения таблицы. Они являются очень быстрыми средствами, которые требуют немного кодирования, и' их использование может дать существенное улучшение функционирования. Такие ограничения включают действия типа определения диапазонов данных в столбцах (например, элементы столбца должны быть больше, чем 10, или — если значение в столбце 1 равно 1, то значение столбца 2 больше 1). Это — лучшее место для защиты данных, поскольку большая часть работы выполняется с помощью SQL Server, и пользователь не сможет обойти это при любой ошибке во внешней программе.
•
Триггеры отличаются от ограничений тем, что они являются частями кода, который вы добавляете к таблице (или таблицам). Этот код автоматически запускается всякий раз, когда происходит событие (события), которое (которые) вы определили для таблицы (таблиц). Они чрезвычайно гибки и могут обращаться с несколькими столбцами, несколькими строками, несколькими таблицами и даже несколькими БД. Как простой случай, рассмотрим ситуацию, где мы хотим гарантировать, что обновление значения выполняется в обеих таблицах, где находятся данные. Мы можем написать триггер, который запретит обновление, если оно не происходит в обеих таблицах. Это — второе лучшее место, чтобы защитить данные. Мы должны закодировать каждое правило триггера в T-SQL, но пользователь не сможет обойти триггер при любой ошибке во внешней программе.
•
Хранимые процедуры — части кода, которые размещены в БД и очень гибко могут работать с таблицами; например, использование различных бизнес-правил при различных обстоятельствах. Простой пример хранимой процедуры — которая возвращает все данные, находящиеся в данной таблице. В более сложном сценарии они могут использоваться, чтобы давать различные разрешения различным пользователям относительно работы с таблицами. Это — не лучшее решение для защиты данных, поскольку мы должны закодировать правила в какой-то процедуре, которая имеет доступ к данным. Однако так как она находится в центральном месте, мы можем использовать ее, чтобы реализовать правила, которые могут быть различными в разных ситуациях.
•
Выполняемый клиентом код — полезен в ситуациях, когда бизнес-правила необязательны или гибки по своей природе. Довольно общий пример, когда SQL Server формирует вопрос пользователю "Вы уверены, что желаете удалить эту запись?" и если вы просите удалить данные, он их удалит. Большинство результатов работы сервера выполнено в этой манере, оставляя программам пользователя возможность реализации гибких бизнес-правил и предупреждений. Имейте в виду, что приложения приходят и уходят, но данные должны быть всегда защищены.
При перемещении вниз по этому списку решения становятся менее желательными, но все же каждое имеет определенные выгоды, которые полезны в некоторых ситуациях. Все эти особенности будут рассмотрены в свое время, однако мы начнем с новой особенности в SQL Server 2000, которая является существенной при формировании ограничений, триггеров и хранимых процедур. Эта особенность — определяемые пользователем функции (User Defined Functions).
358
Обеспечение целостности данных
Примеры таблиц В примерах данной главы, предназначенных для иллюстрации различных концепций, мы будем использовать следующие таблицы: CREATE TABLE artist ( artistld int NOT NULL IDENTITY, name varchar(60), -- заметьте, что первичный ключ является кластерным, -- что будет рассмотрено позже в этой главе CONSTRAINT XPKartist PRIMARY KEY CLUSTERED (artistld) CONSTRAINT XAKartist name UNIQUE NONCLUSTERED (name)
INSERT VALUES INSERT VALUES GO
INTO ('the INTO ('the
artist(name) beetles') artist(name) who')
CREATE TABLE album ( albumld int NOT NULL IDENTITY, name varchar(60), artistld int NOT NULL, -- заметьте, что первичный ключ является кластерным, -- что будет рассмотрено позже в этой главе CONSTRAINT XPKalbum PRIMARY KEY CLUSTERED(albumld), CONSTRAINT XAKalbum_name UNIQUE NONCLUSTERED(name), CONSTRAINT artist$records$album FOREIGN KEY(artistld) REFERENCES artist
INSERT VALUES INSERT VALUES INSERT VALUES
INTO album (name, artistld) ('the white album', 1) INTO album (name, artistld) ('revolver', 1) INTO album (name, artistld) ('quadrophonia', 2)
Здесь создаются две таблицы. Первая таблица — artist (артист), содержащая два столбца: первый — artistld (идентификатор артиста), автоинкрементный целого типа, который не может принимать значение NULL, и второй — name (имя) типа varchar (60). Для этой таблицы задаются ограничения: XPKartist (первичный ключ таблицы artist), представляющее собой кластерный первичный ключ, использующее столбец artistld, и XAKartist_name (вторичный ключ — имя артиста), обеспечивающее некластерный столбец name, содержащий уникальные значения. В эту таблицу помещаются две записи, содержащие в столбце name строки "the beatles" (Битлз) и "the who" (Xy). Вторая таблица album (альбом) содержит три столбца: albumld (идентификатор альбома), автоинкрементный целого типа, который не может принимать значение NULL, второй — name (имя) типа varchar (60) и третий — artistld целого типа, который не может принимать значение NULL. Для этой таблицы задаются ограничения: XPKalbum (первичный ключ таблицы album), представляющее собой 359
Глава 11 кластерный первичный ключ, использующее столбец albumld, XAKalbum_name (вторичный ключ — название альбома), обеспечивающее некластерный столбец name, содержащий уникальные значения, и ограничение artist$records$album (артист записывает альбом), использующее столбец artistld для ссылки на таблицу artist. В эту таблицу помещаются три записи, у которых в столбцы name и artistld будут помещены записи: "the white album", 1; "revolver", 1 и "quadrophonia", 2.
Определяемые пользователем функции Определяемые пользователем функции (ОПФ) (User Defined Functions — UDF) могут применяться, чтобы гарантировать целостность данных, и (по моему мнению) являются одной из наиболее важных особенностей SQL Server 2000. Обычно ОПФ тесно связаны с хранимыми процедурами, но вместо того, чтобы быть средствами, в первую очередь предназначенными для корректировки данных и возвращения результатов, они являются T-SQL-программами, которые возвращают значение. Как мы увидим, компилятор SQL Server не позволит вам выполнить внутри ОПФ любой код, который модифицирует данные, и поэтому ОПФ может лишь использоваться для операций, выполняющих только чтение. Возвращаемое ОПФ значение может быть или простой скалярной величиной, или таблицей, или набором результатов. Что делает их сказочно полезными — то, что они могут быть вложены в запросы, таким образом сокращая нашу потребность в огромных запросах со многими строками повторяющегося кода. Единственная функция может содержать программы, которые могут вызываться из запроса по желанию. В оставшейся части этой главы и в следующей мы будем широко использовать ОПФ, чтобы решать задачи, которые в более ранних версиях SQL Server было или невозможно, или очень трудно решить. ОПФ имеют следующую основную структуру: CREATE FUNCTION [.] [] - - точно т а к же, к а к у хранимых ) RETURNS AS BEGIN [] RETURN END
процедур
При создании этих функций в противоположность хранимым процедурам имеется несколько очень важных различий в синтаксисе, которые мы должны помнить:
360
•
Мы используем CREATE FUNCTION вместо CREATE PROCEDURE.
•
Круглые скобки всегда требуются, независимо от того, имеются ли параметры.
•
Операторы RETURNS используются для объявления типа возвращаемых данных. Обратите внимание, что оператор RETURNS является обязательным для ОПФ в отличие от хранимой процедуры.
•
BEGIN и END требуются для скалярных функций и функций, состоящих из набора многих операторов. Имеются три разных версии ОПФ, которые мы рассмотрим ниже.
Обеспечение целостности данных Скалярные функции Скалярные функции возвращают единственное значение. Они могут использоваться в различных местах, чтобы реализовать простые или сложные функциональные возможности, не имеющиеся обычно в SQL Server. Наш первый пример скалярной функции — один из самых простых возможных примеров: увеличение значения целого числа. Функция просто берет единственный целый параметр, увеличивает его на единицу и затем возвращает это значение: CREATE FUNCTION i n t e g e r $ i n c r e m e n t ( SintegerVal i n t ) RETURNS i n t AS BEGIN SET @integerVal = @integerVal RETURN S i n t e g e r V a l END GO
+ 1
Обратите внимание, что я снова использовал для названия функции знак доллара. Я называю все функции, триггеры и хранимые процедуры в такой манере, чтобы отделить объект (в этом случае integer — целое число) от метода (в данном случае — increment — увеличение). Это — просто мое личное предпочтение. Мы можем выполнять эту функцию с помощью классического синтаксиса выполнения из предыдущих версий SQL Server:
I
DECLARE @integer int EXEC Sinteger = integer$increment @integerVal = 1 SELECT @integer AS value
... который возвратит: value
Теперь, когда мы создали функцию и выполнили ее, логично пожелать вызвать ее из оператора SELECT. Однако это более хитро, чем могло бы вначале показаться. Если мы попробуем выполнить следующий оператор: Щ SELECT integer$increment (1) AS value ... то получим следующее сообщение: Server: Msg 195, Level 15, State 10, Line 1 'integer$increment' is not recognized function name (Сервер: сообщение 195, уровень 15, состояние 10, строка 1 'integer$increment' — неопознанное имя функции)
361
Глава 11 ОПФ требует, чтобы вы определили владельца функции для ее успешного выполнения. §§§ SELECT dbo. integer$increment (1) AS value Теперь мы получим желаемый результат: value
Имеется еще одно замечание по использованию, о котором мы должны упомянуть. Вы не можете передавать названные пользователем параметры функции, как вы можете поступать с хранимыми процедурами. Так, следующие два метода вызова кода не будут работать:
I
SELECT dbo.integer$increment @integerVal = 1 AS value SELECT dbo.integer$increment (@integerVal = 1) AS value
Выполнение их даст: Server: Msg 170, Level 15, State 1, Line 1 Line 1: Incorrect syntax near 'integerVal'. Server: Msg 137, Level 15, State 1, Line 3 Must declare the variable '(SintegerVal'. (Сервер: сообщение 170, уровень 15, состояние 1, строка 1 Строка 1: Неправильный синтаксис в окрестности @integerVal. Сервер: сообщение 137, уровень 15, состояние 1, строка 3 Должна быть объявлена переменная @integerVal.) К сожалению, только первое сообщение об ошибке имеет какой-либо смысл (второе пробует передать логическое значение (QintegerValue = 1) в функцию). Странно, но мы можем использовать синтаксис "@parmname = " только с оператором EXEC, как видели в примере выше. Как заключительный пример, использующий функцию увеличения, мы три раза увеличим значение в одном и том же операторе (обратим внимание на вложенные вызовы): I
SELECT d b o . i n t e g e r $ i n c r e m e n t ( d b o . i n t e g e г $ i n c r e m e n t ( d b o . i n t e g e r $ i n c r e m e n t ( 1 ) AS v a l u e
что, как ожидается, возвратит value
Давайте рассмотрим теперь более сложный пример функции, который, как я уверен, многие читатели хотели бы реализовать: функция, которая берет строку и пишет прописную букву в начале каждого слова внутри этого текста:
362
Обеспечение целостности данных CREATE FUNCTION string$properCase ( OinputString varchar(2000) RETURNS varchar(2000) AS BEGIN — устанавливаем все буквы строчными SET @inputString = LOWER(SinputString) -- затем используем процедуру stuff (вставить) для первого символа SET @inputString = -- вставляем прописную букву следующего символа -- вместо строчной буквы STUFF(@inputString, I, 1, UPPER(SUBSTRING(OinputString, 1, 1))) — @ i - параметр цикла, инициализируемый значением 1 DECLARE @i int SET @i = 1 -- цикл от второго символа до конца строки WHILE @i < LEN(dinputString) BEGIN -- если символ - пробел IF SUBSTRING(dinputString, @i, 1) = ' ' BEGIN -- помещение прописной буквы на место следующего символа SET @inputString = STUFF(@inputString, @i + 1, 1, UPPER(SUBSTRING(@inputString, @i + 1, 1))) END -- увеличение параметра цикла SET @i = @i + 1 END RETURN SinputString END GO
Здесь в переменную QinputString (вводимая строка) типа varchar (2000) при обращении к этой функции помещается преобразуемая строка. Далее с помощью стандартной процедуры LOWER все буквы строки преобразуются в строчные. Затем первый символ строки преобразуется в прописную букву. Для этого с помощью стандартной функции SUBSTRING выделяется строка, начиная с первого символа и длиной в один символ (фактически выделяется один символ), которая с помощью стандартной функции UPPER преобразуется в прописную букву, которая опять-таки с помощью стандартной функции STUFF замещает первую букву строки. Далее организуется цикл просмотра всех символов с первого до предпоследнего (длина строки определяется с помощью стандартной функции LEN). Если очередной символ является пробелом, то следующий символ преобразуется в прописную букву точно так же, как и первый символ строки. Функция возвращает преобразованную строку. Итак, мы можем затем выполнить следующий оператор:
I
SELECT name, dbo.string$properCase(name) AS artistProper FROM artist
363
Глава 11
и получить старые названия и новые, name
artist
the beatles the who
The Beatles The Who
Хотя это и не совершенная функция для изменения строки добавлением заглавных букв, мы могли бы легко расширить ее на "специальные" случаи, которые могут возникнуть (подобно McCartney или MacDougall). Последний пример, который мы рассмотрим, — доступ к таблице альбомов, подсчет числа записей и возвращение числа записей. Что хорошо в этой функции — то, что вызывающий не будет знать, что осуществляется доступ к таблице, так как это сделано изнутри ОПФ. Если мы создадим следующую функцию: CREATE FUNCTION album$recordCount (