Лекция № 2. Разработка баз данных. Таблицы и связи 1. Введение Все деловые приложения хранят значительные объёмы данных, являющиеся ядром информационной системы. В широком понимании под определение информационной системы попадает любая система обработки информации. Такие системы в первую очередь призваны облегчить труд человека, а для этого они должны как можно лучше соответствовать сложной модели реального мира. Типовая информационная система в самых общих чертах включает в себя следующие составляющие: серверы, рабочие станции с их периферийными устройствами; коммуникационное оборудование (маршрутизаторы, шлюзы, коммутаторы, мосты и т.д.); системное программное обеспечение (операционные системы, серверы баз данных и т.д.); базы данных; системы управления базами данных; прикладное программное обеспечение. База данных (БД) – совместно используемый набор логически связанных данных. Система управления базами данных (СУБД) – программное обеспечение, с помощью которого пользователи могут создавать, модифицировать базу данных и осуществлять контролируемый доступ к ней. Промышленные базы данных – это чрезвычайно сложные системы, работающие на архитектуре «клиент-сервер», поддерживающие одновременную работу многих пользователей с БД, предоставляющие возможность восстановления предыдущего состояния в случае неудачи некоторой операции и т.п. Однако базы данных могут представлять интерес не только для крупных организаций, но и для предприятий среднего и малого бизнеса и для отдельных пользователей. Подобные базы данных гораздо проще в создании и поддержке и не требуют использования сложных СУБД. Приложение Microsoft Access хорошо подходит для создания таких небольших баз данных, например: каталогов книг, дисков, фильмов и прочее, и прочее, и прочее; списков рассылки; деловой информации, такой как список клиентов и поставщиков малого предприятия; личной финансовой информации. Приложение Microsoft Excel также имеет возможность работать с данными небольшого объёма, но оно не в состоянии обрабатывать информацию того объёма и сложности, которая подвластна приложению Microsoft Access. Приложение Microsoft Excel плохо справляется с обработкой множественных списков со связанными данными, которые составляют основу БД. Кроме того, приложение Microsoft Access предоставляет набор функций, не имеющих аналогов в мире электронных таблиц, таких как создание настраиваемых процедур поиска, проектирование форм для ввода данных и вывод на печать ярко оформленных отчётов. С другой стороны, приложение Microsoft Access не справляется с созданием баз данных, хранящих большие объёмы информации (более 2 Гбайт) и поддерживающих совместное использование БД в сети. Для создания подобных БД требуют другие средства. Приложение Microsoft Access, в отличие от других приложений пакета Microsoft Office, требовательно к пользователю. Базы данных, вообще, требуют строгого соблюдения правил разработки. Можно сесть за компьютер и сразу же набрать текст в приложении Microsoft Word. Даже если потом вы выясните, что всё сделали неправильно, исправить будет не так уж сложно. С базами данных так не получится. Любая БД требует, прежде всего, тщательного проектирования. Сначала надо разработать таблицы и связи между ними, запросы и формы, и только после этого можно добавлять данные в БД. 2. Реляционные базы данных На сегодняшний день доминирующими среди баз данных являются реляционные БД. Такое широкое распространение связано с применением в них реляционной модели данных, 6 обеспечивающей простоту, гибкость структуры, удобство реализации. Не менее важным является наличие теоретического описания. В основе реляционной модели лежит один из разделов математики – реляционная алгебра. Основой любой реляционной базы данных является таблица. Все операции над базой данных сводятся к манипуляциям с таблицами. База данных содержит множество таблиц, связь между которыми устанавливается с помощью совпадающих полей. В каждой из таблиц содержится информация о каких-либо объектах одного типа. 2.1. Понятие таблицы Таблица – это регулярная структура, состоящая из конечного набора однотипных записей. Каждая таблица реляционной базы данных состоит из строк и столбцов и предназначена для хранения данных об объектах информационной системы. База данных Таблица 1 Столбец/Поле Строка/Запись Таблица 2 … Таблица N Сущность – объект любого происхождения, данные о котором хранятся в БД. Атрибут – свойство, характеризующее сущность. В таблице представляет собой заголовок столбца. Запись – строка таблицы, хранящая информацию об одной сущности. Поле – столбец таблицы, в котором хранятся значения некоторого атрибута для всех сущностей таблицы. Поле может содержать данные только одного типа. Рассмотрим для примера таблицу, хранящую информацию о зданиях. Кадастр 567134 849367 492853 Улица ул. Юности Новое шоссе Светлый проезд Дом 15 171 5 Квартир 98 54 12 Год 1982 1998 2011 Износ 34 12 0 Здесь сущностью является дом. Каждый дом имеет атрибуты: кадастровый номер, улица, номер дома, количество квартир, год постройки, износ. Каждый конкретный дом характеризуется своими собственными значениями атрибутов. Поле Улица содержит текстовые данные. Поля Квартир и Год – числовые данные. 2.2. Ключи и индексы Ключ представляет собой поле или группу полей, которые однозначно определяют любую запись в таблице реляционной базы данных. Ключ предназначен для: идентификации записей в таблице; 7 установления связи между таблицами БД; создания ограничений ссылочной целостности. Для того чтобы функционировать корректно, СУБД должна уметь определять разницу между любой и каждой записью в таблице реляционной БД. Другими словами, вы не можете вставить две записи с полностью идентичными данными. СУБД рассчитаны на максимальную скорость обработки данных, и они не могут сравнивать каждую запись со всеми существующими на предмет совпадения. Вместо этого они полагаются на ключ. До тех пор пока у каждой записи в таблице есть уникальный, никогда не повторяющийся ключ, у вас не будет двух идентичных записей. В худшем случае, они могут быть почти идентичны, с одинаковыми данными во всех остальных полях. Хотя это тоже, скорее всего, ненормальная ситуация. У вас могут быть знакомые, являющиеся полными тёзками, но вряд ли они будут иметь одинаковый номер телефона. В примере с домами, в качестве ключа можно использовать кадастровый номер. Он является уникальным. Если мы захотим создать таблицу, описывающую квартиры в домах, в качестве ключа можно использовать сочетание полей Улица – Дом – Квартира. Это сочетание будет однозначно определять каждую квартиру. Не всегда, однако, можно выделить одно или несколько полей, однозначно идентифицирующих сущность таблицы. Например, как мы уже говорили, среди ваших знакомых могут встречаться тёзки, поэтому сочетание Фамилия – Имя – Отчество не может однозначно идентифицировать человека для БД. В этом случае приходится «насильно» вводить ключевое поле, а именно – задавать каждой сущности уникальный номер, т.е. вставлять в таблицу дополнительные данные. Но иначе СУБД не сможет корректно функционировать. Одним из основных требований, предъявляемых к СУБД, является возможность быстрого поиска требуемых записей. В реляционных базах данных для реализации этого требования служат индексы. Индекс – это указатель на данные, размещённые в реляционной таблице. Он предоставляет информацию об их точном физическом расположении. Например, в таблице, хранящей информацию о зданиях, данные физически располагаются в порядке добавления сведений о сущностях. Но нам необходимо осуществлять поиск по кадастровому номеру, причём, этот поиск должен осуществляться быстро. Для этой цели строится индекс по полю Кадастр, который, по сути, представляет собой ещё одну таблицу, содержащую кадастровые номера, отсортированные в порядке возрастания, и для каждого кадастрового номера – адрес физического расположения соответствующей записи. В отсортированном списке поиск осуществляется гораздо быстрее, чем в неотсортированном. При этом используется так называемый алгоритм двоичного поиска. Суть его в следующем: берётся номер, расположенный в середине списка, и сравнивается с искомым номером. Если искомый номер меньше, то он расположен в первой половине списка. Если же искомый номер больше, то он расположен во второй половине списка. Таким образом, отбрасывается одна половина записей. Далее аналогично берётся номер, расположенный в середине выбранной половины списка, и выполняются аналогичные действия. Эти действия повторяются до нахождения нужной информации. 2.3. Нормальные формы Было доказано, что следуя при создании таблиц и связей между ними только немногим формализованным правилам, можно обеспечить простоту манипулирования данными. Эта методика получила наименование нормализация данных. Существует несколько нормальных форм. Таблица находится в первой нормальной форме, если значения всех её полей атомарные и в ней отсутствуют повторяющиеся группы полей. Таблица находится во второй нормальной форме, если она удовлетворяет условиям первой нормальной формы и любое неключевое поле однозначно идентифицируется полным набором ключевых полей. Таблица находится в третьей нормальной форме, если она удовлетворяет условиям второй нормальной формы и ни одно из неключевых полей таблицы не идентифицируется с помощью другого неключевого поля. Но мы не будем ориентироваться на эти сложные определения, а сформулируем правила формирования таблиц и разбиения базы данных на таблицы в более простом виде. 1. Включайте всю необходимую информацию. Чем полнее будет информация, предусмотренная в БД при проектировании, тем проще и удобнее будет с ней работать, и меньше будет вероятность того, что базу данных придётся перепроектировать. 8 2. В каждую таблицу необходимо включать поля, относящиеся только к одной сущности. Поле, являющееся атрибутом другой сущности, должно принадлежать другой таблице. Это позволяет избегать дублирования данных. Рассмотрим пример со зданиями и квартирами. Здание и квартира – это две отдельные сущности (несмотря на то, что одна находится в другой). Если объединить данные о зданиях и квартирах в одной таблице, то получится, что записывая сведения о квартирах, мы должны для каждой квартиры записать адрес здания, его кадастровый номер, год постройки, износ и т.д. Если в среднем здании имеется около 100 квартир, получается, что мы будем хранить одни и те же сведения 100 раз вместо одного. Любому разумному человеку, даже никогда не слышавшему о реляционной алгебре и нормальных формах, понятно, что в этом нет ни малейшего смысла. Таким образом, как только вы видите, что в таблице начинает повторяться некоторая информация, причём, повторяться не случайно (как, например, однофамильцы), а системно, значит, таблицу надо разбивать на несколько частей. Кроме того, подобное разбиение облегчает модификацию данных, т.к. при необходимости нужно будет изменить только одну запись, а не 100 (что, кроме всего прочего, позволяет избежать таких ошибок, при которых одна запись изменяется, а другая – нет). Разбиение на таблицы также упрощает поиск данных. 3. Разделяйте информацию на наименьшие логические единицы. Ни в коем случае нельзя хранить, например, фамилию, имя и отчество в одном поле. Во-первых, это приведёт к бессистемности вводимой информации, особенно, если базу данных будут заполнять несколько пользователей. Один введёт сначала фамилию, потом имя и отчество, другой сначала имя и отчество, затем фамилию. Один пользователь поставит по одному пробелу, другой – по два, третий добавит запятые. При таком подходе вы можете ввести одного и того же человека несколько раз и никогда этого не обнаружить. Но даже если случится чудо, и все пользователи будут набирать информацию одинаково, и ни один человек не будет повторяться в БД, такой подход чрезвычайно затруднит поиск в базе данных. Сравнить на совпадение фамилию, хранящуюся отдельно, гораздо проще, чем выделять эту фамилию из строки, содержащей фамилию, имя и отчество. 4. Не включайте вычисляемые данные. Нет смысла тратить место на хранение среднего балла студента или количества домов, построенных в определённом году. СУБД всегда может достаточно быстро вычислить эти данные. 5. Всегда определяйте ключ таблицы. Если в таблице есть уникальное поле или набор полей, который однозначно идентифицирует сущность, то это поле или набор полей должны стать ключом. Если таких полей нет, нужно вводить дополнительное поле, которое будет содержать уникальный номер записи. 6. Ни одно из неключевых полей таблицы не должно идентифицироваться с помощью другого неключевого поля. И ещё одно правило, общее для всех систем программирования, – выбирайте значимые имена полей и таблиц. Имена таблиц должны определять сущность, данные о которой хранятся в таблице, а имена полей – атрибуты этой сущности. Имена должны быть, по возможности, короткими и простыми. Не включайте имя таблицы в имя поля. Из имени таблицы должно быть понятно, какую сущность она описывает, и очевидно, что поля – это атрибуты именно этой сущности. Избегайте пробелов. Хотя приложение Microsoft Access позволяет включать пробелы в имена полей, это может усложнить написание запросов. Используйте заглавные и строчные буквы. Будьте последовательны! В качестве примера рассмотрим разработку таблиц для базы данных учёта недвижимости некоторого района. В базе данных должна содержаться следующая информация: адрес здания – текст; год постройки здания – число; износ в процентах – число; стоимость здания в рублях – число (существует специальный денежный тип); наличие лифта – да/нет; номер квартиры – число; количество комнат – число; площадь квартиры – число; 9 номер лицевого счёта – число; ФИО квартиросъёмщика – текст; год рождения квартиросъёмщика – число; ФИО проживающего (может быть несколько) – текст; год рождения проживающего (может быть несколько) – число. Первое правило гласит, что информация должна быть как можно более полной. В данном примере, конечно, это правило не соблюдается, т.к. информации об объектах недвижимости должно быть гораздо больше. При разработке реальной базы данных необходимо будет тщательно опросить все потенциальных пользователей (или, в крайнем случае, себя любимого) для того, чтобы включить всю необходимую информацию. Второе правило гласит, что таблица должна содержать данные только об одной сущности. На первый взгляд мы имеет три сущности – здание, квартира и проживающие (квартиросъёмщика можно отнести к сущности «квартира»). Однако если хорошо подумать, мы увидим, что адрес также надо выделить в отдельную сущность. Он состоит из нескольких частей – собственно название, тип улицы (улица, проспект, шоссе и т.п.), номер дома. Поэтому если оставить адрес как есть могут возникнуть как минимум две проблемы. Во-первых, улицы иногда переименовываются. Чтобы не менять название улицы в сотнях строк таблицы Здания, необходимо вынести список улиц в отдельную таблицу. Каждой улице присваивается свой номер, которые и заносится в таблицу Здания. После этого улицы можно переименовывать хоть по десять раз в день. Во-вторых, разбиение названия улицы на собственно название и тип улицы снимает проблему с возможными погрешностями ввода. Тип должен выбираться из списка возможных значений, ну а ввести название по-разному будет уже сложно. Третье правило гласит, что информация должна быть разбита на наименьшие логические единицы. Мы уже, собственно, применили это правило к адресу, но его также надо обязательно применить к ФИО квартиросъёмщика и проживающих. Четвёртое правило гласит, что не следует включать в таблицы вычисляемые поля, но у нас таких нет. Пятое правило гласит, что каждая таблица должна иметь ключ. Для таблицы Улицы это будет номер улицы, который мы добавим в таблицу. Название ни в коем случае не может быть ключом, поскольку оно, как мы говорили, может меняться. Для таблицы Здания ключом будет сочетание полей Номер улицы + Номер дома. Для таблицы Квартиры ключом будет сочетание полей Номер улицы + Номер дома + Номер квартиры. Для таблицы Проживающие сочетание полей Номер улицы + Номер дома + Номер квартиры не может быть ключом, т.к. проживающих может быть несколько. Поэтому необходимо ввести поле Номер проживающего, и тогда ключом будет сочетание полей Номер улицы + Номер дома + Номер квартиры + Номер проживающего. Таким образом, мы получили следующий набор таблиц. Таблица Улицы № Поле Тип Размер Описание 1. Street Числовой 4 Номер улицы 2. Name Текстовый 30 Название улицы 3. Type Текстовый 10 Тип улицы Таблица Здания № 1. 2. 3. 4. 5. 6. Поле Street House Year Wear Cost Elevator Тип Числовой Числовой Числовой Числовой Денежный Логический Размер 4 2 2 2 Авто 1 Описание Номер улицы Номер дома Год постройки здания Износ здания в процентах Стоимость здания в рублях Наличие лифта Таблица Квартиры № 1. 2. Поле Street House Тип Числовой Числовой Размер 4 2 10 Описание Номер улицы Номер дома № 3. 4. 5. 6. 7. 8. 9. 10. Поле Flat Rooms Square Account Surname First Second Year Тип Числовой Числовой Числовой Числовой Текстовый Текстовый Текстовый Числовой Размер 2 1 Авто 4 30 20 30 2 Описание Номер квартиры Количество комнат Площадь квартиры Номер лицевого счёта Фамилия квартиросъёмщика Имя квартиросъёмщика Отчество квартиросъёмщика Год рождения квартиросъёмщика Таблица Проживающие № 1. 2. 3. 4. 5. 6. 7. 8. Поле Street House Flat Number Surname First Second Year Тип Числовой Числовой Числовой Числовой Текстовый Текстовый Текстовый Числовой Размер 4 2 2 1 30 20 30 2 Описание Номер улицы Номер дома Номер квартиры Номер проживающего Фамилия проживающего Имя проживающего Отчество проживающего Год рождения проживающего Однако если мы внимательно посмотрим на таблицу Квартиры, мы увидим, что не выполняется шестое правило – между неключевыми полями таблицы не должно быть зависимостей. А в таблице Квартиры номер лицевого счёта и квартиросъёмщик однозначно определяют друг друга. Поэтому информацию о квартиросъёмщике надо вынести в отдельную таблицу. Таким образом, вместо одной таблицы Квартиры мы получим две таблицы: Квартиры и Квартиросъемщики. Ключом второй таблицы будет поле Номер лицевого счёта. Таблица Квартиры № 1. 2. 3. 4. 5. 6. № 1. 2. 3. 4. 5. 2.4. Поле Street House Flat Rooms Square Account Поле Account Surname First Second Year Тип Числовой Числовой Числовой Числовой Числовой Числовой Тип Числовой Текстовый Текстовый Текстовый Числовой Размер 4 2 2 1 Авто 4 Описание Номер улицы Номер дома Номер квартиры Количество комнат Площадь квартиры Номер лицевого счёта Размер 4 30 20 30 2 Таблица Квартиросъёмщики Описание Номер лицевого счёта Фамилия квартиросъёмщика Имя квартиросъёмщика Отчество квартиросъёмщика Год рождения квартиросъёмщика Связи Возникает вопрос – как, имея такой набор таблиц, мы можем определить, где проживает Иванов Иван Иванович, и есть ли лифт в его доме? Фамилии, имена и отчества проживающих у нас содержатся в таблицах Проживающие и Квартиросъёмщики. Положим, мы нашли нужного человека в таблице Проживающие. В той же записи имеются сведения о номере улицы, номере дома и номере квартиры. Однако, очевидно, что в таком виде адрес выводить нельзя. Номер дома и номер квартиры – это понятно, но пользователю обычно не известно, какая улица имеет какой номер в вашей базе данных, поэтому этот номер ему ничего не скажет. Однако, зная этот номер, мы можем найти соответствующую запись в таблице Улицы, узнать название и тип улицы и 11 вывести эти данные на экран. Кроме того, зная номер улицы и номер дома, мы можем найти соответствующую запись в таблице Здания, и вывести значение поля Лифт из этой записи, определив таким образом, есть ли лифт в доме, где проживает Иванов Иван Иванович. Все эти действия возможны, поскольку при проектировании таблиц мы подразумевали, что они существуют не сами по себе, что между ними существуют логические связи. В поле Номер улицы в таблице Здания записывается не любое значение, а только значение из таблицы Улицы, причём, записывает номер именно той улицы, где находится то или иное здание. Если не придерживаться этого правила, то информация в таблицах, действительно, распадётся на несвязанные части, и работать с такой базой данных будет бессмысленно. При проектировании базы данных в приложении Microsoft Access (или любом другом приложении разработки баз данные) будет необходимо явно указать, какие связи между таблицами существуют в вашей базе данных. Принято поля, связывающие таблицы, называть одинаково – в разных таблицах можно создавать поля с одинаковыми именами. Это упрощает нахождение и понимание связей, но не является строгим правилом. Связи между таблицами устанавливаются на основании значений полей, а не на основании имён полей. А вот тип связующих полей должен быть одинаковым! Выделяют четыре типа связей между таблицами. Связь «один к одному» означает, что каждой записи одной таблицы соответствует только одна запись другой таблицы и наоборот. В нашем примере такая связь существует между таблицами Квартиры и Квартиросъёмщики. Одна квартира – один квартиросъёмщик. Связь между таблицами поддерживается по полю Номер лицевого счёта. Связь «один ко многим» означает, что каждой записи одной таблицы соответствует несколько записей другой таблицы. В нашем примере таких связей несколько. На каждой улице стоит несколько домом. В каждом доме существует много квартир. В каждой квартире может проживать несколько человек (конечно, существуют квартиры, где проживает один человек, но они представляют собой частные случаи). Связь «многие к одному» полностью аналогична связи «один ко многим». Тип связи зависит исключительно от точки зрения. Связь «многие ко многим» возникает между двумя таблицами в тех случаях, когда одна запись из первой таблицы может быть связана более чем с одной записью из второй таблицы, а одна запись из второй таблицы, может быть связна более чем с одной записью из первой таблицы. Однако реляционная модель и реляционные СУБД не позволяют непосредственно работать с подобными связями. При возникновении таких связей необходимо вводить в БД дополнительные таблицы и преобразовывать связи к типам «один к одному» и «один ко многим». 12