РАБОТАЕМ С ДАННЫМИ | КОМПЬЮТЕРНАЯ ГАЗЕТА
Проектирование базы данных Как гласит народная мудрость, танец обычно начинается от печки, а создание базы данных - с хорошего проекта. (c) Компьютерная газета | И дело тут вовсе не в неких абстрактных правилах программирования. Просто текстовый документ, равно как и электронная таблица, представляет собой нечто целое и от других объектов мало зависящее. На худой конец, особого труда не составит в любой момент переставить абзацы контракта или строки таблицы. Редактирование при этом потребуется самое минимальное. Разве что со сводными таблицами придется малость помучиться, впрочем не особенно и долго. А вот с СУБД дела обстоят абсолютно иначе. Как вы уже могли заметить, вся идеология систем управления базами данных построена на высокой степени специализации каждого из компонентов. Таблицы хранят данные, но не манипулируют ими. Запросы управляют данными, но не хранят их. Формы вообще служат лишь для удобной демонстрации данных на экране монитора. Таким образом, для успешной работы всего проекта в целом следует непременно тесно взаимоувязать все эти компоненты и заставить их работать по единому плану. Я недаром сначала рассказал об основных компонентах СУБД Microsoft Access 97 и областях их применения. Лишь четко усвоив базовые принципы СУБД, можно разработать действительно хороший проект базы данных, который впоследствии не придется кардинально перерабатывать. И вообще, рекомендую запомнить одно "золотое" правило: хороший проект - это две трети успеха. А посему, давайте рассмотрим этот момент повнимательнее. Начинать всегда следует с того, чем конкретно должна будет заниматься ваша база данных. На этом этапе желательно взять большой лист бумаги, вооружиться ручкой или карандашом (кому что больше нравится) и начать аккуратненько записывать в столбик все ваши требования и пожелания. Не скромничайте и не стремитесь что-либо сразу систематизировать. Как показывает практика, только специально тренированные и особо талантливые люди способны сразу учитывать все аспекты конкретной задачи, с тем чтобы сразу верно систематизировать все ее тонкости и нюансы. Для остальных-то как раз и нужны карандаш с бумагой. В зависимости от того, насколько детально вы перед этим продумали механизмы работы своей будущей базы данных, составленный вами перечень займет одну или несколько рукописных страниц. Теперь наступает пора просмотреть все перечисленные требования и провести над ними две операции. Сначала следует удалить все повторяющие или явно взаимоисключающие условия, которые неизбежно появляются в результате любого "мозгового штурма". Потом оставшиеся пункты можно свести в одну общую таблицу, которая получится очень и очень длинной. Предположим, мы создаем базу данных для маленькой семейной бухгалтерии, чтобы быстро и точно учитывать все свои доходы и расходы. Следовательно, в перечень обязательных войдут следующие требования: учет всех поступлений по суммам; учет источников поступлений; учет текущего курса, например к доллару, так как высокие темпы инфляции в стране не позволяют корректно сравнивать даже цены на масло трехмесячной давности; учет наименований статей расходов по группам товаров; учет расходов по наименованию товаров; учет остатков средств; учет различных вариантов вложений средств в разные финансовые инструменты, например в валюту, на депозиты и так далее. Получается таблица из шести строк. Теперь пора заняться столбцами. Первый - это, конечно, те элементы, которые вы перечислили выше. Второй - это способ использования данной информации. Всего, как вы понимаете, могут существовать всего три возможных варианта: информация вводится, выводится или вычисляется. Допустим, заработная плата, прочие поступления и действующий курс доллара (неважно какой) обычно однозначно вводятся пользователем, в то время как величина доходов в валютном выражении однозначно вычисляется самой системой исходя из суммы полученных рублей и курса на момент их получения. Проставляя в каждой строке способ использования данной информации, следует предусматривать все возможные варианты. К примеру, однозначно видно, что цену на молоко вы будете не только вводить в качестве входного параметра, но и время от времени выводить на экран для просмотра (допустим, при демонстрации общей статистики за тот или иной период). Третьим в общей таблице будет столбец с описанием конкретного типа информации. Можете мне поверить, достаточно скоро вы и сами начнете путаться в собственных сокращениях и условных обозначениях, так что лучше позаботиться о себе заранее. Четвертый столбец мы пока оставим пустым. Перед его заполнением следует еще раз просмотреть всю получившуюся таблицу с целью определения степени достаточности перечисленной информации. В данном примере очевидны, как минимум, два упущения: подразумевалось, но не было указано конкретно, что желательно осуществлять пересчет всех расходов и цен в твердые денежные единицы (мы условились "привязываться" к доллару, хотя можно и к английскому фунту стерлингов и даже к евро); также не указывалось, коль уж мы заговорили о семейном бюджете, кто конкретно получил тот или иной доход или совершил определенную покупку. Вообще говоря, при составлении данной таблицы следует весьма серьезно задуматься и "взять на карандаш" даже те вещи, которые как бы сами собой разумеются или автоматически подразумеваются. В противном случае неизбежно окажется, что вы вспомните о них слишком поздно и либо их реализация потребует серьезной переработки уже почти законченного проекта (зачастую именно по этой причине остаются нереализованными многие из хороших идей), либо авральное дописывание приведет к нерациональному использованию ресурсов компьютера, снижению производительности самой базы данных и, как следствие, снижению ее потребительских характеристик. Хотя, конечно, по первому разу ошибки абсолютно неизбежны. И только теперь, когда первоначальная таблица полностью учитывает все пожелания, наступает пора проставить в последнем столбце своего рода символы принадлежности. Как я упоминал ранее, в СУБД все данные хранятся в таблицах. Этих таблиц обычно несколько, что позволяет избежать лишнего расхода системных ресурсов на многократное заполнение ключевых полей уже имеющейся информацией. Мы договорились в создаваемой СУБД вести учет в рублях и в американских долларах, причем программа самостоятельно будет определять текущую стоимость товаров и услуг в USD исходя из вводимого курса рубля к доллару. Однако таблица есть таблица, при ее ведении вам неизбежно придется либо проставлять курс каждый раз, как только будут вводиться какие бы то ни было рублевые суммы, либо сразу их пересчитывать в валюту и опять же вводить результат самостоятельно в соответствующую графу. И то и другое одинаково неудобно. Во-первых, любая из перечисленных величин может легко быть получена путем простой математической операции деления цены на курс. Во-вторых, одновременно и рублевые и валютные величины вряд ли когда-нибудь потребуются одновременно, да еще и ежедневно. В-третьих, раз данные могут быть получены расчетным путем, то их дублирование оператором есть не что иное, как непростительная пустая трата рабочего времени. Отсюда вырисовывается следующее очевидное решение: данные разделить на две таблицы - в одной содержать только наименования товаров и рублевые величины платежей, а в другой - текущий курс и дату его введения. Кроме таблиц, понадобится еще запрос, который будет брать рубли, делить на курс и показывать, сколько это в валюте. В результате мы можем добиться, как минимум, двух важных эффектов: СУБД не будет хранить избыточной информации и в то же время она станет производить пересчет только для тех данных, которые запросил пользователь в данный конкретный момент, чем значительно сэкономит вычислительную мощность персонального компьютера. Вот только теперь, когда стали просматриваться определенные взаимосвязи между теми или иными типами информации, перечисленными в первом столбце вашей рабочей таблицы, можно приступить к заполнению последнего ее столбца обозначениями принадлежности к тому или иному объекту. Обычно к единому объекту стремятся относить однородные вещи. К примеру, совершенно очевидно, что все поступления, откуда бы они ни исходили, относятся к одной группе, так как для данной СУБД это однозначно входящая информация. Следовательно целесообразно пометить в четвертом столбце первой и второй строк (источник поступления и его размер), что эта информация относится к объекту (пока не существующему) ПОСТУПЛЕНИЯ. Потом, мы уже разобрались, что нам неизбежно понадобится объект КУРС ВАЛЮТЫ. По аналогии с курсом целесообразно свести всех членов семьи в одну таблицу ПЕРСОНЫ, что позволит впоследствии легко корректировать этот список при возникновении такой необходимости. Все, что касается любых расходов, попадает в ведение объекта РАСХОДЫ. Несколько "бесхозным" остается только условие "Остаток средств", под которым мы подразумевали ведение учета остатка семейного бюджета после всех расходов. Его бесхозность заключается в том, что эта информация может быть получена двумя путями: посредством запроса просуммировать все расходы текущего периода, а результат отнять от суммы доходов за тот же период или проделывать то же самое, но уже периодически, а итог заносить в некую специализированную таблицу для хранения. По аналогии с курсом доллара, считаю наиболее рациональным отдельную таблицу для остатков не создавать, а получать их при помощи запроса, но чтобы не забыть что к чему, в графе "Объекты" этой строки мы проставим ОСТАТОК. На этом первый этап проектирования СУБД заканчивается и начинается второй. Теперь разработчик должен конкретизировать требования к каждому из намеченных ранее объектов и определить характер связей между ними. Опять берем карандаш, бумагу и приступаем к рисованию таблиц. Я не стану сейчас перебирать все объекты ввиду явного недостатка свободного места, остановлюсь лишь на двух принципиальных ситуациях, с которыми придется сталкиваться на втором этапе проектирования баз данных. Так как очертания объектов намечены лишь в самом первом приближении, то впоследствии вы можете обнаружить, что заданные параметры не полностью его описывают либо они чрезмерно избыточны или даже вообще неоднородны. Примером первого случая может служить описание объекта КУРС. Согласно сделанным ранее выкладкам, он должен являться таблицей, в которую время от времени будет вводиться некоторая величина, соответствующая количеству рублей и копеек, "даваемых" за один американский доллар. В рамках нашего проекта подобной информации явно недостаточно, так как объект КУРС никоим образом не может дать ответ на вопрос: в какой момент времени этот курс действует. Отсюда следует простой вывод: кроме столбца с котировкой курса, следует еще иметь столбец с датой его введения. Таким образом, сравнивая даты поступления дохода или совершения покупки с датой внесения очередного курса, можно достаточно легко определить, каким конкретно значением следует пользоваться при пересчете рублей в валюту. Второй случай имеет место быть в объекте РАСХОДЫ. Оставаясь однородным по отношению к первой нашей рабочей таблице, он, тем не менее, содержит чрезмерно большое количество разнородной информации, которая неизбежно будет дублироваться, - значит, требуется проанализировать его содержимое с целью более подробной селекции данных по типам. Если присмотреться, в объекте РАСХОДЫ должны храниться и действительно уникальные значения расходов (например, цена и количество купленного хлеба или колбасы), и повторяющиеся наименования общих расходных статей (например, коммунальные расходы, образование, развлечение и тому подобное). Не знаю как вы, а я, например, практически ежедневно покупаю молоко, чтобы варить ребенку какао. Следовательно, будь это моя база данных, то в ней быстро накопилось бы достаточно большое количество упоминаний о покупках молока в разной упаковке и по разным ценам, но постоянно относящихся к одной и той же категории "молочные продукты". Причем сюда же будут относиться кефир, сметана, масло, сливки и творог. В итоге мы приходим к необходимости расщепления объекта РАСХОДЫ на две обособленные части: ПЕРЕЧЕНЬ РАСХОДОВ и КАТЕГОРИИ РАСХОДОВ. В первую войдут только сами покупки, а во вторую - задаваемые вами самими общие категории для их учета. Независимо от того, что потребуется проделать с объектами, полученными после "раздела" первой рабочей таблицы, на втором этапе вы опять станете составлять таблицы. По одной таблице на один объект. И каждая таблица опять будет состоять из четырех столбцов. В первом, естественно, приводится наименование конкретного поля, например "Курс доллара" или "Дата". Во втором уже вполне детально конкретизируется тот тип данных, который следует использовать в том или ином случае, - если это наименование, значит предполагается текстовый тип, если это дата, значит понадобится тип дата/время и так далее. Причем тут же рекомендуется прикинуть и желаемую величину данных. Если для даты это не имеет особого значения, то в случае текста или чисел от изначально заданного размера во многом зависит и точность полученного результата и общий объем всей СУБД в целом. Третьим опять станет столбец с описанием того или иного поля. В дальнейшем вы оцените такую мелочь, как описание, ибо вскоре вам понадобится вся ваша память, чтобы не запутаться в проекте. Корректно созданные пояснения значительно облегчают жизнь разработчика, так что пренебрегать ими не стоит. Последним, четвертым, столбцом в данном случае должен быть столбец, в котором будет отмечен такой параметр, как "условие на значение". Совершенно очевидно, что количество любой покупки измеряется вполне конкретными единицами: килограммами, литрами, стандартизированными пачками, упаковками, нормализованными бутылками и тому подобным; таким образом, графа "Единицы измерения" требует наличия вполне определенного условия на значение. Хотя поле и текстовое, но вводить в него должно быть разрешено только заранее оговоренные комбинации символов. Аналогичным образом: цена товара не может быть отрицательной или количество стандартизированного товара не может быть дробным. Сомневаюсь, что вам продадут в магазине полбутылки молока или треть пачки чая. В том случае, когда ограничение очевидно, его стоит записать с должной степенью детализации; если же оно теоретически должно быть, но пока его не удается должным образом конкретизировать, то будет вполне достаточно просто отметить, что таковое вообще требуется. Теперь наступает черед такого важного действия, как нормализация. Если сложить вместе все таблицы, полученные на втором этапе, то весьма вероятно, что многие их поля будут повторяться. Это может быть дата, имя, единицы измерения или что-то еще. Если же прибавить к этому прослеживаемые связи между данными, то станет очевидным, что некоторые таблицы могут быть сокращены за счет создания новых таблиц, содержащих общие данные. Например, весьма недальновидно указывать получателя дохода (зарплата жены, авторский гонорар мужа и т.п.), а потом опять приводить те же фамилии в другой таблице - ПЕРЕЧЕНЬ РАСХОДОВ. В нашем случае этот момент уже нашел свое отражение в таблице ПЕРСОНЫ, однако во время нормализации могут всплыть и другие похожие ситуации. Второй "пользой" нормализации является окончательное уточнение всех взаимосвязей между данными вашей базы. Понятно, что для таблицы ПОСТУПЛЕНИЯ все суммы будут вводиться исключительно самими пользователями с клавиатуры и никак иначе, а вот при расчете остатка месячного бюджета запрос станет получать суммарный доход за тот или иной период только из таблицы ПОСТУПЛЕНИЯ. Примерно так же прослеживается очевидная связь между таблицей КУРС и любым другим объектом, в задачу которого входят расчеты, связанные с валютой. Таким образом, после нормализации вы должны получить три важных итога: во-первых, умозрительные пожелания теперь воплощены в конкретные типы и виды данных, а также в совокупность необходимых ограничений или граничных условий; во-вторых, все данные теперь взаимосвязаны между собой и эти связи предельно конкретизированы; в-третьих, совокупность таблиц и связей позволяет составить своего рода блок-схему функционирования всей базы данных в целом. Получив такую схему, не так сложно уже составить окончательно "доведенный" проект СУБД, на основании которого остается лишь воплотить схемы и графики в конкретные электронные таблицы, формы и запросы. И чем подробнее и скрупулезнее будет проделана вся первоначальная подготовительная работа, тем меньше потом уйдет времени на конкретное программирование. Проблема заключается в том, что "всплывшая" на каком-либо этапе ошибка или логическое несоответствие при программировании оборачивается порой даже полным изменением всего проекта в целом, что нередко вызывает переработку таблиц, переписывание запросов и написание новых форм. Тему проектирования базы данных я затронул не только чтобы показать, как это делается в принципе. Почти повсеместной практикой является ситуация, когда ставят задачу на создание базы данных - одни, указанную задачу реализуют - другие, а пользуются - третьи. Чтобы все они понимали друг друга, необходимо, чтобы они говорили на одном языке и оперировали категориями одного характера. Я надеюсь, что на эту информацию обратят внимание не только те, кто впоследствии станут разрабатывать приложения на основании СУБД Microsoft Access 97, но и те, для кого эти приложения будут создаваться... Александр Запольскис E-mail: leshy@nestor.minsk.by