Академический Документы
Профессиональный Документы
Культура Документы
Builderv
разработка приложений баз данных
Все права защищены. Любая часть этой книги не может быть воспроизведена в какой бы то ни
было форме и какими бы то ни было средствами без письменного разрешения владельцев авторс-
ких прав.
Все упомянутые в данном издании товарные знаки и зарегистрированные товарные знаки при-
надлежат своим законным владельцам.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых из-
дательством как надежные. Тем не менее, имея ввиду возможные человеческие или технические
ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведе-
ний и не несет ответственности за возможные ошибки, связанные с использованием книги.
От автора 7
Д
авно ушли в прошлое те времена, когда программисту, всю свою сознательную
жизнь проработавшему с языками программирования С или C++, приходилось
отказываться от любимого инструмента только потому, что возникала необходи-
мость проектировать базы данных. Еще свежи в памяти судорожные попытки этих не-
счастных найти библиотеки функций, написанных неизвестными героями, с помощью
которых можно было бы создать базу данных никому неизвестного или малоизвестного
формата с использованием языков C/C++. В такой ситуации почти никто не задумывался о
гарантиях безопасности и целостности данных. Понимание важности этих вопросов при-
ходило гораздо позже, когда большая часть приложения уже была сделана и отлажена, и
изменить что-либо не было никакой возможности.
Весь этот ужас для любителей языков программирования C/C++ закончился с выходом
в свет первой версии продукта C++ Builder от фирмы Borland. Кроме всех прочих пре-
лестей этот продукт содержал и так называемый механизм баз данных Borland (Borland
Database Engine, или, сокращенно, BDE). BDE — это набор системных библиотек DLL,
являющихся интерфейсом между приложением пользователя и базой данных выбранного
формата. Механизм BDE обеспечивал доступ к локальным базам данных (Paradox, dB-
ASE, текстовые файлы) и базам данных SQL (Oracle, DB2, Informix, Interbase, MS SQL
Server и др.). При помощи драйверов ODBC (Open Database Connectivity), разработанных
различными фирмами, можно было подключаться к файлам Access, Excel, FoxPro, Btrieve
и другим. Благодаря тщательно продуманным компонентам для доступа к базам данных и
их объектам можно было легко создать приложение, работающее со всеми популярными
в то время источниками данных.
С момента появления Borland C++ Builder прошло уже немало времени. C++ Builder
менялся от версии к версии, совершенствовался, становился более гибким и удобным, со-
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6
Базы данных:
классификация и этапы разработки
Прежде чем говорить о возможностях Borland C++ Builder 6 по созданию приложены!
баз данных, следует сделать несколько замечаний о базах данных как таковых. Основна
цель этого раздела — дать небольшой обзор терминологии, которая будет использоватьс;
в дальнейшем, а также обсудить общепринятые подходы к разработке баз данных.
Под базой данных (в дальнейшем иногда будет использоваться сокращение БД
обычно понимают хранилище информации, описывающей некую часть реального мира
Это означает, что в базе данных должно храниться описание определенного количеств!
реальных объектов, а также процессов, происходящих с этими объектами. Например, Б/
для учета движения товаров в магазине может содержать описание объектов товары, по
ставщики, покупатели, сотрудники и процессов поступление товаров, продажа това
ров, поступление денег, выплата денег.
С другой стороны, данное определение подразумевает, что хранящиеся в одной базе
данных объекты должны быть так или иначе связаны между собой при помощи про-
цессов, описание которых хранится в этой же базе данных. Другими словами, в одной
базе данных должны храниться объекты и процессы, представляющие собой целостную,
связную картину части реального мира. Если объекты и процессы распадаются на две не
связанные друг с другом группы, их следует хранить в разных базах данных. И объекты, и
процессы мы в дальнейшем будем называть объектами.
Приложение БД (Database Application) — это программа, которая в процессе выполне-
ния может подключаться к БД для манипуляций или структурой самой базы данных, или
хранящимися в ней данными. Одно приложение БД может подключаться как к одной, так
и (одновременно) к нескольким базам, расположенным или на одном из локальных или
сетевых дисков, или на удаленном сервере. Верно и обратное: к одной базе данных могут
быть одновременно подключено несколько приложений, в том числе через локальную
сеть или удаленное соединение.
Исходя из данного выше определения, базой данных можно называть как совокупность
хранящейся в ней информации о различных объектах (например, список всех товаров,
клиентов или банковских операций), так и описание структуры отдельных объектов или
самой БД. Чтобы избежать двусмысленности, описание БД (структуры) часто называют
моделью данных (Data Model). Модель данных создается или изменяется в процессе раз-
работки базы данных или при модификации ее структуры во время работы приложения
БД. Условно модель данных можно считать шаблоном, который заполняется информацией
при использовании БД.
Подходы к классификации
Существует несколько общепринятых подходов к классификации баз данных, характе-
ризующих различные аспекты их использования. Важнейшие из них два. В соответствии
с первым подходом БД разделяются на реляционные и нереляционные. Если информация в
БД хранится упорядоченно, такая база данных называется реляционной, в противном слу-
чае — нереляционной. Упорядоченной называется информация, доступ к которой можно
получить унифицированным способом. Если метод доступа к информации зависит от ее
содержания, то такая информация называется неупорядоченной.
Если быть ближе к практике, можно считать, что если информация в базе данных хра-
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder б
нится в виде обычных двумерных таблиц, то она является реляционной. В качестве при-
мера нереляционных баз данных можно упомянуть хранилища файлов различных типов
(текстовых, графических и т.д.), для доступа к которым применяются различные методы,
архив сообщений электронной почты, база данных, в которой хранится список файлов
наряду с самими файлами, и тому подобные источники информации. В дальнейшем речь
будет идти только о реляционных базах данных, хотя провайдер данных OLE DB, который
используется для доступа к данным по технологии ADO, позволяет работать и с нереля-
ционными источниками информации.
Второй из упомянутых подходов к классификации — комбинированный. При его ис-
пользовании учитывается способ доступа к базе данных (не к информации!), способ физи-
ческого хранения информации и другие характеристики, касающиеся размещения и хране-
ния данных. В соответствии с этим подходом можно выделить четыре типа баз данных.
Q Локальные — предназначены для решения простых задач обработки информации. БД
этого типа хранятся в одном из каталогов локального компьютера (отсюда и название)
в виде отдельных файлов. Каждому объекту (таблице) БД соответствует как минимум
один файл. Кроме файла таблицы каждому объекту может соответствовать один или
несколько служебных (например, индексных) файлов. Если учесть эту особенность,
становится понятно, почему создать сложную базу данных этого типа и управлять ею
— невероятно трудно, а зачастую и просто невозможно. Кроме того, хотя к локальным
базам данных возможен доступ через локальную сеть или через удаленное соединение,
они все же не предназначены для многопользовательского доступа. Типичные приме-
ры локальных баз данных — dBASE, FoxPro, Paradox.
О Файл-серверные БД, в отличие от локальных, предназначены для многопользователь-
ского доступа через сеть. Вся информация, включая служебную, в БД файл-серверного
типа хранится в одном файле. При доступе через сеть на локальном компьютере (т.е. на
компьютере, с которого осуществляется доступ) создается собственная копия данных.
Если используется большой объем данных, сеть может испытывать значительную на-
грузку, так как при этом заметно увеличивается сетевой трафик. Кроме того, поддерж-
ка многопользовательской работы в базах данных этого типа не реализована в полной
мере. В связи с этим файл-серверные базы данных используются в основном для ра-
боты на локальном компьютере или в небольшой локальной сети. В качестве примера
СУБД этого типа можно привести Microsoft Access.
Q Клиент-серверные БД предназначены для создания сложных, корпоративного уров-
ня информационных систем, предоставляющих доступ как по локальной сети, так и
через удаленное соединение. К клиент-серверной БД может получить доступ огром-
ное количество пользователей одновременно. При клиент-серверной организации
информационных систем сама БД хранится на удаленном компьютере, называемом
удаленным сервером. Именно на этом компьютере и происходит вся обработка ин-
формации, включая проверку допустимости различных действий пользователя (до-
бавление, удаление или редактирование информации). Клиент посылает удаленному
серверу определенным образом сформулированный запрос на выборку информации
или на производство каких-либо действий, сервер обрабатывает запрос и отсылает
ответ обратно на компьютер пользователя. При таком подходе значительно снижается
сетевой трафик (количество передаваемой в обоих направлениях информации в еди-
ницу времени), уменьшается время ожидания ответа каждым из клиентов (из-за равно-
мерного распределения ресурсов), возрастает надежность и устойчивость работы всей
14 Borland C++ Builder6. Разработка приложений баз данных
Если ответ на какой-либо из вопросов вам не ясен, следует повторно опросить предпо-
лагаемых пользователей. Таким образом, сбор информации также может быть итера-
тивным.
2. Разбиение информации на объекты (сущности) и атрибуты. На этом этапе необхо-
димо проанализировать всю информацию на концептуальном уровне и распределить
ее на объекты (сущности) и атрибуты. Объекты представляют собой конкретные
реальные объекты или процессы, которые вы собираетесь описывать в базе данных.
Например, если вы создаете базу данных для учета деятельности небольшого магази-
на, то к объектам (сущностям) можно отнести клиентов, поставщиков, сотрудников,
приобретение, расход и т.д. Атрибуты — это свойства объекта, при помощи которых
он и описывается. Например, для объекта клиент атрибутами могут быть название
клиента, его адрес, банковские реквизиты, контактные телефоны, Web-адрес и тому
подобная информация. В базе данных объектам соответствуют таблицы, а атрибутам
объектов — столбцы (поля) таблицы.
На этом этапе также необходимо удалить из объектов излишнюю информацию и пе-
ренести ее в другие объекты. Например, если объект описывает расходные накладные,
то нет смысла хранить подробную информацию о клиенте в каждой записи. Вместо
этого нужно создать новый объект, описывающий клиентов, а в объекте расходные
накладные достаточно оставить ссылку на конкретного клиента. При помощи этой
ссылки объект клиент может быть связан с объектом расходные накладные. Разбиение
больших разнородных блоков информации на более мелкие однородные структуры и
установление связей между ними называется нормализацией (Normalization).
5. Отображение системы объектов (сущностей) и их атрибутов на таблицы и столб-
цы. На этом этапе в соответствии с планом, разработанным в предыдущем пункте,
создаются таблицы. Для каждой таблицы определяются первичные и внешние ключи,
а также индексы. Именно на этом шаге проявляется большая часть ошибок, допущен-
ных в процессе нормализации.
t. Определение атрибутов, однозначно идентифицирующих каждый объект. На этом
этапе для каждой таблицы необходимо определить столбец или группу столбцов, при
помощи которых можно однозначно отделить одну запись от другой. Такой столбец (или
группа столбцов) служат в качестве первичного ключа (Primary Key). В качестве первич-
ного ключа можно указать один или несколько уже существующих атрибутов или соз-
дать новый атрибут специально для этих целей. Например, если у вас имеется таблица
Персонал, то в качестве первичного ключа можно использовать столбцы Фамилия, Имя,
Отчество. Однако в этом случае вы не сможете ввести в таблицу запись о сотруднике, у
, которого фамилия, имя и отчество совпадают с соответствующими атрибутами сотруд-
ника, принятого на работу ранее. Другими словами, такая таблица не приемлет полных
тезок. Из этой ситуации можно выйти, сделав допущение, что полные тезки не могли
родиться в один день. Конечно, это не исключено, но практически можно считать такое
совпадение невероятным. Вам остается только добавить к столбцам Фамилия, Имя,
Отчество, составляющим первичный ключ, еще и столбец Дата рождения.
Чаще, однако, в качестве первичного ключа используют дополнительный служебный
столбец, который не несет никакой другой смысловой нагрузки, кроме уникальной
идентификации каждой записи. Обычно это просто номер записи. Каждая новая за-
пись в простейшем случае получает в качестве номера увеличенный на единицу номер
предыдущей (последней) записи. Таким образом обеспечивается уникальность каждой
10 Borland C++ Builder 6. Разработка приложений баз данных
записи. Следует обратить внимание на то, что каждая СУБД имеет свои собственнь
средства поддержки первичных ключей.
Кроме первичных ключей на этом этапе вы должны также задать, если это нужн
столбцы, которые будут использоваться в качестве внешних ключей (Foreign Keys,
Определив на этом шаге столбцы, которые будут ключами (первичными или внешт
ми), вы сможете впоследствии использовать их для задания связей между таблицам:
Каждому первичному ключу одной таблицы при такой связи будет соответствова!
любое количество (теоретически от нуля до бесконечности) записей из связанной Т!
блицы. О связях между таблицами будет рассказано чуть позже.
5. Разработка системы правил, устанавливающих для каждой таблицы вариант!
доступа, модификации и добавления новых записей. На этом этапе вы должш
уточнить для каждого из атрибутов (столбцов) характеристики, связанные с выбран
ным для него типом данных. Например, для столбца, содержащего текстовые значе
ния, необходимо указать максимально допустимую длину строки, для числовых типо.
указать, какое число может быть допустимым — целое, длинное целое, с плавающее
точкой и т.д. Кроме того, вы должны задать, если это необходимо, и остальные ха
рактеристики столбцов: какие значения допустимы, может ли в какой-либо из ячее!
данного столбца содержаться значение NULL (т.е. допускается ли для данного столбцг
отсутствие значения хотя бы в одной ячейке), какое значение является для столбцг
значением по умолчанию (т.е. что будет подставлено в соответствующую ячейку ново*
записи, если вы не введете в нее определенного значения).
6. Фактическое задание связей между таблицами при помощи первичных и внешни}!
ключей, запланированных на ранних стадиях проектирования. На этом этапе устанав-
ливаются связи между таблицами. В реляционных базах данных это делают при помощи
первичных и внешних ключей, зарезервированных на этапе конструирования таблиц. Как
уже упоминалось, первичный ключ служит для однозначной идентификации каждой за-
писи таблицы. Таким образом, в таблице может быть только одна запись с конкретным
значением первичного ключа. Каждому значению первичного ключа таблицы может соот-
ветствовать несколько записей с совпадающим значением внешнего ключа. Предположим,
например, что таблица Клиенты связана с таблицей Отгрузки. Для связи используется
столбец Номер клиента, являющийся в таблице Клиенты первичным ключом, и столбец
Клиент, который служит внешним ключом в таблице Отгрузки. Предположим далее, что
в ячейке столбца Номер клиента, соответствующей записи о клиенте Big Technologies, со-
держится число 5. Тогда любая запись таблицы Отгрузки, в которой в столбце Клиент
содержится число 5, будет относиться именно к клиенту Big Technologies. С другой сто-
роны, если внешний ключ какой-либо записи в таблице Отгрузки содержит число 5, то
в таблице Клиенты должна существовать только одна запись, первичный ключ которой
имеет значение 5. Если такой записи нет, значит, нарушена ссылочная целостность дан-
ных (Referential Integrity). Таким образом, используя совпадающие значения первичного
и внешнего ключа, можно для каждой записи из таблицы Отгрузки получить подробную
информацию о клиенте, которому была произведена отгрузка.
Существует несколько вариантов фактического задания связей. Их можно задать при
помощи визуальных инструментов (как в Microsoft Access или CASE-инструментах)
или при помощи соответствующего SQL-оператора. Независимо от способа задания
связей, системы управления базами данных автоматически поддерживают правила
ссылочной целостности, задаваемые этой связью.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6
Описанная выше связь между таблицами Клиенты и Отгрузки — это связь типа
«один-ко-многим». При такой связи каждой записи одной из таблиц может соот-
ветствовать несколько записей (от нуля до бесконечности) из другой таблицы. Это
наиболее распространенный тип связей. Существует еще два типа связей — «один-к-
одному» и «многие-ко-многим». Связь типа «один-к-одному» означает, что каждой за-
писи из одной таблицы соответствует только одна запись из другой таблицы. Этот тип
связи используется очень редко. Единственный практический смысл его применения
— разбиение таблицы с очень большим количеством столбцов на несколько таблиц
меньшего размера.
При связи «многие-ко-многим» нескольким записям из одной таблицы могут соот-
ветствовать несколько записей из второй. Связь этого типа организуется при помощи
третьей, вспомогательной таблицы и, по сути, является совокупностью двух связей ти-
па «один-ко-многим». В качестве примера связи типа «многие-ко-многим» можно при-
вести связь между таблицами Сотрудники и Проекты. Над проектом может работать
несколько сотрудников, и сотрудник может работать над несколькими проектами сразу.
Для реализации связи в этом случае необходимо создать новую таблицу, например,
Сотрудники-Проекты. Каждая запись этой таблицы ставит в соответствие номер со-
трудника номеру проекта, в котором он участвует. Таким образом, первичному ключу
Номер сотрудника таблицы Сотрудники соответствует внешний ключ Сотрудник та-
блицы Сотрудники-Проекты, а первичному ключу Номер проекта таблицы Проекты
соответствует внешний ключ Проект таблицы Сотрудники-Проекты.
На этом же этапе следует указать условия целостности данных и задать необходимость
каскадного обновления и удаления связанных данных. Подробнее об этом будет сказа-
но далее в этой книге.
7. Выбор индексов для каждой таблицы. Хотя набор индексов проектируется при
создании каждой конкретной таблицы, на этом, практически завершающем этапе
разработки БД нужно окончательно решить, сколько и каких индексов необходимо
для каждой из таблиц. С одной стороны, чем больше индексов, тем быстрее будут
выполняться поиск, фильтрация и сортировка. С другой стороны, если таблица имеет
большое количество индексов, то будут медленно выполняться вставка, удаление и
модификация записей. Особенно это заметно для больших таблиц, имеющих порядка
ста тысяч записей или больше. В связи с этим для каждой таблицы следует определить
оптимальный состав индексов. Особенно тщательно это нужно делать для тех таблиц,
которые могут содержать большое количество записей.
Несмотря на простую формулировку задачи, определить оптимальный состав ин-
дексов в каждом конкретном случае не так просто. Исходя из опыта, можно посовето-
вать не создавать в одной таблице больше трех индексов. При этом следует учесть, что
большинство систем управления базами данных автоматически создают индекс, соот-
ветствующий первичному ключу. Приняв во внимание такие ограничения, необходимо
создать несколько индексов на основе тех столбцов, которые чаще всего участвуют в
операциях сортировки, поиска и фильтрации. Например, для таблицы Персонал мож-
но создать составной индекс на основе столбцов Фамилия, Имя, Отчество и простой
индекс на основе столбца Дата приема на работу.
Конечно, это только рекомендации. Более точно определить необходимость того
или иного индекса можно при помощи эксперимента. Для этого необходимо измерить
время выполнения запросов на выборку, вставку и модификацию данных до того, как
Borland C++ Builder б. Разработка приложений баз данных
вы определите новый индекс. Естественно, все эти запросы должны активно исполь
зовать столбцы, которые вы предполагаете включить в индекс. Далее, необходимс
измерить время выполнения тех же запросов, но уже после определения индекса. Не
основании полученной информации можно сделать вывод о целесообразности его ис
пользования. Следует помнить о том, что если таблица, над которой вы проводите экс
перимент, может содержать большое количество записей (например, порядка 100 000)
то испытывать индекс нужно примерно при таком же количестве записей. Эксперимеш
над таблицей, содержащей всего 100 строк, не даст вам объективной информации.
8. Планирование системы безопасности. Этот этап разработки БД, хотя и не имеет
непосредственного отношения к формированию модели данных, все же достаточно
важен, особенно если вы разрабатываете многопользовательскую информационную
систему. На этом этапе определяется перечень пользователей, которые могут опериро-
вать с базой данных. Для каждого пользователя устанавливается допустимый уровень
разрешений: может ли пользователь только просматривать конкретную таблицу базы
данных или он может вносить изменения в записи таблицы; может ли он модифициро-
вать макет таблицы или ему разрешен только просмотр. Организация системы безопас-
ности зависит от типа разрабатываемой БД (скажем, система безопасности, поддержи-
ваемая Microsoft Access, отличается от системы для Interbase). Так что на этом этапе
вам следует обратиться к соответствующей справочной документации, четко уяснив
себе все имеющиеся в вашем распоряжении возможности.
Успешно пройдя все восемь этапов разработки базы данных, не спешите сразу при-
ступать к непосредственному воплощению продуманной схемы. Вначале нужно оценить
логическую структуру получившейся базы данных как единого целого с высоты получен-
ного при разработке опыта (к концу разработки обычно начинаешь понимать те аспекты
описываемой картины реального мира, о которых не имел никакого понятия в самом на-
чале). Найдя какие-либо логические ошибки, недочеты или несоответствия, необходимо
вернуться к первому этапу и перепланировать модель данных с самого начала. Таким
образом, разработка модели данных итеративна. Не стоит экономить на это время: потра-
ченное на планирование и разработку модели данных, оно с лихвой окупится позднее, при
ее непосредственной реализации и эксплуатации.
Далее будут описаны два очень полезных инструмента, поставляемых вместе с C++
Builder 6, — SQL Explorer (Database Explorer) и Database Desktop. С их помощью вы смо-
жете создавать, просматривать или модифицировать структуру и данные БД различных
форматов. Конечно, эти утилиты не могут составить конкуренцию специализированным
средствам управления базами данных конкретного типа, однако, возможно, это будут
единственные инструменты, которые окажутся в вашем распоряжении.
SQL Explorer
SQL Explorer — иерархически организованный браузер, основное назначение которого
— в подробном исследовании структуры баз данных. Вы не можете с его помощью соз-
давать базы данных, а также создавать, модифицировать или удалять таблицы, индексы
и другие объекты. Однако вы можете модифицировать записи в выбранных таблицах, а
также вводить и выполнять SQL-операторы. Кроме того, с помощью SQL Explorer'a мож-
но создавать и модифицировать псевдонимы (Alias) базы данных, словари (Dictionary) и
наборы атрибутов (Attribute Set). Но об этом чуть позже.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 21
Утилита SQL Explorer поставляется вместе с C++ Builder 6 версии Enterprise.
Соответствующая утилита есть и в версии поставки Professional, однако называется она
Database Explorer, и ее возможности несколько ограничены. В дальнейшем речь пойдет
именно об SQL Explorer.
Запустить SQL Explorer можно двумя способами: из самой среды C++ Builder (IDE
C++ Builder, или просто IDE) и как отдельное приложение. В первом случае необходимо
выбрать пункт Explore меню Database, а во втором — запустить приложение dbexplor.exe,
размещенное в подкаталоге BIN каталога, в который установлен C++ Builder (для такого
каталога принято обозначение $(ВСВ)). Оба варианта запуска практически равнозначны,
однако первый предпочтителен. Если вы использовали первый вариант, то окно приложе-
ния будет обозначать свое присутствие не кнопкой на панели задач Windows, а отдельным
пунктом меню Window в списке открытых окон. В любом случае, на экране будет отобра-
жено окно, показанное на рис. 1.1.
•Qbtecl йсболюу
е, X .- : ':
I All Database Aliase
Detebese s ) DicSoi Definition 1
) Databases Type STANDARD
- DEFAULT DRIVER PARADOX
+. Ho DefauHDD ENABLE BCD FALSE
+ *o IBLocal PATH Deprogram Files\Common Files\Boriwid Shoied\Data
t; TJ myAccees
P. ТГ База данныхМЗ Access
:*j Ti Бвзадвнных Visual FoxPro
"S "о Та&лииы Visual FoxPro
-f. TJ Файлы dBASE
jj TS Файлы Excel
_Ш
Создать новый псевдоним довольно просто. При выборе пункта New меню Object Hi
экране будет отображено диалоговое окно, позволяющее указать в выпадающем списю
тип драйвера базы данных (см. рис. 1.2). Обычно доступны следующие драйверы (в за-
висимости от варианта инсталляции): STANDARD, MSACCESS, SYBASE, ORACLE,
MSSQL, INFORMIX, DB2, INTRBASE. Тип драйверов, возможно, кроме первого, поня-
тен из их названия. Драйвер STANDARD поддерживает локальные базы данных Paradox
(таблицы в файлах с расширением db), dBASE и FoxPro (таблицы в йй/^файлах), а также
таблицы в текстовом формате (файлы с расширением txt). Выбрав нужный драйвер, на-
жмите кнопку ОК. Чтобы зарегистрировать новый псевдоним, нужно выбрать пункт Apply
все того же меню Object.
Це!р
Databases jpidronaryj
;-; Q) Databases _*j Nome : Type Siie Prnls H,y
EV ЭД BCDEMOS И NAME CHARACTER 10 10
г о г
:
в & Tables •SEE NUMERIC
- И animals dbt EWEIGHT NUMERIC 2 о г
Я ©Ms fflAREA CHARACTER 20 го
: : Ot NAME _J ШВ BMP BINARY 10 10
: ilH SIZE
i ; t DB WEIGHT
: ф Щ AREA
; • ж и BMP
Т ЁВ Indices
.fiUfl VelidiV Checks
В Ё1 Referential Constraints
i ,*i gg Securrty Specs
| b'fil FamiV Members »j Ч . _. i. j i J Jj
5F«ld,,™»,™l-:.db., —• -
Замечание
Не забудьте, перед тем как использовать новый псевдоним, сохранить его. Для этог
предназначена команда Apply меню Object. Соответствующая клавиатурная комбинаци
- Ctrl + A.B противном случае вы получите сообщение об ошибке.
! Аи Detnbane Aliases
Detebages j Dicjionaty
Databases
« 6CDEWOS
В Ш Tables
!*• H animals dbf
1 ahcce Category-' Sheik' jj ^
•
-IJ . ""Г
•*. В bk)lTte,db
<l 1 ^i
1
QD countrydb {Category |Cornnion_Nani( jSpeeiasName., . ~*j
И cusioiy.dQ HShark Swell Shark Cephatoscyilium ventrtosur {
OS customer db J Shark Nurse Shark Ginglyrnostoma cirratum ^ i
!__ И employee. db
tj <} •' -M-:- _>J
СйаЬэ,» Oirionay)
i -Ш Dictioneiy
r-: El Database!
: «-П BCDEMOS
; ш-flB DBDEMOS
• -v Я Фейлы dQASE
S И Attribute Sets
•«•И Company
:+ H Continent
Referencing Fielt
И BCDEMOS.O.:
-•BCOEMOS or
EOHOEMuviujd
DatabaseDesktop
Возможности Database Desktop несколько больше, чем у SQL Explorer. С помощью
этой утилиты вы сможете создавать и модифицировать таблицы в форматах Paradox,
dBASE и SQL, добавлять, удалять, редактировать записи, сортировать информацию, вы-
полнять запросы, и многое другое. Database Desktop поставляется как отдельная утилита.
Запустить ее можно или при помощи пункта Database Desktop меню Tools, или найдя
файл dbd32.exe. Обычно этот файл расположен в каталоге /Common Files/Borland Shared/
Database Desktop/. В этом разделе кратко описаны основные возможности, предоставляе-
мые Database Desktop. За подробной информацией обратитесь к справочной системе.
:teble>_StoreNo
s-tor,.
Замечание
В нижней части диалогового окна Select Borrow Table расположено несколько
флажков. Устанавливая эти флажки, вы сможете добиться импорта следующих объектов:
Первичный ключ/индекс (Primary index), Проверка на допустимость (Validity check),
Таблицы просмотра (Lookup table), Вторичные индексы (Secondary index), Ссылочная
целостность (Referential integrity).
Резюме
Эта глава посвящена вводным замечаниям о БД. В частности, рассматриваются
несколько основных подходов к классификации, вводятся основные понятия, исполь-
зуемые при дальнейшем изложении вопросов проектирования БД. Не имея опыта,
практически невозможно создать устойчиво работающую, надежную и удобную БД.
Рекомендации, представленные в этой главе, помогут не делать ошибок с самого на-
чала проектирования.
В конце главы описываются две утилиты, поставляемые вместе с C++ Builder 6. SQL
Explorer и Database Desktop могут оказаться очень полезными, особенно если у вас нет
специализированных инструментов от сторонних разработчиков. Описывается, как с по-
мощью этих утилит создать таблицу, настроить должным образом поля, индексы и другие
объекты. Рассказывается о способах создания и использования псевдонимов, словарей и
наборов атрибутов.
Использование механизма BDE
В ЭТОЙ ГЛАВЕ
Основы BDE
Компонент liable
Навигация, поиск, фильтрация и другие элементарные действия с набором данных
Определение состояния набора данных и объект TDataSource
Постоянные и динамические поля
Объект TField и редактор полей
Вычисляемые поля и поля подстановки
Создание связи Master/Detail
Режим кэширования изменений
Создание таблиц программным способом
Резюме
В
предыдущей главе были рассмотрены основные понятия, связанные с БД. Это
вполне логично — прежде чем начинать разработку приложений БД, необходимо
получить некоторое представление о самих базах данных, а также научиться кон-
струировать хотя бы простейшие таблицы. Следующие несколько глав посвящены разным
аспектам создания приложений, взаимодействующих с БД различных типов: вариантам
подключения к локальным и клиент-серверным базам, использованию стандартных
и клиентских наборов данных, элементов визуализации и управления данными и другим
связанным с этими вопросам.
В этой главе будут обсуждаться вопросы создания приложений, которые для связи
с базами данных используют механизм BDE. BDE часто называют также процессором
баз данных Borland. Фактически BDE представляет собой библиотеку функций API, при
помощи которых разработчик сможет подключаться к БД, получать выборки данных, ма-
нипулировать информацией и структурой таблиц и осуществлять практически все харак-
терные для информационных систем действия.
Механизм BDE предоставляет разработчику единый интерфейс для взаимодействия
приложений с БД различных типов. Для подключения к базе данных может использовать-
ся один из трех вариантов. Для подключения к локальным БД (FoxPro, dBASE, Paradox,
текстовые таблицы) и к базам данных Microsoft Access используются так называемые
естественные (Native) драйверы. Естественными они называются потому, что являют-
ся для BDE базовыми. Использование именно этих драйверов дает наиболее полный
контроль над подключением к источникам данных. Группа драйверов, называемая SQL
Links, служит для подключения к удаленным серверам баз данных (Interbase, DB2, Oracle,
Informix, MS SQL Server и Sybase). Третью группу составляют драйверы ODBC, при по-
мощи которых BDE может подключиться к любой базе данных (при условии, что для нее
существует работоспособный драйвер ODBC).
30 Borland C++ Builder 6. Разработка приложений баз данных
Чтобы сделать поле CategoryNo ключевым, нужно дважды щелкнуть мышкой в соот-
ветствующей ячейке столбца Key. Тогда в этой ячейке появится символ звездочки (*).
Любое поле, для которого в столбце Key отображен символ звездочки, входит в состав
первичного ключа. При создании ключа Paradox автоматически создает и первичный
индекс, называемый <Primary>. Остальные индексы, создаваемые программистом, на-
зываются вторичными (Secondary Indexes).
Следующие два поля имеют текстовый тип (Alpha). Поле Category размером 25 сим-
волов будет содержать наименование категории товаров, а поле Description размером 50
символов предназначено для краткого описания каждой категории. Поле Category явля-
ется обязательным, поэтому для него необходимо установить флажок Required Field. Это
приведет к тому, что для этой таблицы будет создано объект Проверка допустимости
(Validity Checks).
Теперь таблицу можно сохранить. Для начала создайте какой-нибудь каталог, напри-
32 Borland C++ Builder 6. Разработка приложений баз данных
мер, D:\Data. Далее, нажмите кнопку Save As, выберите созданный ранее каталог и укаж]
те для таблицы имя — Category. Кроме файла таблицы category.db в этом же каталоге б;
дут еще два файла — category.px (для хранения информации о primary key) и category. v>
(объекты Validity Checks). Кроме этих файлов могут быть и другие. Например, вторичнь
индексы будут храниться в файлах category.xgO и category.ygO (первый индекс), categi
ry.xgl и category.ygl и т.д. Поля типа MEMO будут храниться в файле category, mb и т.^
1
Таким образом, для одной таблицы Paradox может быть создано много файлов, что, коне
но же, затрудняет работу над большими проектами, содержащими множество таблиц.
Замечание
Прежде чем сохранить таблицу на диске, нужно выбрать язык, который буде
использован при вводе в таблицу текстовой информации. Для этого из комбинированной
списка Table properties следует выбрать пункт Table Language и нажать кнопку Modify. Hi
экране будет отображено диалоговое окно, в котором нужно выбрать подходящий язык
Если вы собираетесь вводить информацию в таблицу Paradox на русском, то подойде
Paradox Cyrr 866. К сожалению. Database Desktop, скорее всего, не будет правильнс
отображать русский текст в режиме просмотра или модификации таблицы. Зато SQI
Explorer будет отображать русский текст вполне корректно.
Замечание
В C++ Builder б инспектор объектов предоставляет возможность просмотреть целиком
всю цепочку между элементом управления данными (например, TDBGrid) и таблицей БД.
Щелкая мышкой на элементе развертывания слева от свойства DataSource, мы получаем
доступ к списку свойств объекта DataSourcel, среди которых есть и свойство DataSet,
имеющее значение Table?. Аналогично, разворачивая список свойств объекта Tablel, мы
можем получить доступ к его свойствам DatabaseName и TableName. Таким образом,
инспектор объектов предоставляет возможность в удобной иерархической форме
увидеть связь между конкретным элементом управления данными и таблицей БД.
Благодаря этому простому анализу можно получить представление о том, каким об-
разом элементы визуализации и управления данными (Data Controls), такие как TDBGrid
или TDBEdit, связываются с информацией из БД. В первую очередь следует выделить
2 Зак. 319
34 Borland C++ Builder 6. Разработка приложений баз данных
жимая кнопку Deactivate, можно отключать или включать набор данных, а при помош
кнопки Status проверять его состояние (см. рис. 2.4).
Второй способ активации/деактивации набора данных заключается в применении м<
тодов Open и Close. Объявление этих методов имеет следующий вид.
void fastcall Open(void);
void fastcall Close(void);
Вызов любого из этих методов соответствующим образом меняет и значение свойств
Active. Метод Open устанавливает для Active значение true, метод Close устанавливав
значение false. Учитывая это, обработчик события OnClick для кнопки DeactButton мож»
представить в таком виде:
if(Tablel->Active)
{
Tablel->Close() ;
DeactButton ->Сарtion="Activate";
}
else
{
Tablel->0pen();
DeactBut ton->Caption="Deactivate";
}
Очевидно, что использование свойства Active во многих случаях позволяет писать
более компактный код, зато применение методов Open и Close позволяет сделать код не-
сколько производительней.
Таблица Items
Для дальнейшего изложения нам понадобится таблица, имеющая в своем составе
достаточно много разнообразных записей. Вполне подойдет таблица, содержащая
перечень всех товаров, которыми торгует вышеупомянутая гипотетическая фирма Big
Computer. Каждый из товаров должен принадлежать к одной из категорий, перечис-
ленных в таблице Category.
Для создания нужной нам таблицы снова воспользуемся утилитой Database Desktop.
Создайте новую таблицу типа Paradox 7 и добавьте определения следующих полей:
Q ItemNo. Тип (Туре) — Autoincrement. Это поле следует также отметить звездочкой
в столбце Key, так как его предполагается использовать в качестве первичного ключа.
Q Item. Тип (Туре) —Alpha, размер (Size) — 50. Предназначено для хранения наименований
товаров. Установите флажок Required Field, так как это поле не может быть пустым.
38 Borland C++ Builder 6. Разработка приложений баз данных
Q Category. Тип (Туре) — Long Integer. Флажок Required Field должен быть установле
Это поле предназначено для связи с таблицей Category, т. е. оно будет внешним клк
чом. Значение, хранящееся в поле Category, должно ссылаться на первичный клк
1
таблицы Category. Обратите внимание на следующее: первичный и внешний клю
связываемых таблиц должны иметь один и тот же тип, иначе связь установить i
удастся. В данной ситуации мы поступили правильно, так как тип Long Integer эквив;
лентен типу Autoincrement.
Q Description. Тип (Туре) —Alpha, размер (Size) — 100. Необязательное поле, в которо
может быть размещено краткое описание товара.
Q Index. Тип (Туре) — Alpha, размер (Size) — 5. Это текстовое поле длиной 5 символе
служит для однозначной идентификации товаров, если учет ведется в нескольких н
связанных между собой БД. Важность поля Index становится понятной, если учест
тот факт, что в разных таблицах Items одному и тому же товару будут соответствоват
различные первичные ключи. В такой ситуации тщательно разработанная систем
индексов (артикулов и т.д.) поможет избавиться от всех проблем, связанных с синх
ронизацией данных из различных источников. В частности, в состав индекса можн<
ввести информацию о категории, подкатегории и т.д. В данной таблице поле Inde
имеет длину пять символов. Предполагается, что первый символ указывает на катего
рию, второй — на подкатегорию, третий может обозначать разновидность товара шн
его производителя. Последние два (или три) символа указывают на конкретный товар
Будем считать, что поле Index является обязательным, так что флажок Required Fields
нужно установить.
О Price. Тип (Туре) — Money. В поле Price будет храниться цена товара. Это поле также
обязательно.
Таблица Items в режиме конструктора изображена на рис. 2.5.
Так как предполагается, что таблица Items может быть достаточно большой, для уско-
рения поиска, выборки и сортировки нужно определить индексы. Для этого выберите
в комбинированном списке Table properties пункт Secondary Indexes. Обратите внимание
на то, что в списке вторичных индексов уже присутствует индекс Category. Этот индекс
был создан автоматически на основе внешнего ключа при создании связи.
40 Borland C++ Builder 6. Разработка приложений баз данных
Сохраните таблицу в том же каталоге Data под именем Items и закройте Database
Desktop. Чтобы заполнить таблицу Items данными, откройте для нее SQL Explorer и перей-
дите на вкладку Data. При этом не забудьте, что все поля, кроме Description, — обязатель-
ны. Поэтому попытка оставить любое из этих полей пустым (кроме поля ItemNo, которое
является автоинкрементным) при переходе на другую запись вызовет ошибку. Вызовет
ошибку и ввод в поле Category значения, отсутствующего в поле CategoryNo таблицы
Category.
Таблица Items, содержащая все необходимые данные, располагается на поставляемом
с книгой диске в каталоге Data.
1;SamsungML-4SQO
'
3 Epson EFt-590QL
:
4 CanonLBP-810 GDI, 600dp,, a:',
5 Brother HL-1 240 Mono Laser А ОЗУ гмб, еось;';
6 Epson AcuLoserCIQOO !т, ОЗУ 16(5;-;
7 Epson AcuLsssrC200D
B:HPLaserJet1000w :А4 ОЗУ! Мб. GD '/
А^ озув(?г)Мб/''
10:HP Laser JetlZZG "А4. сканер 60'J dp
11 HP LaseiJet 3200 -М. сканер BCD dp
12:HP Laser JelZZQG
13 -Epson Stylus Color CJOSX ;A4. а т. 720 dpl.
HJEpsor. Stylus Color «DUX
15 lEpson Stylus Color C60 -A1. цвет. 2883x72^
• ' - H
TButton * Temp;
Temp=dynamic_cast<TButton *>(Sender);
if(Temp)
switch(Temp->Tag)
case 1:
Tablel->First(); break;
case 2 :
Tablel->Prior(); break;
case 3:
Tablel->Next(); break;
case 4:
Tablel->Last(); break;
BFirst->Enabled=!Tablel->Bof;
BPrior->Enabled=!Tablel->Bof;
BNext->Enabled=!Tablel->Eof;
Blast->Enabled=!Tablel->Eof;
события для кнопки или для другого компонента (например, по ошибке). Все эти дей
ствия нужны в связи с тем, что один и тот же обработчик события используется сразу дл:
четырех кнопок. Если динамическое приведение было успешным, проверяется свойств!
Tag кнопки. Напомню, что при создании мы задали для четырех кнопок первой группь
свойству Tag значения от 1 до 4. Определив таким образом, какая из кнопок была на
жата, выполняем соответствующий метод компонента ТТаЫе. В заключение при помощ!
свойств Во/ и £0/задается возможность доступа к кнопкам. Как только становится истин
ным свойство Во/, кнопки BFirst и BPrior будут недоступны, а при истинности свойств!
Eof недоступными становятся кнопки BNext и BLast. Как только Bofvam Eof приобретаю'
значение false, доступными становятся и соответствующие кнопки.
Вторая группа элементов управления состоит из кнопки BMoveBy и элементов редак
тирования EPlan и EReally. Чтобы переместиться по набору данных на нужное количестве
записей вперед или назад, следует указать соответствующее значение в элементе редак-
тирования EPlan и нажать кнопку BMoveBy, Количество записей, на которое реальнс
переместился указатель в наборе данных, будет отображено в элементе редактирования
EReally. Чтобы обеспечить эту возможность, в тело обработчика события OnClick кнопки
BMoveBy введите следующий код:
void fastcall TForml::BMoveByClick(TObject *Sender)
{
EReally->Text=IntToStr
(Tablel->MoveBy(StrToIntDef(EPlan->Text,0))) ;
}
В коде этого обработчика функция StrToIntDef используется для преобразования
значения EPlan->Text типа AnsiString в значение целого типа. Второй аргумент функ-
ция StrToIntDef возвращает в случае, если попытка преобразования первого закончится
неудачей. Число записей, возвращаемое методом MoveBy, снова преобразуется к типу
AnsiString и отображается в элементе EReally.
Удобно было бы после ввода числа в поле EPlan запускать процесс перемещения по
набору данных и нажатием на клавишу Enter. Чтобы получить такую возможность, введи-
те в тело обработчика события OnKeyPress элемента EPlan следующий код:
void fastcall TForml::EPlanKeyPress(TObject «Sender, char &Key)
{
if(Key == VK_RETURN) BMoveByClick(NULL);
}
Как известно, это событие происходит в случае, если пользователь нажимает одну из
символьных клавиш (т. е. клавиш, которым соответствует один из символов ASCII) в то
время, когда элемент имел фокус ввода. В данном случае, если была нажата клавиша Enter
(VK_RETURN— константа из набора виртуальных клавиш, соответствующая клавише
Enter), то вызывается обработчик нажатия клавиши BMoveBy с аргументом NULL.
Элементы редактирования ЕВо/н ££о/должны отображать, соответственно, значения
свойств Во/ и Eof набора данных TTable. Для того чтобы информация в этих элементах
управления обновлялась своевременно, можно воспользоваться событием A/terScroll ком-
понента TTable. Это событие происходит всякий раз, как только перемещается указатель
активной записи, независимо от способа, которым это было сделано (при помощи мышки,
клавиш управления курсором или вызовом одного из методов навигации). В тело обработ-
чика события AfterScroll введите следующий код:
Глава 2. Использование механизма ВОЕ 45
if(DataSet->Bof) EBof->Text="true";
else EBof->Text="false";
if(DataSet->Eof) EEof->Text="true";
else EEof->Text="false";
Tablel->0pen();
BFirst->Enabled=false;
BPrior->Enabled=false;
}
Сохраните получившийся проект на диске (у меня он называется NavMethods) и запу-
стите его на выполнение. Попробуйте поэкспериментировать с набором данных — это по-
может вам четко выяснить все нюансы применения использованных в программе свойств
и методов.
Полностью текст программы приведен в листингах 2.1 и 2.2.
Листинг 2.1. Файл заголовка приложения NavMethods
#ifndef UnitlH
#defme UnitlH
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <DB.hpp>
#include <DBCtrls.hpp>
#include <OBGrids.hpp>
#include <DBTables.hpp>
#include <ExtCtrls.hpp>
#include <Grids.hpp>
TButton *BNext;
TButton *BLast;
TPanel *Panell;
TPanel *Panel2;
TButton *BMoveBy;
TEdit *EPlan;
TEdit *EReally;
TPanel *Panel3;
TEdit *EBof;
TLabel *Labell;
TLabel *Label2;
TEdit *EEof;
void _ fastcall FormCreate(TObject *Sender) ;
void _ fastcall BFi rstClick(TObject *Sender) ;
void _ fastcall BMoveByClick(TObject *Sender);
void _ fastcall EPlanKeyPress(TObject *Sender, char &Key)
void _ fastcall TablelAf terScroll (TDataSet *DataSet);
private: // User declarations
public: // User declarations
_ fastcall TForml(TComponent* Owner);
#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForml *Forml;
_fastcall TForml : :TForml(TComponent* Owner)
: TForm(Owner)
Temp=dynamic_cast<TButton *>(Sender);
if(Temp)
switch(Temp->Tag)
/
case 1:
Tablel->First(); break;
case 2:
Tablel->Prior(); break;
case 3:
Tablel->Next(); break;
case 4:
Tablel->Last(); break;
BFirst->Enabled=!Tablel->Bof ;
BP r i or ->Enabled=! Table l->Bof;
BNex t->Enabled=! Table! ->Eof;
Bias t->Enabled=! Table!- >Eof ;
Поля (Fields)
Одно из самых важных свойств набора данных — свойство Fields. Оно имеет тип
TFields и является контейнером, в котором содержится набор объектов TField. Каждый
из объектов TField представляет одно из полей набора данных. TField— очень полезный
и мощный объект, которым вы будете довольно часто пользоваться. Объекту TField будет
посвящен один из следующих разделов, здесь же мы кратко рассмотрим свойства и мето-
ды контейнера Fields.
48 Borland C++ Builder 6. Разработка приложений баз данных
Как и всякий контейнер, объект TFields не имеет событий, но имеет несколько свойст
и методов, в основном позволяющих управлять его содержимым. Ниже приводится опре
деление свойств объекта TFields:
property int Count = {read=GetCount, nodefault};
_property TDataSet* DataSet = {read=FDataSet};
property TField* Fieldsfint Index] =
{read=GetField, write=SetField};
Первые два свойства предназначены только для чтения. При помощи свойства DataSe
можно получить доступ к набору данных, содержащему этот контейнер Fields. Однакс
вряд ли вы будете когда-нибудь пользоваться этим свойством. Свойство Count содержи:
количество объектов TField, имеющихся в контейнере. Чаще всего это свойство исполь
зуется для организации итерации по всем полям набора данных. Свойство Fields предна-
значено для индексированного доступа к отдельным полям и равносильно применении:
оператора [].
Замечание
Контейнер TFields содержит список только неагрегатных полей. Агрегатные поля,
поддерживаемые некоторыми типами наборов данных (например, TCIientDataSet),
в отличие от обычных полей содержат итоговую информацию по группе записей. Для
доступа к агрегатным полям служит свойство набора данных AggFields.
TListltem *Temp;
for(int i=0;i<Tablel->Fields->Count;i++)
Temp=ListViewl->Items->Add() ;
Temp->Caption=i ;
Temp- > Subl terns ->Add (Table l->Fi eld s->Fi elds [i] ->FieldName) ;
Temp- >SubI terns ->Add (Table l->Fi elds ->FieldByNumber (i+1) ->FieldName) ;
Таблица Hems cc
l( rr 4 0
Р ::
Item temNo ?. '' '
Iten
2 ' Category : Category
* Jridex .. _ .1 Description
4 . Price i Index
5 : Qeacriptipn : Price
, _
Фильтрация
Чтобы не отображать для пользователя всю таблицу, что может быть сопряжено
с большими неудобствами и значительными затратами ресурсов, следует прибегнуть
к фильтрации. BDE предоставляет несколько ее вариантов. В первом варианте для филь-
трации должны использоваться только индексированные поля. Второй вариант, более
общий, позволяет использовать любые поля, в том числе неиндексированные.
Для фильтрации набора данных по индексированным полям применяются следующие
методы:
void fastcall ApplyRange(void);
void fastcall CancelRange(void);
void fastcall EditRangeEnd(void);
void fastcall EditRangeStart(void);
void fastcall SetRange(const System::TVarRec * StartValues,
const int StartValues_Size, const System::TVarRec * EndValues,
const int EndValues_Size);
void fastcall SetRangeEnd(void);
void fastcall SetRangeStart(void);
Следует заметить, что все эти методы объявлены в классе ТТаЫе, а не TDataSet.
Последовательность действий, необходимых для того чтобы установить для набора
данных (таблицы) фильтр с использованием методов этой группы, такова.
Q Вначале нужно задать для компонента ТТаЫе индекс, который будет использоваться
для фильтрации. Сделать это можно при помощи свойств — или IndexFieldNames, или
IndexName. Значением свойства IndexFieldNames является строка AnsiString, в кото-
рой перечислены через точку с запятой все поля, составляющие индекс. В свойстве
IndexName, как и следует из его названия, указывается имя индекса, заданное при
проектировании таблицы. Индекс, по которому будет проводиться фильтрация, можно
задать как в режиме конструктора, так и при выполнении программы.
Q На втором шаге необходимо задать начальное и конечное значение индекса, в соот-
ветствии с которыми будут отбираться данные. Для этого предназначены методы
Глава 2. Использование механизма ВОЕ 51
(TLabel) и две кнопки. Для кнопок задайте имена BApply, BCancel и подписи Apply Rang:
и Cancel Range соответственно. Поле ввода назовите ECat и очистите его свойство Text
для метки задайте подпись Category. Разместите все элементы управления примерно так
как на рис. 2.10.
iLJ
#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"
#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
long int Cat;
_fastcall TForml: :TForml(TComponent* Owner)
: TForm(Owner)
Еще один способ фильтрации набора данных — использование свойств Filter, Filtered
и FilterOptions. Все они объявлены в классе TBDEDataSet, который является промежуточ-
ным между классом TDataSet и ТТаЫе. Способ фильтрации с применением свойств Filter
и Filtered — наиболее общий, гибкий и, что немаловажно, очень простой. Этот способ не
требует применения индексов. Вы можете задавать фильтр с использованием любого поля
или произвольного набора полей набора данных.
Вначале необходимо задать в свойстве Filter строку условия, которому должны удо-
влетворять записи набора данных, чтобы попасть в итоговую выборку. Простейший вари-
58 Borland C++ Builder 6. Разработка приложений баз данных
JLJJ
Еще более изощренный фильтр можно применить при помощи обработчика собьт
OnFilterRecord компонента ТТаЫе. Соответствующее объявление в классе TDataSet вь
глядит следующим образом:
typedef void _ fastcall ( _ closure *TFilterRecordEvent)
(TDataSet* DataSet, bool &Accept) ;
Обработчик события (если задан) вызывается для каждой записи набора DataSet пр
открытии или обновлении набора данных, если для свойства Filtered задано значение tru>
Если нужно, чтобы конкретная запись не попала в итоговый набор, следует задать да
аргумента Accept -значение false. Ниже приведен код обработчика события OnFilterRecon
в результате действия которого в итоговый набор данных будут включаться записи о ТОВЕ
pax указанного ценового диапазона. Границы диапазона задаются в полях ввода Edit
и Edit2.
void _ fastcall TForml: :TablelFilterRecord(TDataSet *DataSet,
bool &Accept)
{
Accept=
(DataSet->FieldValues["Price"J >= StrToIntDef (Edi tl->Text ,0))
&&
(DataSet->FieldValues["Price"] <= StrToIntDef (Edit2->Text, 1000)) ;
Поиск
Как и в случае фильтрации, для поиска нужного значения в наборе данных существует
несколько вариантов. Один из них основан на применении методов SetKey (или EditKey
и GotoKey и используется для поиска только по индексированным полям. Вот как объяв-
лены эти методы в классе ТТаЫе:
void _ fastcall SetKey(void) ;
void _ fastcall Edi tKey (void) ;
bool __ fastcall GotoKey (void) ;
void _ fastcall GotoNearest(void) ;
Прежде чем пользоваться этими методами, следует указать, какой из индексов табли-
цы будет применяться для поиска. Для этого можно прибегнуть к услугам или свойства
IndexFieldNames (имя одного или нескольких полей, участвующих в индексе), или свой-
ства IndexName (имя индекса). Если этого не сделать, вы получите сообщение об ошибке
наподобие следующего: Field 'Имя_Поля ' is not indexed and cannot be modified («Поле
'Имя_Поля 'не проиндексировано и не может быть изменено»). Далее нужно вызвать ме-
тод SetKey. Это приведет к тому, что набор данных перейдет в состояние dsSetKey. В этом
состоянии присвоение полю какого-либо значения будет воспринято как задание образца
для поиска и не приведет к изменению значения этого поля. Естественно, образцы поис-
ка необходимо задать для всех полей, входящих в индекс и указанных ранее при помощи
свойства IndexFieldNames или свойства IndexName.
Метод GotoKey выполняет поиск в соответствии с произведенной настройкой. Если
найдено соответствие образца значению поля (полей) одной из записей, метод возвращает
значение true и переводит на нее указатель активной записи. В противном случае указа-
тель активной записи не меняется, а метод GotoKey возвращает значение^а/^е.
Метод EditKey предназначен для тех же целей, что и метод SetKey, однако с его помо-
Глава 2. Использование механизма ВОЕ 61
щью можно поменять образцы поиска не для всех полей индекса, а только для некоторых.
Например, вы используете для поиска составной индекс, основанный на полях Фамилия, Имя,
Отчество. Вначале вам нужно вызвать метод SetKey и установить для каждого поля образец
поиска, например, для поля Фамилия значение «Иванов», для поля Имя — значение «Иван»,
а для поля Отчество — значение «Иванович». Если метод GotoKey не нашел нужную запись,
можно вызвать метод EditKey и затем изменить образец поиска, например, для поля Отчество
задав значение «Петрович». Следующий вызов метода GotoKey инициирует поиск записи, со-
держащий в соответствующих полях индекса значения «Иванов», «Иван», «Петрович».
Ниже приведен отрывок кода, описывающий поиск по полю Item таблицы Items.
Tablel->IndexName="ItemNDX";
Tablel->SetKey();
Tablel->FieldByName("Item")->AsString=ESample->Text;
Tablel->GotoKey();
Tablel->IndexName="";
В первой строке этого кода задается имя индекса, который будет использоваться для
поиска. Если вы помните, при построении таблицы Items индексу на основе поля Item
было присвоено имя ItemNDX. Далее вызывается метод SetKey, переводящий набор
данных в режим поиска, и в качестве образца поиска по полю Item выбирается текст,
введенный пользователем в элемент редактирования ESample. Метод GotoKey в случае
удачи переводит указатель активной записи на первую запись, соответствующую образцу
поиска. В последней строке кода в качестве имени индекса указывается пустая строка.
Дело в том, что после того как вы укажете для свойства IndexName имя индекса ItemNDX,
набор данных будет отсортирован по полю Item в соответствии с установками, заданными
при создании индекса ItemNDX (в данном случае по возрастанию). Если индекс для на-
бора данных не задан, то сортировка производится с использованием первичного ключа,
а если его нет — то записи будут отображаться по мере того, как они вводились в таблицу.
В нашем примере в результате выполнения последней строки приведенного выше отрыв-
ка кода набор данных вернется к первоначальному виду (т. е. записи не будут перемеша-
ны). В реальном приложении, чтобы избежать ошибочных ситуаций, в начале этой части
кода следует запомнить имя индекса во временной переменной, а в последней строке от-
рывка — восстановить предыдущее значение свойства IndexName.
Если вам точно не известно значение индексного пол», которое вы разыскиваете, то
вместо метода GotoKey можно воспользоваться методом GotoNearest. Он перенесет ука-
затель активной записи на ту запись, значение выбранного индекса которой ближе всего
к образцу поиска. Чаще всего этот метод используется для поиска строки по ее заданной
части. Метод GotoNearest не возвращает никакого значения, однако если подходящая за-
пись не найдена, активной становится последняя запись набора.
Есть еще пара методов, аналогичных GotoKey и GotoNearest, но более простых в использо-
вании. Это методы FindKey и FindNearest. Вот как они объявлены в классе ТТаЫе:
bool fastcall FindKey(const System::TVarRec * KeyValues,
const int KeyValues_Size);
void fastcall FindNearest(const System::TVarRec * KeyValues,
const int KeyValues_Size);
Первый аргумент обоих методов представляет собой массив значений полей (образ-
цов для поиска), перечисленных через запятую. Второй аргумент — последний индекс
динамического массива. Как всегда в таких случаях, вместо этих двух аргументов мож-
62 Borland C++ Builder 6. Разработка приложений баз данных
Hum
^5г :JFC-PGA • -
*SfjlS
•V.FiBdtosr;;::.! ' FBiti^fe<l :| i
:
tewnNo . jttem •: •-•*.- .- - ---:--' • "" ' ."-. "• jCetegor*}
63 Socket A GIGA-BYTE GA-7ZXE
• 63 Socket A GIGA-BYTE GA-7ZX Rev. 5X
r| - •••
6< Socket A ChaJnTBchCT-7AJA2 —'
65 Socket A GIGA-BYTE GA-7ZMMM
66 Socket A ASUS A7V133-C
HJfl Socket 370 FC PGA/PPGA Canvon 6LEBMS
68 Socket 370 FC-PGA/PPGA Canyon SLEBMS-T
• 69 Socket 370 FC-PGA/PPGA Menli ЭС899
7C Socket 370 FC-PGA/PPOA PC ParlnerT205C
71 Socket 37C FC-PGA/PPOA FoslFame 3SLAP
> 72 Socket 370 FC-PGA/'PPGA PC Partner C203T . :-?-<
#include <vcl.h>
#pragma hdrstop
3 Зап. 319
66 Borland C++ Builder 6. Разработка приложений баз данных
#include "Unitl.h"
#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
_fastcall TForml : : TForml (TComponent* Owner)
: TForm(Owner)
Изменение данных
Еще один очень важный момент, связанный с компонентом ТТаЫе,- изменение данных та-
блицы, к которой этот компонент подключен. В режиме конструктора, щелкнув правой клави-
шей мышки на значке компонента ТТаЫе, вы можете выбрать из контекстного меню пункты,
при помощи которых можно удалить или переименовать связанную таблицу. Тех же резуль-
татов в процессе выполнения программы можно добиться при помощи методов DeleteTable
нКепапгеТаЫе. Изменять данные в таблице при выполнении программы можно благодаря
методам, объявленным в классе TDataSet и его наследниках. К основным методам изменения
записей таблицы, объявленным в классе TDataSet, можно отнести следующие:
void fastcall Append(void);
void fastcall AppendRecord(const System::TVarRec * Values,
const int Values_5ize);
void fastcall Delete(void);
void fastcall Edit(void);
HIDESBASE void fastcall Insert(void);
void fastcall InsertRecord(const System::TVarRec * Values,
const int Values_Size);
В классе TBDEDataSet объявлены еще два важных метода:
virtual void fastcall Cancel(void);
virtual void fastcall Post(void);
При помощи методов первой группы можно вносить изменения в набор данных,
в частности, добавлять, вставлять, удалять или редактировать записи. Методы из второй
группы позволяют отменить сделанные изменения (метод Cancel) или окончательно их
принять (метод Post). Рассмотрим их все более подробно.
Метод Insert открывает в буфере записей набора данных новую пустую запись, и дела-
ет ее активной. Набор данных переходит в состояние dslnsert, в результате чего пользова-
тель получает возможность вводить в поля записи значения. Чтобы занести информацию,
введенную пользователем в новую запись, в набор данных (т. е. добавить эту запись
в таблицу), необходимо вызвать метод Post. Для отмены вставки записи следует вызвать
метод Cancel.
Если вы вносите изменения в набор данных после вызова метода Insert, тем или иным
образом вызывая метод Post (его можно вызывать и неявно), может произойти одно из
следующих событий.
Q Для таблиц Paradox, имеющих первичный индекс, запись вставляется в набор данных
на основании значения ее первичного ключа.
Q Если таблица Paradox не имеет первичного индекса, то запись вставляется в текущую
позицию набора данных.
Borland C++ Builder 6. Разработка приложений баз данных
G Для таблиц dBASE, FoxPro и Access запись вставляется в конец таблицы. Есл!
в данный момент какой-либо из индексов таблицы активен, то запись будет отображе
на в наборе данных в соответствии со значением ее индекса.
О Для таблиц баз данных в формате SQL реальное расположение новой записи может
быть различным (в зависимости от формата), а отображаться такая запись будет в соот
ветствии с текущим индексом.
Чтобы отменить вставку записи, следует воспользоваться методом Cancel.
Метод InsertRecord также вставляет в набор данных новую запись, заполняет ее поля
в соответствии с переданными в качестве аргумента значениями и вызывает метод Post.
Вместо открытого массива аргументов метода InsertRecord удобно использовать макро
OPENARRAYили ARRAYOFCONST. Значения, передаваемые методу InsertRecord, должны
следовать в том же порядке, в каком следуют поля набора данных.
Замечание
Метод InsertRecord нельзя использовать, если в качестве первичного ключа таблицы
применяется поле типа Autoincrement и для него установлен флажок Required Field (речь
идет о локальных базах данных). В этом случае вы получите сообщение о том, что для
поля требуется значение. В принципе, первичный ключ по определению обязателен,
поэтому требование обязательности ввода в его поля — не только излишне, но и, как
видим, даже вредно.
Методы Append и AppendRecord служат для тех же целей и имеют тот же формат, что
и методы Insert и InsertRecord соответственно, однако новая запись добавляется в конец
набора записей. Для удаления текущей записи служит метод Delete, не имеющий аргумен-
тов. После удаления текущей записи активной становится запись, следующая непосред-
ственно за ней. Если такой записи нет, активной становится предыдущая запись. В пустом
наборе данных активной записи нет.
Метод Edit переводит набор данных в состояние редактирования (dsEdif). Текущая за-
пись копируется в буфер, где вы можете редактировать содержимое входящих в нее полей.
Для сохранения в наборе данных сделанных в буфере изменений следует вызвать метод
Post. Вызовом метода Cancel отменяют все изменения, возвращая набор к первоначально-
му состоянию (режим просмотра dsBrowse). Ниже приведен отрывок кода для модифика-
ции содержимого поля Index текущей записи набора.
Tablel->Edit();
Tab!el->FieldByName("Index")->AsString=EIndex->Text;
Tablel->Post();
Каждый из методов Insert, Append и Edit прежде всего вызывает метод CheckBrowseMode
для сохранения всех изменений, которые еще не были сохранены.
С каждым из методов набора данных, предназначенных для изменения записей, свя-
зана пара событий. К этим событиям относятся: Beforelnsert, BeforeEdit, BeforeDelete,
BeforePost, BeforeCancel, Afterlnsert, AfterEdit, AfterDelete, AfterPost, AfterCancel. Первое
из каждой пары этих событий происходит в самом начале действия соответствующего
метода, а второе — в конце. Например, при вызове метода Delete сначала вызывается об-
работчик события BeforeDelete, затем запись удаляется и, в самом конце, вызывается об-
работчик события AfterDelete. Конечно же, обработчики вызываются только в том случае,
если они определены.
Глава 2. Использование механизма ВОЕ 69
Используя методы модификации набора данных, следует иметь в виду один немало-
важный фактор. Набор данных может быть немодифицируемым, т. е. предназначаться
только для чтения. В этом случае применение одного из методов модификации приве-
дет к исключительной ситуации и выдаче сообщения: Cannot modify a read-only dataset
(«Нельзя модифицировать набор данных только для чтения»). Чтобы избежать этого,
можно предварительно проверять значение свойства логического типа CanModify. Это
свойство только для чтения и доступно только при выполнении программы. Если оно име-
ет значение true, набор данных можно модифицировать, иначе набор может использоваться
только для чтения. В некоторых случаях набор данных нельзя модифицировать даже тогда,
когда свойство CanModijy имеет значение true, например, если пользователь при подключении
к базе данных SQL не имеет надлежащих прав. В этом случае следует использовать обработ-
чик соответствующей исключительной ситуации.
Набор данных может иметь статус только для чтения по нескольким причинам,
например, если набор данных открыт другим пользователем в эксклюзивном режиме
или пользователь не имеет прав доступа для модификации набора данных (в нашем
случае таблицы). Можно и самостоятельно перевести набор данных в режим только
для чтения. Для этого нужно задать для свойства Readonly значение true; свойство
CanModify при этом автоматически примет значение/а/яе. Свойством Readonly можно
пользоваться, чтобы определить, можно ли модифицировать набор данных, точно так
же как и свойством CanModify.
70 Borland C++ Builder 6. Разработка приложений баз данных
Замечание
Класс TDataSet имеет свойство логического типа Modified. Проверяя значение этог
свойства, можно определить, был ли набор данных модифицирован или нет.
Использование закладок
В наборах данных, как и в обыкновенных книгах, можно пользоваться закладками
С их помощью вы сможете быстро открыть набор записей в нужном месте. Один из вари
антов применения закладок — использование свойства Bookmark. Это свойство объявлен!
в классе TDataSet следующим образом:
property AnsiString Bookmark {read=GetBookmarkStr,
wri te=SetBookmarkStr};
Чтобы запомнить текущую позицию в наборе данных, нужно сохранить значение
свойства Bookmark набора данных в переменной типа AnsiString. Для возвращения в пози-
цию, сохраненную ранее в переменной типа AnsiString, нужно присвоить значение этой
переменной свойству Bookmark. Например, можно расположить кнопки BSaveBookmark
и BRestoreBookmark на форме, содержащей обычный набор связанных между собой ком-
понентов TDBGrid, TDataSource и TTable, подключенных к таблице Items базы данных
MyData. Для сохранения текущей позиции в наборе данных будет использоваться первая
из этих кнопок. После этого можно смело перемещаться в любую позицию набора данных,
например, при применении методов поиска или в результате прокрутки набора данных. Для
возвращения в предварительно сохраненную позицию в наборе данных будет использоваться
кнопка BRestoreBookmark. Для реализации этих возможностей следует в модуле формы опре-
делить глобальную переменную типа AnsiString (например, MyBookmark), а в обработчики
событий OnClick кнопок нужно ввести следующий код:
void fastcall TForml::BSaveBookmarkClick(TObject *Sender)
{
MyBookmark=Tablel->Bookmark;
}
void fastcall TForml::BRestoreBookmarkClick(TObject *Sender)
{
Tablel->Bookmaгk=MyBookmark;
}
Альтернативный способ использования закладок — применение методов GetBoohnark,
GotoBookmark и FreeBookmark. Они объявлены в классе TDataSet следующим образом:
v i r t u a l void * fastcall GetBookmark(void);
void fastcall GotoBookmark(void * Bookmark);
virtual void fastcall FreeBookmark(void * Bookmark);
Каждый из этих методов может быть переопределен в наследниках класса TDataSet,
чтобы воплотить свою собственную идеологию использования закладок.
Обратите внимание на то, что методы GetBookmark, GotoBookmark и FreeBookmark
оперируют с переменными типа void*. Однако гораздо нагляднее пользоваться типом
TBookmark, который объявлен в модуле db.hpp следующим образом:
typedef void "TBookmark;
Глава 2. Использование механизма BDE
Класс Описание
TADTField Непосредственный наследник класса TObjectField. В наборе
данных представляет поля типа ADT (Abstract Data Type). Поля
ADT — это поля пользовательского типа (структуры), создан-
ные на стороне сервера. Не все форматы баз данных поддержи-
вают поля ADT.
TAggregateFi eld Непосредственный наследник класса TField. Представляет по-
ля агрегатного типа в клиентских наборах данных. Поля агре-
гатного типа — это совокупность записей и столбцов набора
данных. Для каждого агрегатного поля должна быть определена
итоговая операция, вычисляющая значение по данным, входя-
щим в поле.
TArrayField Непосредственный наследник класса TObjectField. Представляет
в наборе данных поля типа массив. Поле типа массив может со-
держать набор однородных полей. В этот набор могут входить
как поля скалярного типа (целые, с плавающей точкой), так
и нескалярные (например, поля типа ADT).
TAutoIncField Непосредственный наследник класса TInteger Field, который,
в свою очередь, является непосредственным наследником класса
TNumericField. Представляет поля автоинкрементного типа.
TBCDField Непосредственный наследник класса TNumericField. В наборе дан-
ных представляет поля в двоично-десятичном коде (Binary Coded
Decimal). Значения типа BCD часто используются для представле-
ния денежных величин в финансовых расчетах, так как позволяют
производить вычисления практически без потери точности.
TBinaryField Непосредственный наследник класса TField. В наборе данных
представляет нетипизированные бинарные поля.
TBlobField Непосредственный наследник класса TField. Представляет по-
ля, содержащие ссылку на объекты BLOB (Binary Large Object).
TBooleanField Непосредственный наследник класса TField. Представляет поля
логического типа.
TBytesField Непосредственный наследник класса TBinaryField. Представляет
поля типа Byte.
TCurrencyField Непосредственный наследник класса TFloatField, который яв-
ляется наследником класса TNumericField. Представляет поля
типа Currency.
TDataSetField Непосредственный наследник класса TObjectField. Представляет
поля, содержащие ссылку на набор данных, который является
вложенным (nested) для данного набора.
TDateField Непосредственный наследник класса TDateTimeField.
Представляет поля, содержащие значения даты.
Глава 2. Использование механизма ВОЕ 77
TDateTimeField Непосредственный наследник класса TField. Родовой класс для
представления полей, содержащих значения даты и времени.
TFloatField Непосредственный наследник класса TNumericField.
Представляет поля, содержащие числа с плавающей точкой.
TFMTBCDField Непосредственный наследник класса TNumericField.
Представляет поля в двоично-десятичном коде BCD (Binary
Coded Decimal). Класс поля TFMTBCDField отличается от клас-
са поля TBCDField большей точностью и меньшей производи-
тельностью. Этот класс появился в C++ Builder 6.
TGraphicField Непосредственный наследник класса TBlobField. Представляет
поля, содержащие графические данные.
TGuidField Непосредственный наследник класса TStringField. Представляет
поля, содержащие глобальный уникальный идентификатор
(GU1D — Globally Unique Identifier).
TIDispatchField Непосредственный наследник класса TInterfaceField. Представляет
поля, содержащие указатель на интерфейс IDispatch.
TIntegerField Непосредственный наследник класса TNumericField.
Представляет поля целого типа.
TInterfaceField Непосредственный наследник класса TField. Родовой класс,
представляющий поля, в качестве значений содержащие указа-
тели на интерфейс (lUnknown).
TLargelntField Непосредственный наследник класса TNumericField.
Представляет поля типа large integer.
TMemoField Непосредственный наследник класса TBlobField. Представляет
поля MEMO.
TReferenceField Непосредственный наследник класса TObjectField. Представляет
поля, содержащие указатели или ссылки на другие объекты.
TSmalllntField Непосредственный наследник класса TIntegerField, кото-
рый, в свою очередь, унаследован от класса TNumericField.
Представляет поля типа small integer.
TSQLTimeStampField Непосредственный наследник класса TField. Представляет поля
типа Date/Time в наборах данных dbExpress. Этот класс появил-
ся в C++ Builder 6.
TStringField Непосредственный наследник класса TField. Представляет поля
текстового типа.
TTimeField Непосредственный наследник класса TDateTimeField.
Представляет поля, содержащие значения времени.
TVarBytesField Непосредственный наследник класса TBytesField, который,
в свою очередь, унаследован от класса TBinaryField. В наборе
данных представляет нетипизированные бинарные поля пере-
менной длины.
78 Borland C++ Builder 6. Разработка приложений баз данных
Класс TField задает общую функциональность всех классов полей. Этот класс вводит
свойства, методы и события, позволяющие редактировать значения полей в наборе дан-
ных, проверять допустимость вводимых в поле данных, преобразовывать значения полей
из одного типа в другой, задавать стиль отображения данных в соответствующих элемен-
тах управления, вычислять значение поля на основании значений других полей в том же
или в другом наборе данных. Большая часть свойств и методов TField переопределяется
в его классах-наследниках ради возможности манипулировать представляемыми этими
классами данными. Кроме того, каждый из классов-наследников определяет свои свойства
и методы. Например, наряду со свойствами и методами, определенными в классе TField,
в его наследнике, классе TStringField, объявлены также свойства FixedChar и Transliterate,
специфичные для значений текстового типа.
Здесь не будут рассматриваться специфические особенности каждого из классов-на-
следников TField: с этим можно разобраться и самостоятельно. Предпочтительней из-
учить свойства, методы и события, составляющие общую функциональность всех классов
полей.
Значение по умолчанию для объекта поля можно задать при помощи свойств
DefaultExpression. В качестве значения этого свойства может быть любое выраже
ние, допустимое в операторах SQL, не содержащее ссылку на значение какого-либ|
поля. Если выражение не является числовым, его нужно заключить в одинарные ка
вычки, например, 'No Item' или '15:45'. Если для компонента поля задано свойств*
DefaultExpression, то оно переопределяет значение по умолчанию, заданное для пол;
набора данных, так как поля заполняются значениями в приложении до того как лю
бые изменения отсылаются серверу.
Класс TField имеет еще одну группу свойств, по своему назначению напоминаю
щих свойства набора данных (таблицы). Это свойства, связанные с ограничениями ш
вводимые в поле значения. К ним можно отнести CustomConstraint, ImportedConstraint
ConstraintErmrMessage и HasConstraint. В свойстве CustomConstraint можно задать стро-
ку условия, которое будет налагаться на вводимые в поле значения в дополнение к ограни-
чениям, заданным в самом наборе данных. Если введенное значение не будет удовлетво-
рять условию, указанному в свойстве CustomConstraint, то будет отображено сообщение
об ошибке, заданное в свойстве ConstraintErmrMessage.
Свойство ImportedConstraint может содержать строку условия на вводимые в поле зна-
чения, заданного для поля в самом наборе данных. Кроме того, свойство ImportedConstraint
может быть пустым. Других вариантов быть не должно. Если свойство ImportedConstraint
пусто, ограничения для поля, заданные в наборе данных, проверяются на стороне сер-
вера, и при их нарушении именно сервер возбуждает исключительную ситуацию. Если
ограничения, заданные в наборе данных, продублированы в свойстве ImportedConstraint,
то они проверяются в приложении, и именно приложение возбуждает исключение при их
нарушении. В этом случае необходимо задать в свойстве ConstraintErrorMessage строку
сообщения, которая будет отображена для пользователя.
Свойство HasConstraint имеет булевый тип и предназначено только для чтения. Если
это свойство имеет значение true, то для поля задано одно из возможных ограничений.
Другими словами, для поля задано одно из свойств: CustomConstraint, ImportedConstraint
или DefaultExpression.
Если свойство FieldKind объекта-поля имеет значение fkData, то свойство FieldName
этого объекта содержит имя поля набора данных, которое является для него источником
данных.
И в заключение короткого обзора свойств класса TField хотелось бы упомянуть о еще
трех. Свойство Islndex содержит значение true, если поле входит в текущий (активный)
индекс набора данных. Свойство IsNull содержит значение true, если в поле не было вве-
дено никакого значения. Свойство EditMask имеет тип TEditMask (синоним AnsiString)
и задает ограничения на вводимые в поле символы. За более подробной информацией
о допустимых масках ввода следует обратиться к описанию типа TEditMask в справочной
системе C++ Builder.
Класс TField имеет не очень много методов. Практически все они предназначены для
внутреннего использования BDE. Некоторый интерес представляют, пожалуй, только
методы Assign и Clear. Метод Assign копирует свойство Value из другого поля или при-
сваивает полю другой объект. Оба поля или поле и присваиваемый объект должны иметь
один тип. Например, при вызове метода TablelDateO->Assign(TablelDatel) полю DateO та-
блицы Table 1 будет присвоено значение поля Datel той же таблицы. Предполагается, что
оба поля имеют тип Date. В результате вызова метода TablelDescription->Assign(Memol
->Lines) полю Description таблицы Tablel, которое имеет тип Memo, будет присвоено со-
Глава 2. Использование механизма BDE
держимое элемента управления Memol, имеющего тот же тип. Метод Clear очищает поле,
присваивая его свойству Value значение NULL.
У класса TField всего четыре события, но в некоторых случаях они весьма полезны. Это
OnChange, OnGetText, OnSetText и OnValidate. Событие OnChcmge происходит сразу после
того, как данные поля помещаются в буфер текущей записи. Отредактированные данные
поля помещаются в буфер текущей записи при переходе набора данных из режима редакти-
рования в режим просмотра, в том числе при переходе на другое поле текущей записи или
на другую запись. Обработчик события OnChange вызывается сразу после вызова обработ-
чика события OnValidate, но только если данные поля были успешно приняты и помещены
в буфер текущей записи. В обработчике события OnChange можно запрограммировать от-
клик на введенные в поле новые данные, например, обновление не связанных с набором
данных элементов управления. В качестве аргумента в обработчик события OnChange пере-
дается указатель на объект поля, для которого было вызвано событие.
Событие OnValidate происходит непосредственно перед тем как данные поля будут по-
мещены в буфер текущей записи. Обработчику события в качестве аргумента передается
указатель на объект поля, для которого это событие и было вызвано. Событие OnValidate
можно использовать для дополнительной проверки введенного в поле значения на уровне
приложения. Если значение не удовлетворяет критериям приложения, необходимо возбу-
дить исключение (при помощи вызова throw). В этом случае значение не будет сохранено
в базе данных и событие OnChange не произойдет.
Обработчики событий OnGetText и OnSetText объявлены следующим образом:
typedef void fastcall ( closure *TFieldGetTextEvent)(TField* Sender,
AnsiString &Text, bool DisplayText);
typedef void fastcall ( closure *TFieldSetTextEvent)(TField* Sender,
const A n s i S t r i n g T e x t ) ;
Событие OnGetText происходит всякий раз при обращении к свойствам DisplayText или
Text объекта TField. Например, это событие происходит для каждого элемента управления
данными, в котором должно отображаться значение соответствующего поля. При заполне-
нии элемента управления TDBGrid данными событие OnGetText происходит столько раз,
сколько записей содержит сетка. Помимо указателя Sender на объект поля, для которого
вызывается обработчик события, ему также передается ссылка на пустую строку Text типа
AnsiString.
После того как код обработчика события выполнит все запрограммированные дей-
ствия, значение аргумента Text будет использовано для отображения в соответствующем
элементе управления данными. Например, если для отображения информации из табли-
цы используется элемент управления TDBGrid, и для одного из компонентов поля вы
создали пустой обработчик события OnGetText, то соответствующий столбец сетки при
выполнении приложения будет пуст. Другими словами, если вы создали обработчик со-
бытия OnGetText, то сами должны позаботиться о правильном заполнении аргумента Text.
Если обработчика события нет, вместо Text используется свойство AsString объекта поля.
Отсутствие обработчика события OnGetText равносильно наличию следующего обработ-,
чика:
void fastcall TForml::TablelItemGetText(TField *Sender,
AnsiString &Text, bool DisplayText)
{
Text=Sender->AsString;
82 Borland C++ Builder б. Разработка приложений баз данных
tern
Category
temMo jfcKn !Co«fX>.*l
328 HUBCompexPS <ndex
329 HUBHPPraCun Price
330 ниВСотрвкО;
331 HUBEP-ai6DX-
332 Плота Mel EthE
333 Ппота Intel EthE
33-1 Плата Intel Ethe Express Pro/1 Ods Ptl Server Adapt
335 Плота Intel Pro/1DOS Dual Port Server Adapter
336 Плата imel Pro/ COOT
j 337 HUB Intel Stainda one Managed Switch
<JJ :•••' ±f
этого типа могут использоваться для замены полей набора данных, например, для и
менения типа данных поля.
Основную часть рабочей области окна редактора полей занимает список постоянш
полей. При выборе поля в этом списке инспектор объектов будет отображать все его сво
ства. Вы можете изменить значения всех свойств поля, включая свойства Name и Fiel
Name. Свойство FieldName содержит имя поля набора данных, которому соответству
объект поля, а свойство Name — имя самого объекта. По умолчанию в качестве HMCI
объекта поля используется составное имя, содержащее имя поля и имя компонента н
бора данных. Например, Tablelhem. Вы можете задать любое подходящее имя и зате
использовать его в программе для доступа к свойствам поля. Это гораздо удобнее, че
громоздкие конструкции вида Table!->FieldByName(«Item»).
Обратите внимание на то, что изменить тип значений поля при помощи инспектор
объектов вам не удастся. При создании объекта поля редактор полей получает информ;
цию о типе данных поля (или непосредственно из набора данных, или от пользователя пр
создании нового поля) и на основании этой информации создает объект подходящего та
па. Например, для поля Item таблицы Items редактор полей создаст постоянное поле тип
TStringField, а для поля Price той же таблицы — объект типа TCurrencyField. Так что есл]
вы уже создали постоянное поле, то единственный способ изменить его тип — удалить и
списка и создать заново, но уже с другим типом данных. Удалить постоянное поле можно
выделив его в списке редактора полей и нажав клавишу Delete.
Группа из пяти пунктов контекстного меню редактора полей предназначена для рабо
ты с наборами атрибутов (Attribute Sets). О наборах атрибутов полей уже рассказывалос!
в предыдущей главе, при описании работы с утилитой SQL Explorer. Наборы атрибутов
созданные при помощи SQL Explorer, можно использовать в редакторе полей для быстрой
настройки свойств полей. Для этого нужно выделить наименование поля в списке и выбр-
ать из контекстного меню пункт Associate attributes. На экране будет отображено диалого-
вое окно, содержащее список всех доступных на данный момент наборов атрибутов. Если
на вкладке Dictionary утилиты SQL Explorer вы раскроете узел Attribute Sets, вы увидите
точно такой же список.
Если вы выберете в диалоговом окне подходящий набор атрибутов и нажмете кнопку
ОК, объект поля будет связан с этим набором. Это значит, что при помощи нескольких
щелчков мышки вы сможете задать значения для многих свойств поля, включая ограни-
чения, сообщения об ошибке и т.д. Связь поля с набором атрибутов означает, что все из-
менения, сделанные в наборе атрибутов, будут отражаться и на свойствах поля. Чтобы от-
делить поле от набора атрибутов, следует выбрать пункт Unassociate attributes. Значения
свойств поля при этом не изменятся, но изменения в наборе атрибутов больше влиять на
них не будут.
Если поле ассоциировано с каким-либо набором атрибутов, можно воспользоваться
еще тремя пунктами контекстного меню редактора полей: Save attributes, Save attributes
as и Retrieve attributes. При помощи пункта Save attributes можно сохранить изменения,
сделанные в свойствах поля, в наборе атрибутов, с которым это поле связано. С помощью
пункта Save attributes as можно сохранить набор атрибутов в словаре под другим именем.
Пункт Retrieve attributes предназначен для обновления свойств поля на основе связанного
с ним набора атрибутов.
В заключение обзора возможностей редактора полей следует упомянуть о панели на-
вигации, расположенной в верхней части рабочей области его окна. С помощью этой па-
нели можно передвигаться по записям набора данных прямо в режиме конструктора.
Глава 2. Использование механизма ВОЕ
Замечание
Поля, перечисленные в списке редактора полей, можно перетаскивать с помощью
мышки на форму. При этом на форму будут добавляться элементы управления,
соответствующие перетаскиваемому полю. Например, для полей простого типа
(числового или текстового) будет создан объект типа TDBEdit, а при перетаскивании на
форму поля, содержащего рисунок, будет создан объект типа TDBImage. Кроме того, для
каждого из созданных элементов управления данными на форму будет добавлена метка
(объект типа Tlabel), в качестве подписи содержащая имя поля. Можно пометить группу
полей, которые предполагается разместить на форме, и перетащить их все на форму за
один раз. Такой нехитрый прием позволяет моментально воспроизвести необходимую
структуру формы.
зу данных с псевдонимом MyData, пока содержащую две таблицы, еще одну таблицу. Он
понадобится нам и в дальнейшем. Откройте Database Desktop и создайте новую таблиц
типа Paradox 7. Добавьте в ее макет определения полей, как указано в таблице 2.2.
Таблица 2.2. Определение полей таблицы Employees
Имя поля (Field Name) Тип (Туре) Размер (Size) Ключ (Key)
EmployeeNo (Счетчик) Autoincrement (+) *
LName (Фамилия) Alpha (A) 50
FName (Имя) Alpha (A) 20
MName (Отчество) Alpha (A) 25
HireDate (Дата приема) Date (D)
FireDate (Дата увольнения) Date (D)
Description (Характеристика) Memo (M) 240
Photo (Фотография) Graphic (G)
Birthday (Дата рождения) Date (D)
Для полей LName и HireDate следует установить флажок Required field — они долж-
ны быть обязательно заполнены. Далее в поле со списком Table properties выберите
позицию Secondary Indexes и нажмите кнопку Define. Создайте вторичный индекс на
основе полей LName, FName и MName (именно в такой последовательности) и дайте
ему имя NameNDX. Сохраните таблицу в том же каталоге, что и остальные таблицы,
под именем Employees. Таким образом, мы получили таблицу для хранения инфор-
мации о сотрудниках. Если вы заметили, в таблице Employees нет данных о зарплате
сотрудников и о занимаемых ими должностях. Для этого лучше создать отдельную
таблицу, что-то вроде штатного расписания. В ней будут храниться те данные о сотр-
уднике, которые со временем могут меняться. По коду сотрудника (EmployeeNo) обе
таблицы должны быть связаны.
Теперь перейдем к примеру применения вычисляемых полей. Как вы уже, навер-
ное, догадались, в этом примере будет использоваться созданная нами новая таблица
Employees. Пока она пуста, поэтому вы можете заполнить ее данными по своему усмо-
трению или скопировать с диска, поставляемого вместе с книгой. Далее» создайте новое
приложение и разместите на пустой форме компоненты TDBGrid, TTable и TDataSource,
оставив их имена по умолчанию. Подключите, как всегда, DBGridl к DataSourcel, a Data-
Sourcel к Tablel. Объект Tablel подключите к таблице Employees базы данных MyData.
Задача состоит в следующем: необходимо создать список сотрудников, еще не уво-
ленных с предприятия, содержащий фамилии и инициалы, возраст и стаж работы. Список
должен быть отсортирован по возрастанию по фамилиям, именам и отчествам.
Нас интересуют еще не уволенные сотрудники, т.е. те, для которых поле FireDate (дата
увольнения) не заполнено. Поэтому прежде всего необходимо отфильтровать набор дан-
ных так, чтобы в итоговую выборку попали только те записи о клиентах, значение поля
FireDate которых равно NULL. Для этого в свойство Filter набора данных введите следую-
щую строку: FireDate=NULL. Чтобы итоговая выборка была отсортирована по фамилиям
и инициалам по возрастанию, укажите в качестве текущего индекса созданный ранее при
конструировании таблицы Employees индекс NameNDX (просто выберите его имя из спи-
ска свойства IndexName).
Глава 2. Использование механизма ВОЕ 87
AnsiString FI.SI;
unsigned short yearl, yearO, month, day;
if (!TablelFName->IsNull)
FI=" " + (TablelFName->Value). SubString(0, 1) + "." ;
if((!Table!MName->IsNuU) && ( ! (FI . IsEmpty ()) ))
SI=" " + (TablelMName->Value) . SubString(0, 1) + "." ;
TablelName->Value=TablelLName->Value + FI + SI;
Date() .DecodeDate(&yearl, &month, &day) ;
(TablelBirthday->Value) .DecodeDate(&year0, &month, &day) ;
TablelAge->Value=yearl-year0;
(TablelHireDate->Value) .DecodeDate(&year0, &month, &day) ;
TablelStanding->Value=yearl-year0;
Borland C++ Builder 6. Разработка приложений баз данных
Бобров С Н
ЦрейбвнФ.А
: Field ptoperties
: Цата: |CrtegoiyJome Component |ToblelC3tegoryName2 ;.,;
Чуть ниже комбинированного списка Available Indexes расположены еще два элемента
управления. Список Detail Fields содержит перечень полей, входящих в выбранный ин-
декс. В нашем примере здесь будет только одно поле — Category. В списке Master Fields
перечислены все поля главной таблицы. Чтобы создать нужную нам связь, выберите
в списке Detail Fields поле Category, а в списке Master Fields — поле CategoryNo, после
чего нажмите кнопку Add. В результате этих действий созданная между полями связь
будет добавлена в список Joined Fields (см. рис. 2.18). Связь между таблицами будет окон-
Глава 2. Использование механизма ВОЕ
чательно оформлена после нажатия кнопки ОК. При этом свойство MasterFields получит
значение CategoryNo.
И последний штрих, впрочем, не имеющий отношения к заданию связей. Создайте об-
работчик события OnCreate формы и введите в него следующий оператор:
void _fastcall TForml::FormCreate(TObject *Sender)
{
DBLookupComboBoxl->KeyValue=Tablel->
Field ByName("CategoryNo")->Value;
}
При помощи этого оператора устанавливается начальное значение комбинирован-
ного списка подстановки DBLookupComboBoxl. Если этого не сделать, то после за-
грузки приложения этот элемент управления будет пуст. В свойстве KeyValne элемента
DBLookupComboBoxl содержится значение ключевого поля, в нашем случае — поля
CategoryNo. Присвоив этому свойству значение поля CategoryNo первой записи открытого
набора данных Tablel, мы приведем в соответствие оба элемента визуализации данных.
Откройте оба набора данных, присвоив их свойствам Active значение true, и запу-
стите приложение на выполнение. Вы должны получить результат наподобие изобра-
женного на рис. 2.19. Теперь можно выбирать из комбинированного списка категорию,
а элемент TDBGrid будет автоматически обновляться, отображая список товаров вы-
бранной категории.
обновления, который применяется для данной записи. Аргументы этого типа могут при-
нимать одно из следующих значений: ukModify (запись была модифицирована), uklnsert
(запись была вставлена) и ukDelete (запись была удалена). Обычно этим аргументом поль-
зуются в информационных целях.
Третий аргумент — - UpdateAction — имеет особое значение для обработчика со-
бытия OnUpdateRecord. Если вы создали обработчик, то обязаны задать для аргумента
UpdateAction подходящее значение типа TUpdateAction. К этому типу относятся следую-
щие значения: uaAbort (текущее действие прекращается без выдачи сообщения об ошиб-
ке), uaApplied (текущее действие продолжается, т. е. запись сохраняется в базе данных),
uaFail (текущее действие прекращается, и на экране отображается сообщение об ошибке),
uaSkip (сохранение записи прекращается, но информация о ней остается в кэше). К этому
типу относится также значение uaRetry (повтор попытки сохранить запись), но пользо-
ваться им можно только в обработчике события OnUpdateRecord. Если вы создали пустой
обработчик события, то аргумент UpdateAction автоматически получит значение uaFail.
Сообщение об ошибке при этом будет информировать, скорее всего, о том, что сохране-
ние невозможно, так как набор данных предназначен только для чтения. Для того чтобы
запись была действительно сохранена в наборе данных, в теле обработчика события
OnUpdateRecord нужно присвоить аргументу UpdateAction значение uaApplied.
Если обновление какой-либо записи привело к генерации исключения, то для этой за-
писи вызывается событие OnUpdateError. Например, исключение можно сгенерировать
при помощи оператора throw, если проверка допустимости введенных данных закончи-
лась неудачей. Обработчик этого события имеет четыре аргумента: три аргумента по типу
и смыслу совпадают с аргументами обработчика события OnUpdateRecord, а четвертый
(точнее, второй по счету) имеет тип указателя на объект EDatabaseError. Этот объект со-
держит информацию о происшедшей ошибке. При помощи события OnUpdateError вы
можете по-своему обработать возникшую исключительную ситуацию.
Чтобы проиллюстрировать принципы применения основных свойств, методов и событий,
связанных с режимом кэширования изменений, рассмотрим небольшой пример. Итак, создай-
те новое приложение и на пустой форме разместите компоненты TDBGrid, TTable и TDataS-
ource, связав их, как обьино, в одну информационную цепочку и оставив их имена по умол-
чанию. Объект Tablel подключите к таблице Items базы данных MyData. Откройте набор
данных и переведите его в режим кэширования изменений, задав для свойства CachedUpdates
значение true. В верхней части формы разместите следующие элементы управления.
Q Флажок (TCheckBox) и метку (TLabef). Для флажка задайте имя CBUpdatesPending
и пустую строку в качестве свойства Caption. Для метки задайте подпись:
«Незавершенные обновления». Этот флажок должен будет отображать состояние кэша.
Если флажок установлен, значит, в кэше имеются несохраненные изменения.
Q В верхней правой части формы разместите панель (ТРапеГ) и две кнопки на ней. Для
одной кнопки задайте имя ButtonApply и подпись Обновить, а для второй — имя
ButtonCancel и подпись Отменить. При помощи этих кнопок можно будет или сохра-
нить кэшированные изменения, или отменить их.
Q В центре верхней части формы разместите еще одну панель и на ней четыре мет-
ки (TLabel). Для меток задайте следующие имена и подписи, соответственно: LIns
и «Вставленных записей: 0», LDel и «Удаленных записей: 0», LMod и «Модифициро-
ванных записей: 0», LErr и «Ошибок при обновлении: 0». Эти метки будут отображать
количество сохраненных записей в зависимости от типа их обновления, а если при
обновлении произошли ошибки, то и количество ошибок.
96 Borland C++ Builder 6. Разработка приложений баз данных
Не завершенные
iLd ±Г
TUpdateAction &UpdateAction) ;
void _ fastcall FormCloseQuery(TObject *Sender, bool &CanClose)
private: // User declarations
void _ fastcall UpdateLabel(void) ;
public: // User declarations
_ fastcall TForml (TComponent* Owner);
#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"
#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
int NDel, NIns, NMod, N E r r ;
_fastcall TForml::TForml(TComponent* Owner)
: TForm(Owner)
NDel=0; NMod=0;
NIns=0; NErr=0;
UpdateAction=uaApplied;
switch(UpdateKind)
case ukModify:
{ NMod++; break; }
case uklnsert:
{ NIns++; break;}
case ukDelete:
{ NDel++; break; }
Замечание
В этом разделе рассмотрены не все возможности режима кэширования изменений
присущие компоненту ТТаЫе. Например, при помощи метода RevertRecord можнс
отменить изменение только последней модифицированной записи, при помощ!
свойства UpdateObject - указать объект типа TUpdateSQL, который будет обновляв
соответствующий набор данных, и т. д. Вы можете сами ознакомиться со всем!
возможностями режима кэширования изменений, обратившись к разделу Using the BD
to cache updates справочной системы C++ Builder.
delete(Tablel);
Как только объект ТТаЫе будет создан, нужно задать параметры подключения и тиг
таблицы:
Tablel->DatabaseName="MyDatal";
Tablel->TableName="Itemsl.db";
Tablel->TableType=ttParadox;
На следующем этапе нужно создать определения полей и индексов. Для этого ис-
Глава 2. Использование механизма ВОЕ 101
NewFieldDef->DataType=ftString;
NewFieldDef->Size=100;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Index";
NewFieldDef->DataType=ftString;
NewFieldDef->Size=5;
NewFieldDef->Requi red=true;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Price";
NewFieldDef->DataType=ftCuгrency;
NewFi eldDef->Required=true;
TIndexDef* NewIndexDef=Tablel->IndexDefs->Add!ndexDef();
NewIndexDef->Name="<Primary>";
NewIndexDef->Fields="ItemNo";
NewIndexDef->Options=TIndexOptions() ixllnique;
NewIndexDef=Tablel->IndexDefs->AddIndexDef();
NewIndexDef->Name="ItemNDX";
NewIndexDef->Fields="Item";
NewIndexDef->Options=TIndexOptions() ixUnique;
Tablel->CreateTable();
delete(Tablel);
В следующей главе будет продолжено рассмотрение возможностей механизма BDE
В частности, будут описаны объекты TSession, TDatabase, TQuery, основные компоненть
визуализации и управления данными (Data Controls), пользовательские наборы данныз
и другие вопросы.
Резюме
В этой главе мы рассмотрели основы использования механизма BDE для созда
ния приложений баз данных. Внимание прежде всего было уделено наборам данных
в частности, компоненту ТТаЫе. Были приведены различные примеры манипуляций
с наборами данных: открытие/закрытие набора данных, методы навигации, фильтра-
ции, поиска по заданному образцу, использование закладок. Кроме того, были описань
тесно связанные с компонентом ТТаЫе объекты TDataSource и TField. Объяснялось
как при помощи свойств, методов и событий объекта TDataSource можно определит!
состояние набора данных — находится ли он в режиме вставки записи, редактирова-
ния, просмотра и т.д.
Набор данных может содержать коллекцию динамических (Dynamic) или постоянных
(Persistent) полей. В главе была объяснена разница между этими коллекциями и рассказа-
но, как при помощи редактора полей можно создать набор постоянных полей. Описаны
основные свойства, методы и события объектов полей и приведены примеры их исполь-
зования. Объяснено, как можно создать и использовать вычисляемые (Calculated) поля
и поля подстановки (Lookup Fields).
Заключительная часть главы посвящена созданию связей между таблицами типа
главная — подчиненная (Master/Detail), использованию режима кэширования изменений,
а также созданию таблиц «налету», т.е. при помощи программного кода.
Использование механизма BDE
(продолжение)
Модули данных
•ф Компоненты TDataba.se и TSession
ф Элементы визуализации и управления данными
•ф Введение в локальный SQL
ф Компонент TQuery
Ф Компоненты TUpdateSQL и TBatchMove
Ф Клиентские наборы данных
ф Резюме
В
предыдущей главе рассматривались основы применения Borland Database Engine
(BDE) при разработке приложений баз данных. Внимание уделялось прежде всего ра-
боте с объектом ТТаЫе и главным особенностям использования наборов данных. Были
также кратко рассмотрены компоненты TDataSmirce и TField. В настоящей главе будет про-
должено изучение основных принципов построения приложений баз данных на основе ме-
ханизма BDE. В частности, будет рассматриваться другой наследник класса TDataSet — ком-
понент TQuery. Компонент TQuery имеет большое значение, так как именно он применяется
в большинстве случаев — в силу своей гибкости и удобства. Кроме исследования возможно-
стей компонента TQuery, в предлагаемой вашему вниманию главе будут кратко описаны осно-
вы языка локальный SQL, а также компоненты TSession, TDatabase, TUpdateSQL, TBatchMove,
модули данных (TDataModule) и клиентские наборы данных (TBDECHentDataSet).
Начнем, пожалуй, с модулей данных.
Модули данных
Модуль данных (компонент типа TDataModule) представляет собой не что иное, как
специального вида форму. «Специальность» модуля данных заключается в том, что его
можно видеть только при конструировании приложения, а в процессе выполнения модуль
данных на экране не отображается. В то время как форма служит контейнером для любых
компонентов, модуль данных может содержать только невизуальные компоненты (что,
впрочем, вполне естественно).
Обычно в модуль данных помещают компоненты для связи с базами данных. К этим
компонентам относятся уже рассматривавшиеся в предыдущей главе ТТаЫе, TDataSource,
а также и другие невизуальные компоненты — TQuery, TDatabase, TSession, TStoredProc
и т.д. Все эти невизуальные компоненты располагаются на вкладке Data Access, а также
104 Borland C++ Builder 6. Разработка приложений баз данных
lUnliltedl 3 J ii
li J
:
\Umt2 '-РРЛ 1 П|й
1 Modified 'Insert h ADiagretri./
Рис. З.2. Схема данных (Diagram), построенная для модуля данных приложения, де-
монстрирующего связь типа Master/Detail
Глава 3. Использование механизма BDE (продолжение) 105
Для того чтобы объекты и методы модуля данных были доступны в форме, во-
площающей пользовательский интерфейс, нужно в эту форму добавить заголовок
модуля данных. Проще всего это сделать при помощи команды Include Unit Hdr
меню File. Тогда обращаться к объектам и методам модуля данных из формы можно
при помощи конструкций вида ИмяМодуля->ИмяОбъекта_или_Метода, например,
DModl->Tablel. После этого можно размещать в модуле данных и настраивать соот-
ветствующим образом необходимые невизуальные компоненты. Если предполагается,
что модуль данных будет содержать большое количество объектов, представляющих
собой отдельные взаимосвязанные цепочки, нужно по мере создания таких цепочек
на вкладке Diagram редактора кода создавать соответствующую схему данных. На
рис. 3.2 изображена схема, описывающая связь типа Master/Detail между таблицами
Category и Items.
В отличие от формы, модуль данных имеет всего два события: OnCreate и OnDe-
stroy. Оба события имеют стандартный тип TNotifyEvent. Смысл применения этих
событий очевиден. Событие OnCreate используется для начальной настройки соеди-
нения, открытия наборов данных, включения фильтрации и тому подобных действий.
OnDestroy используется для противоположных целей: закрытия наборов данных, от-
ключения от базы данных и т.д.
В дальнейшем практически во всех более-менее сложных примерах приложений будут
использоваться модули данных.
'
j-jMyOOB
Eammeler overrides
SERVER NAME'd\Temp\photodatabase gdb
USER NAME-SYSDBA
P*SSWORD=rrmsterkey
Г Login prampt
• 17 beep inactive connection
Рис. З.4. Редактор компонента TDatabase содержит настройки для подключения к базе
данных photodatabase.gdb типа INTRBASE
108 Borland C++ Builder 6. Разработка приложений баз данных
TDatabase. Оба свойства предназначены только для чтения. Свойство DataSetCount содер-
жит количество активных наборов данных, a DataSets представляет собой индексирован-
ный массив активных наборов данных (объектов типа TDBDataSei). Ниже приведен не-
большой отрывок кода, иллюстрирующий применение свойств DataSetCount и DataSets:
Memol->Clear();
for (int i = 0; i < Databasel->DataSetCount; i++)
{
TTabTe* MyTable;
MyTable=dynamic_cast<TTable *>(Databasel->DataSets[i]);
if(MyTable) Memol->Lines->Add(MyTable->TableName);
}
В этом отрывке кода предполагается, что в приложении задействован объект ТМето
(Memo!), объект TDatabase (Database!) и несколько подключенных к нему активных объ-
ектов ТТаЫе. Сеанс связи с базой данных предполагается открытым. Отрывок начинается
с оператора, очищающего содержимое элемента Memol. Затем осуществляется перебор
всех открытых наборов данных, подключенных к объекту Database!. В теле цикла каж-
дый из активных наборов данных приводится к типу ТТаЫе* (т. е. проверяется, является
ли набор данных таблицей). В случае успеха, к набору строк элемента Memol добавляется
наименование таблицы, к которой подключен набор данных.
Свойство только для чтения Locale позволяет определить языковой драйвер, используемый
для объекта TDatabase. Следующие два свойства также предназначены только для чтения.
При помощи свойства логического типа IsSQLBased можно определить, подключен ли объект
TDatabase к базе данных при помощи драйверов ODBC или SQL Links. Свойство InTransaction
также имеет логический тип. Значение true этого свойства означает, что в данный момент
в контексте объекта базы данных выполняется транзакция. Это свойство необходимо обяза-
тельно проверить, перед тем как запустить очередную транзакцию.
Есть еще одно свойство, которое может пригодиться при разработке собственных
компонентов с использованием объекта TDatabase или если вы применяете в своих при-
ложениях модуль данных, содержащий объект TDatabase и предварительно сохраненный
в депозитарии объектов. Это свойство логического типа HandleShared. Чтобы избежать
конфликта имен объектов TDatabase во всех упомянутых выше случаях, следует задать
для этого свойства значение true.
Транзакции и ВОЕ
Транзакция {Transaction) — это целостный набор действий по модификации данных
одной или нескольких таблиц базы данных (или нескольких БД). Под целостным подраз-
умевается такой набор действий, который или в целом успешно завершается, или целиком
отменяется. Другими словами, если при выполнении действий, входящих в состав тран-
закции, произошла ошибка, то набор таблиц, которые должны были подвергнуться воз-
действию, остается в том состоянии, в котором они были до начала транзакции. Таблицы
подвергаются модификации только в том случае, если все действия, входящие в транза-
кцию, завершаются успешно. Стандартный и понятный пример использования транзак-
ций — перевод денег с одного счета на другой. Если при переводе денег произошел какой-
нибудь сбой, то может случиться, что деньги будут сняты с одного счета, а на другой счет
переведены не будут (исчезновение денег). В результате применения транзакций деньги
или будут успешно переведены на другой счет (удачная транзакция), или останутся на
110 Borland C++ Builder 6. Разработка приложений баз данных
первом счете (неудачная транзакция). В любом случае информация базы данных останет-
ся целостной (непротиворечивой).
Транзакции должны удовлетворять четырем основным свойствам. Эти свойства на-
зываются ЛС/£>-свойствами (на жаргоне— кислотными), по первым буквам понятий
характеризующих эти свойства. Ниже приведено определение для этих свойств.
Q Целостность (Atomicity). Или все модификации таблиц в рамках отдельной транзак-
ции завершаются, или база данных остается в том состоянии, в котором она находи-
лась до транзакции.
Q Устойчивость (Consistency). Все модификации таблиц базы данных корректны и ост-
авляют базу данных в устойчивом состоянии.
Q Изоляция (Isolation). Результаты выполнения любой незаконченной (неподтвержден-
ной) транзакции не видны другим транзакциям.
LI Прочность (Durability). Подтверждение обновления записей не должно зависеть от каких-
либо сбоев, в том числе сбоев оборудования, сбоев при передаче данных в сети и т. д.
Механизм BDE обеспечивает автоматическое управление транзакциями на уровне
отдельной записи. Это означает, что обновление модифицированной записи таблицы про-
изойдет только в случае успеха, иначе запись останется неизменной. Это предотвращает
изменение значений одних полей записи, если другие поля этой же записи в силу тех или
иных причин модифицированы не будут. Если такой уровень использования транзакций
вас не устраивает (например, вам обязательно нужно одновременно обновить несколько
записей, принадлежащих нескольким таблицам), вы должны воспользоваться возможно-
стями управления явными транзакциями, имеющимися у компонента TDatabase.
Для управления явными транзакциями (далее— просто транзакциями) компонент
TDatabase имеет три метода и одно свойство. Последовательность действий, входящих в одну
транзакцию, начинается с вызова метода StartTransaction. Заканчивается транзакция или вы-
зовом метода Commit для подтверждения всех изменений при транзакции, или вызовом мето-
да Rollback— для отмены произведенных транзакцией действий в случае неудачи. Обычно
механизм управления транзакциями применяется совместно с блоком try...catch. Происходит
это следующим образом. Вначале вызывается метод StartTransaction объекта TDatabase.
Далее в блоке оператора try предпринимается попытка модифицировать информацию в базе
данных. В случае успеха вызывается метод Commit для подтверждения сделанных изменений.
Если что-то пошло не так, в блоке оператора catch вызывается метод Rollback для отмены всех
сделанных изменений. Ниже приведен отрывок кода, в котором транзакция используется для
сохранения сделанных изменений в режиме кэширования изменений (cached updates):
Databasel->StartTransaction() ;
try
{
Tablel->ApplyUpdates() ;
Databasel->Commit() ;
}
catch (...)
{
Databasel->Rollback() ;
throw;
}
Tablel->Commi tUpdatesQ ;
Глава 3. Использование механизма ВОЕ (продолжение)
Обратите внимание на то, что после вызова метода Rollback при помощи метода th-
row возбуждается исключение. Это нужно для того, чтобы предотвратить вызов метода
CommitUpdates, очищающего буфер кэшированных изменений.
На выполнение транзакций влияет значение свойства Tramlsolation объекта TDatabase.
Это свойство может принимать одно из трех значений типа TTranslsolation. Каждое из
этих значений определяет уровень изоляции текущей транзакции:
О tiDirtyRead— наименьший уровень изоляции. Разрешается чтение неподтвержденных
изменений, сделанных текущей транзакцией.
Q tiReadCommitted — • значение по умолчанию для баз данных в формате SQL.
Разрешается чтение только подтвержденных (committed) изменений.
Q tiRepeatableRead'— наиболее изолированный уровень выполнения транзакций.
Транзакции доступна информация, известная на момент начала выполнения транзакции.
Если вы применяете транзакции для модификации информации в базе данных типа
STANDARD (Paradox, dBASE), то допустим только уровень изоляции tiDirtyRead. Для баз
данных текстового типа (ASCIIDRV) недопустимо использовать транзакции вообще.
Database! был доступен в форме Forml, нужно добавить вес модуль файл заголов]
модуля данных. Для этого активизируйте форму и выберите пункт Include Unit Hdr MI
ню File. В открывшемся окне выберите имя модуля, соответствующего модулю даннь
(Unit2) и нажмите кнопку ОК. В результате в модуль формы (Unitl) будет добавлена erpi
ка ^include "Uni2.h". Впрочем, то же самое можно было сделать и вручную.
Список ListViewl, расположенный в левой части формы, будет включать все таблиц
базы данных. Заполнить этот список значениями можно при открытии формы, в обрабо
чике ее события OnCreate:
void fastcall TForml::FormCreate(TObject *Sender)
{
DModl->Databasel->GetTableNames(ListBoxl->Iterns);
ListBoxl->ItemIndex=0;
DModl->Databasel->GetFieldNames(
Li stBoxl->Iterns->Strings[ListBoxl->ItemIndex],
ListBox2->Items);
}
В первой строке обработчика события вызывается метод GetTableNames, заполняй
щий свойство Items списка ListBoxl именами таблиц базы данных, к которой подключе
объект Database!'. Далее активизируется первый элемент списка (с индексом 0) и вызывг
ется метод GetFieldNames, заполняющий свойство Items списка ListView2 именами поле
таблицы, имя которой указано в первом аргументе (т. е. таблицы, чье имя является первы
в списке). Обратите внимание на то, что при обращении к объекту Database! нужно чере
модификатор -> указывать имя модуля данных DModl.
Category EmployeeNo
LNome
ЯЕЕКЗЭИИИИИИИИИЯИИ FName
Suppliers MNome
HireDtfe
FireDeta •
Description
Photo
Birthday Т
созданному объекту TSession. Обратите внимание, что если вы добавили на форму или
модуль данных один из объектов набора данных (например, ТТаЫе), то неявно создается
и связывается с Session и объект типа TDatabase. Доступ к неявно созданному объекту
TSession можно получить через глобальную переменную Session.
Если есть необходимость в настройке свойств объекта TSession в режиме конструкто-
ра, то нужно создать объект явно, поместив его с вкладки BDE или на форму, или в модуль
данных. В большинстве случаев в приложении достаточно одного объекта TSession, соз-
данного явно или неявно. Однако иногда бывает необходимо использовать несколько та-
ких объектов, например, если вы применяете несколько наборов объектов баз данных, для
которых предусмотрены разные глобальные настройки поведения. Кроме того, если вы
используете в своем приложении несколько потоков (Threads), то необходимо для каждого
из потоков использовать свой объект TSession.
Далее будет рассмотрено несколько задач, решаемых при помощи компонента
TSession.
ческую генерацию имен, если форма или модуль данных содержит более одного объект
TSession (неявно создаваемая сессия Default при необходимости автоматически удаляет
ся). С другой стороны, нельзя добавить на форму или в модуль данных объект TSession
если там уже есть объект этого типа и для него свойство AutoSessionName имеет значени
true. Последнее ограничение состоит в том, что в процессе выполнения программы вы н
сможете задать значение для свойства SessionName, если для сессии разрешена автомати
ческая генерация имен.
Свойство KeepConnections логического типа задает значение для свойств;
KeepConnection всех неявно создаваемых в контексте текущей сессии объектов баз дан
ных. Свойство KeepConnection компонента TDatabase уже подробно рассматривалос]
ранее, и здесь мы останавливаться на этом не будем. SQLHourGlass — еще одно свойств(
логического типа, задающее поведение выполняющихся в контексте сессии объектов ба:
данных. Смысл свойства, я думаю, понятен из его названия. Если для него задано значе
ние true, курсор мышки примет вид песочных часов SQL, пока BDE ожидает завершеню
выполнения SQL-сервером запрошенных у него действий. Свойство можно устанавливай
только до открытия сессии.
Свойство NetFileDir специфично для таблиц Paradox и предназначено для храненш
пути к файлу PDOXUSRS.NET, который BDE использует для управления сетевым до-
ступом к таблицам Paradox. Если путь к этому файлу не задан, то он берется из настроек
драйвера Paradox. Свойство PrivatDir задает каталог, в котором будут храниться создава-
емые BDE при обработке таблиц временные файлы. По умолчанию используется каталог
BIN той папки, в которую был установлен C++ Builder.
После того как все необходимые свойства для сессии заданы, ее можно открыть, за-
дав для свойства Active значение true. Как всегда в таких случаях, для тех же целей мож-
но воспользоваться методом Open. При открытии сессии инициализируются свойства
PrivateDir, NetFileDir, если они не были настроены вручную в режиме конструктора
и ConfigMode. Для закрытия сессии можно воспользоваться свойством Active, задав для
него значение false, или вызвав метод Close. После закрытия сессии деактивируются так-
же и все имеющие отношение к ней сеансы связи с базами данных и, как следствие, все
выполняющиеся в ее контексте наборы данных. После повторного открытия сессии вам
придется самостоятельно активировать все нужные наборы данных. Кстати, самим от-
крывать сессию не обязательно. Достаточно активизировать один из наборов данных или
объектов баз данных, связанных с сессией, и она автоматически будет открыта.
И в заключение обзора свойств компонента TSession следует упомянуть о свойствах
только для чтения Databases и DatabaseCount. Эта парочка свойств, как и следует из их
названия, позволяет осуществлять итерацию по всем открытым в контексте текущей сес-
сии объектам баз данных. Ниже приведен пример кода, позволяющий активизировать все
объекты базы данных, связанных с сессией Sessionl при повторном ее открытии:
Sessionl->Active=true;
for (int i = 0; i < Sessionl->DatabaseCount; i++)
Sessionl->Databases[i]->Active = true;
Конечно же, вам придется позаботиться еще и о том, чтобы открыть все связанные с сесси-
ей (или, что то же самое, со всеми объектами баз данных текущей сессии) наборы данных.
Компонент TSession имеет всего два события. Событие OnStartup происходит при
переключении свойства Active из значения false в значение true. Обработчиком этого
Глава 3. Использование механизма ВОЕ (продолжение) 117
том, что объекты типа TSession могут хранить в своем внутреннем буфере набор паролей
При попытке подключения к таблице, закрытой паролем, BDE может получить сообще
ние о недостаточных правах доступа. В этом случае для подключения к таблице може'
использоваться один из паролей, хранящихся в сессии, в контексте которой работает от
крываемый набор данных. Если пароля для таблицы в буфере объекта TSession нет, вы
дается стандартное диалоговое окно для ввода пароля (см. рис. 4.6). Используя это окно
можно добавить новый пароль (кнопка Add), удалить указанный пароль (кнопка Remove)
а также удалить все пароли, хранящиеся в текущем объекте TSession (Remove all). Для до-
бавления пароля и задания дальнейшей реакции BDE можно воспользоваться событием
OnPassword, которое упоминалось в предыдущем разделе.
Простейший способ добавить пароль к текущей сессии - - использовать метод
AddPassword. В качестве аргумента этот метод принимает строку AnsiString, содержащую
пароль. Метод AddPassword за один вызов добавляет только один пароль, но этот метод
можно вызывать сколько угодно раз. Допустим, что в контексте сессии Session! выпол-
няется четыре набора данных. Все эти наборы (объекты ТТаЫе) подключены к таблицам,
для доступа к которым требуется указать пароли Passl, Pass2, Pass3 и Pass4 соответствен-
но. Тогда, чтобы успешно подключиться к таблицам, а также отменить выдачу стандарт-
ного диалогового окна для ввода пароля, в тело обработчика события OnPassword можно
вставить следующий код:
void fastcall TForml::SessionlPassword(TObject *Sender, bool &Continue)
{
Sessionl->AddPassword("Passl")
Sessionl->AddPassword("Pass2")
Sessionl->AddPassword("Pass3")
Sessionl->AddPassword("Pass4")
Continue=true;
}
Если в предыдущем отрывке кода опустить последний оператор, присваивающий зна-
чение true аргументу Continue, то соответствующая таблица открыта не будет, и на экран
будет выдано сообщение о недостатке прав для подключения.
Метод A ddPassword можно использовать не только в обработчике события OnPassword.
Например, можно собственными средствами организовать ввод имени и пароля пользова-
теля при запуске приложения, а затем, в зависимости от введенных значений, один или
несколько раз вызвать метод AddPassword для добавления набора паролей, задающих
уровень доступа пользователя к таблицам базы данных.
По вполне понятным причинам можно не только добавлять пароли во внутренний
буфер объекта типа TSession, но и удалять их. Делать это можно при помощи методов
RemovePassword и RemoveAllPasswords. Метод Remove? assword принимает в качестве
аргумента строку AnsiString, содержащую пароль из буфера объекта TSession. Пароль
должен был быть добавлен до этого при помощи метода AddPassword. При помощи
метода RemoveAllPasswords можно удалить все добавленные ранее пароли, очистив тем
самым внутренний буфер объекта TSession. Оба метода могут использоваться при пере-
ключении приложения с одного пользователя на другого без перезагрузки приложения.
В этом случае, удалив одну группу паролей, соответствующих первому пользователю,
можно добавить другую группу паролей, определяющих права на доступ к информации
второго пользователя.
Глава 3. Использование механизма BDE (продолжение) 119
Есть еще один полезный метод, который можно с успехом использовать в своих при-
ложениях. Это метод GetPassword, не имеющий аргументов и возвращающий значение
логического типа. Вызов его приводит к возбуждению события OnPassword сессии. Если
для события определен обработчик, то выполняется заложенная в него логика, иначе на
экране отображается стандартное диалоговое окно для ввода пароля. В последнем слу-
чае метод GetPassword возвращает значение true, если пользователь нажал кнопку ОК,
и значение false, если пользователь нажал Cancel. Услугами метода GetPassword можно
воспользоваться для того, чтобы добавить группу паролей в буфер объекта TSession сразу
после определения пользователя, не дожидаясь реального подключения к таблицам.
}
fj n a 11 у
{
delete(ParList);
}
DBGridl->DataSource=DataSourcel;
DataSourcel->DataSet=Tablel;
Tablel->SessionName="MySession";
Tablel->DatabaseName="MyIntrbase";
Tablel->TableName="Sales";
Tablel->0pen();
Замечание
При повторном выполнении приведенного выше отрывка кода будет возбуждена ис-
ключительная ситуация с сообщением о том, что имя базы данных не является уникаль-
ным в данном контексте. Действительно, псевдоним с тем же именем уже был создан
при первом выполнении и остается доступен для ВОЕ в контексте текущей сессии. Чтобы
избежать этой ситуации, прежде чем создавать новый псевдоним, необходимо прове-
рить, нет ли уже псевдонима с таким именем. Для этого можно воспользоваться методом
IsAlias объекта TSess/on. Этот метод получает строку с именем псевдонима, и если такой
псевдоним данной сессии доступен, то возвращает значение true.
Замечание
Состав доступных в контексте данной сессии псевдонимов зависит от значения
свойства ConfigMode компонента TSess/on. Это свойство может принимать следующие
значения: cfmVirtual (доступны все псевдонимы из конфигурационного файла ВОЕ, все
псевдонимы данной сессии, как постоянные, так и временные); cfmPersistent (доступны
только псевдонимы из конфигурационного файла ВОЕ и постоянные псевдонимы данной
сессии); cfmSession (доступны только псевдонимы, созданные в контексте данной сес-
сии). По умолчанию для сессии доступны все возможные псевдонимы.
Метод AddAHas в основном используется для создания псевдонимов для баз данных
SQL. Для баз данных в формате STANDARD (Paradox, dBASE или текстовый формат) про-
ще использовать другой метод — AddStandardAlias. Он также принимает три аргумента,
и все эти аргументы имеют тип AnsiString. Первый аргумент должен задавать имя псев-
донима, второй — путь к таблицам базы данных. Третий аргумент указывает тип базы
данных и может принимать одно из трех значений: «Paradox», «DBASE» и «ASCIIDRV».
При помощи метода DeleteAlias можно удалить указанный псевдоним из сессии.
В качестве аргумента этому методу нужно передать имя доступного для сессии псев-
донима. Метод ModifyAlias позволяет модифицировать псевдоним, изменив его пара-
метры. Первый аргумент типа AnsiString должен задавать имя существующего псевдо-
нима. Второй аргумент типа TStringList* должен содержать список новых параметров
псевдонима. Приложение должно позаботиться о создании, заполнении и удалении
объекта типа TStringList.
Созданный в контексте сессии псевдоним сохраняется только до тех пор, пока BDE
остается в памяти компьютера. Чтобы созданный псевдоним был доступен и при по-
следующих сеансах BDE, следует вызвать метод SaveConfigFile. Дело в том, что при
инициализации BDE вся информация из его файла конфигурации загружается в память,
и все изменения, сделанные в конфигурации BDE, автоматически на диске не сохраня-
Глава 3. Использование механизма BDE (продолжение) 121
ются. Следует заметить, что если свойство ConfigMode объекта TSession имеет значе-
ние cfmSession, то все изменения конфигурации, сделанные в контексте этого объекта,
сохранены не будут.
Так же как и псевдонимы, в сессию можно добавлять драйверы баз данных SQL.
Для манипуляции драйверами баз данных объект TSession имеет три метода: AddDriver,
DeleteDriver и ModifyDriver. По смыслу и способу использования эти методы мало отли-
чаются от методов манипуляции псевдонимами. Более подробную информацию можно
найти в справочной системе C++ Builder.
И последняя группа методов, о которых следует упомянуть в этом разделе, непо-
средственно связана с управлением явно и неявно созданными объектами TDatabase.
К этой группе относятся методы FindDatabase, OpenDatabase, CloseDatabase и Drop-
Connections. Метод FindDatabase принимает в качестве аргумента строку AnsiString,
содержащую имя объекта TDatabase, ассоциированного с данным объектом TSession.
Метод проверяет свойство DatabaseName всех сеансов связи, выполняющихся в конте-
ксте сессии, и в случае совпадения с переданным аргументом возвращает указатель на
найденный объект TDatabase. Если указанный объект базы данных не найден, то воз-
вращается значение NULL. Метод FindDatabase можно использовать в приложении
перед созданием нового объекта TDatabase, чтобы предотвратить попытки создать
объект с уже существующим именем.
Метод OpenDatabase предназначен для того, чтобы открыть сеанс связи, основан-
ный на уже существующем объекте TDatabase. Имя объекта базы данных передается
методу в качестве аргумента. В процессе работы метод OpenDatabase вызывает метод
FindDatabase, чтобы попытаться найти указанный объект TDatabase среди существую-
щих. Если объект не найден, создается временный объект TDatabase. В любом случае для
объекта базы данных вызывается метод Open и, в конце концов, метод OpenDatabase воз-
вращает на него указатель.
Закрыть объект TDatabase можно при помощи вызова метода DeleteDatabase. Объект
базы данных, свойство которого DatabaseName совпадает с переданным методу аргумен-
том, закрывается. Если этот объект был временным, то он удаляется из памяти, освобож-
дая ресурсы. И, наконец, метод DropConnections удаляет из памяти все временные объек-
ты базы данных, если они неактивны. Этот метод обычно используется для освобождения
ресурсов, если свойство KeepConnections сессии установлено в значение true.
В следующем пункте, завершающем обзор компонента TSession, приводится информа-
ция о том, как использовать сессию для получения исчерпывающей информации обо всех
соединениях и наборах данных, выполняющихся в ее контексте.
имеет значение true, то имена таблиц Paradox и dBASE будут содержать и расширения.
Значение true аргумента SystemTables предполагает, что кроме имен регулярных таблиц
базы данных будут возвращены также имена системных таблиц (если таковые имеются).
И последний метод, GetFieldNames, в качестве аргументов принимает имя базы дан-
ных и имя таблицы, возвращая в аргументе List список полей.
Схему работы со всеми этими методами проще понять на конкретном примере.
Создайте новое приложение и в верхней правой части пустой формы разместите элемент
TComboBox. Под ним, расположите три объекта типа TListBox, расположив их один за
другим по горизонтали. Над каждым из списков разместите метки (компонент TLabef),
последовательно задав для них подписи: «Псевдонимы:», «Параметры:» и «Поля:».
В заключение расположите на форме объект TSession. Оставьте для всех элементов имена
по умолчанию. Для списка ListBoxS и метки Labels задайте для свойства Visible значе-
ние false, так как сразу после открытия формы эти элементы не должны быть видимы.
В завершение настройки макета будущего приложения щелкните на кнопке построителя
для свойства Items объекта TComboBox и в диалоговом окне String List Editor введите три
строки: «Псевдонимы», «Драйверы» и «Базы данных».
Логика работы приложения должна быть следующая. При выборе объекта из комбини-
рованного списка ComboBoxl должны обновляться соответствующим образом и списки
ListBoxl viListBox2 (при необходимости viListBox3). Далее, при выборе определенного
элемента в списке ListBoxl соответственно должны обновляться и списки ListBoxl и, при
необходимости, ListBoxS. Список ListBox3 будет использоваться, только если в комбини-
рованном списке ComboBoxl был выбран элемент Базы данных. В остальных случаях сам
список и соответствующая ему метка отображаться на форме не будут, а ширина формы
должна быть настроена соответствующим образом.
В листингах 3.1 и 3.2 приведен полный код файла заголовка и файла модуля приложе-
ния GetSessionlnfo, а после листингов помещено краткое описание.
Листинг 3.1. Файл заголовка приложения GetSessionlnfo
//
#ifndef UnitlH
#defme UnitlH
//
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <DBTables.hpp>
//
class TForml : public TForm
{
published: // IDE-managed Components
TListBox *ListBoxl;
TSession *Sessionl;
TComboBox *ComboBoxl;
TLabel *Labell;
TListBox *ListBox2;
TLabel *Label2;
TListBox *ListBox3;
TLabel *Label3;
124 Borland C++ Builder 6. Разработка приложений баз данных
#include <vcl.h>
#pragma hdrstop
#include "Unitl.h
#pragma package(smart_ini^t)
#pragma resource "*.dfm"
TForml *Forml;
_fastcall TForml : :TForml (TComponent* Owner)
: TForm(Owner)
case 1: //Драйверы
{
Sessionl->GetDri ve r Names (My Li st) ;
Width=330;
ListBox3->Visible=false;
Label 3->Visible=false;
break;
}
case 2: //Базы данных
{
Sessionl->GetDatabaseNames(MyList) ;
Width=495;
ListBox3->Visible=true ;
Label3->Visible=true;
break;
Li stBoxl->Items=MyLi st ;
ListBoxl->ItemIndex=0;
Label!- >Caption=ComboBoxl->I terns- >St rings
[ComboBoxl->ItemIndex]+" : " ;
}
_ finally
{
delete MyList;
}
ListBoxlClick(NULL) ;
>
// ------------------------------------------------------------------
void _ fastcall TForml : : ListBoxlClick(TObject *Sender)
I
TStringList *MyList=new TStringListO ;
try
{
swi tch(ComboBoxl->ItemIndex)
{
case 0: //Псевдонимы
{
Sessionl->GetAliasParams (Li stBoxl->I terns- >
5trings[ListBoxl->ItemIndex] , MyList) ;
Label2->Caption="napaMeTpbi: " ;
break;
}
case 1: //Драйверы
{
Sessionl->GetDriverParams(Li s tBoxl->I terns- >
Strings[ListBoxl->ItemIndex] .MyList) ;
Label2->Caption = "riapaMeTpbi: " ;
break ;
126 Borland C++ Builder 6. Разработка приложений баз данных
ListBox2->Items=MyList ;
ListBox2->ItemIndex=0;
}
_ fl n a 1 1 у
{
delete MyList;
}
if (ComboBoxl->ItemIndex == 2) ListBox2Click(NULL) ;
}
// ----------- .
void __ fastcall TForml: :ListBox2Click(TObject *Sender)
{
i f (ComboBoxl->ItemIndex != 2) return;
TStringList *MyList=new TStringLi st () ;
try
{
Sessionl->GetFieldNames (Li stBoxl->I terns- >
Strings [ListBoxl->ItemIndex] ,
ListBox2->Items->Strings[ListBox2->ItemIndex] ,
MyList) ;
ListBox3->Items=MyList ;
ListBox3->ItemIndex=0;
}
_ fi n a 1 1 у
{
delete MyList;
DefauUDO
IBLocel
MyOata
MvOotol
База ценных MS Access
:unl FoxPro
Та&лииы Visual FoxPro
Файлы dSASE
Замечание
К упомянутой группе методов можно отнести также и методы GetAliasDriVerName
и GetConf/'gParams. Первый из них получает в качестве аргумента имя псевдонима
и возвращает имя используемого псевдонимом драйвера. Второй метод предназначен
для возвращения параметров конфигурации ВОЕ. Более подробную информацию об
этом методе можно получить в справочной системе C++ Builder и ВОЕ.
128 Borland C++ Builder 6. Разработка приложений баз данных
Элементы визуализации
и управления данными
Сталкиваясь с элементами управления этого типа, сразу убеждаешься в том, насколькс
английский язык приспособлен к тому, чтобы быть языком терминов. То, что по-русскр
называется элементами визуализации и управления данными, по-английски звучит так
Data-Aware Controls (или просто Data Controls). Все элементы этого типа собраны НЕ
вкладке Data Controls палитры компонентов. Основное их назначение, как и следует и;
названия, — отображать информацию из источника данных и предоставлять возможность
манипулировать данными. Таким образом, интерфейс любого приложения баз данных со-
стоит из набора элементов визуализации и управления данными.
Часть компонентов с вкладки Data Controls по своим функциям соответствуют привыч-
ным компонентам, расположенным на вкладке Standard. Например, элементу редактирования
TEdit соответствует элемент редактирования TDBEdit, метке TLabel соответствует компо-
нент TDBText, и т.д. Основное отличие элементов визуализации и управления данными от
их стандартных собратьев в том, что все элементы с вкладки Data Controls имеют свойство
DataSource. При помощи этого свойства компонент может подключаться к источнику данных,
предоставляя пользователю возможность просматривать или модифицировать информацию
из одной или нескольких таблиц, соответствующих этому источнику.
Какой из элементов визуализации и управления данными использовать в своем при-
ложении, определяется конкретной ситуацией и назначением формы. Большая часть
элементов управления с вкладки Data Controls предоставляет доступ к значению поля
текущей записи набора данных. Такие элементы управления, как TDBGrid и TDBCtrlGrid,
предоставляют доступ сразу к нескольким записям набора данных. В большинстве случа-
ев удобнее всего использовать сетку (компонент TDBGrid). Этот компонент предоставля-
ет пользователю огромное количество возможностей оформления, просмотра, навигации,
модификации информации из набора данных. Однако достаточно часто возникают ситуа-
ции, когда в форме нужно отображать только значения полей текущей записи. Например,
в форме, отображающей список операций (приход, расход, списание товара и т. д.), удоб-
нее использовать сетку (TDBGrid). Из этой формы можно вызывать другую форму, со-
держащую детальную информацию о текущей операции. Эта форма, по вполне понятным
причинам, должна отображать информацию только о текущей записи.
В последнем случае тип элемента управления определяется типом того поля набо-
ра данных, значения из которого элемент управления должен отображать. Например,
для отображения значений полей логического типа целесообразно использовать флаж-
ки (TDBCheckBox), для полей графического типа— компонент TDBImage, для полей
Memo — компонент TDBMemo и т. д.
В предлагаемом вашему вниманию разделе будут кратко освещены общие и некоторые спе-
цифические вопросы использования элементов визуализации и управления данными. Основное
внимание, однако, будет уделено описанию возможностей элемента сетка (компонент TDBGrid).
уж сложно. В этом разделе будет рассмотрен пример, описание которого поможет понят
логику работы с компонентом TDBChart.
Прежде чем приступить к примеру, следует добавить в базу данных с псевдониме»
MyData еще одну таблицу. Эта таблица будет называться Storage, и в ней будут хранить
ся данные об остатках товара на складе. Описание структуры таблицы Storage приведен!
в таблице 3.1.
Таблица 3.1. Описание структуры таблицы Storage
Как видите, таблица Storage невелика— всего четыре поля. Поле StoreltemNo яв
ляется первичным ключом таблицы, а поле Item — внешним ключом, ссылающимся н<
первичный ключ таблицы Items. Поле Qty предназначено для хранения количества товара
имеющегося на складе. Вводить значение в это поле не обязательно, так как отсутстви*
значения просто будет расцениваться как отсутствие товара на складе (т. е. отсутствующее
значение будет считаться нулем). В поле Description можно будет хранить дополнитель
ную информацию, например, о необходимости заказать товар и т.д. В заключение таблиц)
Storage необходимо связать с таблицей Items по полям Item и ItemNo. Как это можнс
сделать при помощи утилиты Database Desktop, уже рассказывалось в предыдущих гла
вах. Сохраните таблицу под именем Storage в том же каталоге, где хранятся и остальные
таблицы базы данных (псевдонима) MyData.
labels: (C
0 - Cetegoiy
1 -Item
"
Рис. 3.14. Заголовки столбцов сетки проще всего настроить в режиме конструктора
Сетка TDBGrid, как уже говорилось, предназначена не только для отображения
информации; если это допускает набор данных, она дает возможность редактировать
138 Borland C++ Builder 6. Разработка приложений баз данных
данные. Однако если установить свойство Readonly объекта TColumn в значение tru
редактировать значение соответствующего поля в ячейке сетки будет запрещено. Г
умолчанию объект TColumn предоставляет тот же стиль редактирования, что и компоне!
TDBEdit. Изменить стиль редактирования в ячейке столбца можно при помощи свойст!
ButtonStyle. Оно может принимать одно из трех значений: cbsAuto, cbsEllipsis и cbsNon
Значение по умолчанию — cbsAuto. При нем ячейки столбца ведут себя или как обычнь;
элементы редактирования (компонент TDBEdit), или как комбинированные списки (KOIV
понент TDBComboBox). Все зависит от свойства PickList объекта TColumn. Это свойств
имеет тип TStrings* и может содержать список значений для подстановки. В этом случа
в режиме выполнения приложения при переходе в режим редактирования, ячейка столбц
приобретет вид комбинированного списка (компонент TDBComboBox). Прикреплении]
к ячейке список будет содержать все значения, указанные в свойстве PickList, и одн<
из них можно выбрать в качестве значения поля, соответствующего столбцу (см. рис
3.15). Высоту выпадающего списка в строчках можно указать при помощи свойств;
DropDownRows. По умолчанию это свойство имеет значение 7, т. е. выпадающий СПИСОР
будет содержать 7 строк.
Значение cbsNone свойства ButtonStyle предполагает, что ячейка в любом случае будет
вести себя как обычный элемент редактирования.
Если для свойства ButtonStyle задать значение cbsEllipsis, то при переходе в режим ре-
дактирования, ячейки соответствующего столбца будут снабжаться кнопкой построителя
(кнопка с троеточием —Ellipsis). При нажатии этой кнопки (см. рис. 3.17) будет генериро-
ваться событие OnEditButtonClick компонента TDBGrid. Кнопкой построителя и событи-
ем OnEditButtonClick удобно пользоваться, чтобы вызвать на экран диалоговое окно, при
помощи которого, например, можно не только выбрать нужное значение, но и добавить
к списку новое (предварительно добавив новую запись в соответствующую таблицу).
Q Dataset—Table2.
G Lookup Keys — CategoiyNo.
G Result Field—Category.
В результате этих действий в наборе данных Tablel будет определено поле подстанов
ки с именем CategoryName.
Теперь перейдем к созданию формы Forml. Прежде всего, задайте для ее заголовю
(свойство Caption) какое-нибудь подходящее значение, например Items. После этого по
местите на форму компонент TDBGrid, оставив для него имя по умолчанию (DBGridl)
Для свойства DataSource сетки укажите значение: DMod l-> DataSource 1. Щелкнув нг
кнопке построителя свойства Columns сетки DBGridl, введите определения трех колонок
Последовательно задайте для их свойств FieldName значения CategoryName, Item и Price.
Для первой колонки (CategoryName) в качестве значения свойства ButtonStyle укажите
cbsEllipsis. Тогда ячейки этой колонки в режиме редактирования будут снабжаться кноп-
кой построителя.
Далее следует настроить заголовки всех трех колонок. Для доступа к свойствам заго-
ловка нужно развернуть свойство Title каждого из объектов TColwnn. Для колонок сетки,
изображенной на рис. 3.16, заданы следующие значения свойств (объекта-свойства Title):
Q свойство Caption — Категория, Товар и Цена соответственно;
G свойство Alignment — taCenter;
Q свойство Font, подсвойство Color — clMaroon',
1_J свойство Font, подсвойство-объект Style, подсвойство fsBold— true.
В ответ на'нажатие кнопки построителя, которая будет появляться в ячейках первой
колонки сетки при переходе в режим редактирования, на экране должна отображаться
форма, предоставляющая интерфейс для манипуляций записями таблицы Category.
Создайте новую форму (меню File \ New \ Form) и добавьте ее к текущему проекту, со-
хранив в том же каталоге, что и остальные файлы проекта. Для заголовка новой формы за-
дайте значение Categories. В верхней части формы разместите компонент TDBNavigator,
чуть ниже — компонент TDBGrid, еще ниже — две кнопки (компонент TButton). Для
имен всех объектов оставьте значения по умолчанию. Для свойств DataSource объектов
DBNavigatorl и DBGridl задайте значение DModl->DataSource2.
На следующем шаге нужно настроить сетку DBGridl формы Form2. Откройте редак-
тор столбцов (щелкнув на кнопке построителя свойства Columns) и добавьте определения
двух столбцов. Первый столбец свяжите с полем Category, второй — с полем Description
таблицы Category. Для заголовков задайте подписи Категория и Описание соответствен-
но. Остальные свойства заголовков столбцов настройте так же, как и для столбцов сетки
DBGridl формы Forml.
Форма Form2 должна отображаться на экране при щелчке мышкой на кнопке построи-
теля в ячейке первого столбца сетки. Чтобы обеспечить это, нужно соответствующим об-
разом обработать событие OnEditButtonClick объекта DBGridl. В первую очередь в обра-
ботчике этого события нужно проверить, в ячейке какого столбца была нажата кнопка
построителя. Это важно, так как стиль cbsEllipsis мог быть задан для нескольких столбцов
сетки. В таком случае удобно пользоваться свойством SelectedField компонента TDBGrid.
В следующей строке приведено определение свойства SelectedField:
property Db::TField* SelectedField =
{read=GetSelectedField, write=SetSelectedField};
Глава 3. Использование механизма ВОЕ (продолжение) 141
В первой строке кода объявляется переменная SelF типа ТТаЫе*, а затем этой пере-
менной присваивается значение свойства SelectedField. Далее проверяется, выделена ли
какая-нибудь ячейка сетки (в этом случае значение переменной SelF не должно равняться
NULL). Если переменная SelF содержит допустимую ссылку на поле набора данных, про-
веряется его имя. Если имя поля — CategoryName, создается объект формы Form2, затем
эта форма открывается в модальном режиме. Когда форма будет закрыта, соответствую-
щая объектная переменная удаляется.
Чтобы приведенный выше код обработчика события OnEditButtonClick работал пра-
вильно, нужно предотвратить автоматическое создание формы Form!. Для этого нужно
отобразить на экране диалоговое окно параметров проекта (меню Project \ Options) и перей-
ти на вкладку Forms. Затем в списке автоматически создаваемых форм (Auto-create forms)
следует выбрать Form2 и нажать кнопку со стрелкой вправо (>). В результате Form2 будет
переведена в список доступных форм (Available forms).
В заключение нужно правильно настроить форму Form2. В частности, желательно,
чтобы после загрузки формы активной записью сетки была запись о категории, для ко-
торой и была вызвана форма. Например, если кнопка построителя нажата в ячейке за-
писи о товаре из категории Процессоры, то сразу после загрузки формы Form2 активной
должна быть запись о категории Процессоры. Для этого нужно воспользоваться событием
OnCreate формы Form2. Ниже приведен код обработчика этого события:
void _ fastcall TForm2 : : FormCreate(TObject *Sender)
{
AnsiString ValString;
ValString=Forml->DBGridl->SelectedFi eld- >AsSt ring;
if (! (ValString.IsEmptyO))
{
TLocateOptions SearchOptions ;
DModl->Table2->Locate("Category", ValString, SearchOptions);
выделенной в форме Forml. Если это значение не пусто, то при помощи мето/
Locate указатель активной записи набора данных Table! (таблицы Category) nepi
водится на запись, значение поля Category которой совпадает со значением пер<
менной ValString.
В нижней части формы Form2 размещены две кнопки (компонент TButtori). При н<
жатии одной из них выбранное значение категории (значение первичного ключа) должн
быть присвоено полю Category текущей записи таблицы Items, после чего форма Form
должна быть закрыта. При нажатии второй кнопки форма Form2 должна быть просто за
крыта. Для первой кнопки задайте подпись ОК, для второй — Cancel. В тело обработчик
события OnClick кнопки ОК введите следующий текст:
void fastcall TForm2::BOKClick(TObject *Sender)
{
TDataSet* GridDataSetl;
TDataSet* GridDataSet2;
GridDataSetl=Forml->DBGridl->DataSource->DataSet;
GridDataSet2=DBGridl->DataSource->DataSet;
if(GridDataSetl && GridDataSet2)
{
GridDataSetl->Edit();
GridDataSetl->FieldByName("Category")->Value=
GridDataSet2->FieldByName("CategoryNo")->Value;
GridDataSetl->Post() ;
}
Close 0;
}
В первых двух строках кода обработчика события OnCreate объявлены две пере-
менные типа TDataSet*. В следующих двух строках кода переменной GridDataSetl
присваивается указатель на набор данных, к которому подключена сетка из формы
Forml (объект Table!), а переменной GridDataSetl — указатель на набор данных для
сетки из формы Form2 (объект ТаЫе2). В принципе, без этих громоздких действий
можно обойтись, но только если вы точно уверены, что оба объекта TDBGrid подклю-
чены к источникам данных. Именно это проверяется в условном операторе // Неплохо
было бы еще проверить имена источников данных и при несовпадении предусмотреть
соответствующую реакцию.
Если обе переменные типа TDataSet* ссылаются на нужные наборы данных, то на-
бор, связанный с таблицей Items, переводится в режим редактирования, полю Category
присваивается значение первичного ключа текущей записи набора данных, связанного
с таблицей Category, и вызывается метод Post. В заключение форма Form2 закрывается
при помощи вызова метода Close. Обработчик события кнопки Cancel должен содержать
только один оператор — вызов метода Close.
Откомпилируйте приложение и запустите его на выполнение. Щелкните на кнопке по-
строителя одной из ячеек столбца CategoryName (Категория). В результате на экране бу-
дет отображена форма Form2 (см. рис. 3.18). Используя элементы управления этой формы,
вы можете добавить в таблицу Category новый элемент, выбрать любой элемент из сетки
DBGridl, а затем, нажав кнопку ОК, присвоить соответствующее значение категории по-
лю Category таблицы Items.
Глава 3. Использование механизма ВОЕ (продолжение) 143
К. „.гари.)
Принтеры St. ± - Ч* -I-! I I ?
К.Г»1(Ц-(1|1И« >;:fMlMf! '• _*]
Принтеры -lEi
> Принтеры Принтеры струйные, матричные. пвэер1
Принтеры О
Сканеры Сканеры ппаншетные
. Принтеры Вг,
Принтеры Efu;
; Процессоры Процессоры Intel, Aind и VIA
, Принтеры Ef;-;
Модули памяти Моаули памяти DIMM. DIMM DDR и RlMM ' •.
'( Принтеры НС/
Видеокарты Виаеокарты от различных производите
,;:. Принтеры HI :-
Мониторы ЭПТ- и ЖКИ-мониторы i
^Принтеры HI
iLJ ±Г
OK " """| Cancel
Параметры сетки
Еще одно важное интегральное свойство компонента TDBGrid, управляющее различ
ными аспектами поведения и отображения сетки на экране, — свойство Options (параме-
тры]. Это свойство-множество (Set)', оно может содержать до 13 элементов перечислимо-
го типа TDBGridOption. Как обычно в таких случаях, чтобы включить элемент в СОСТЗЕ
параметров сетки в режиме конструктора, нужно в инспекторе объектов задать для неге
значение true. Ниже приведено краткое описание каждого из элементов.
Q dgEditing — разрешается редактировать данные в сетке. Этот элемент игнорируется,
если в состав параметров сетки входит элемент dgRowSelect.
LJ dgAlwaysShowEditor — сетка всегда будет находиться в режиме редактирования, так
что пользователю не нужно нажимать клавишу Enter или F2, чтобы перейти к редак-
тированию данных. Элемент dgAlwaysShowEditor игнорируется, если в набор параме-
тров сетки не входит элемент dgEditing или входит элемент dgRowSelect.
G dgTitles — если этот элемент присутствует в наборе параметров, то в верхней части
столбцов сетки будут отображаться заголовки.
Q dglndicator — наличие среди параметров этого элемента приводит к отображению в левой
части сетки столбца серых кнопок, на одной из которых — индикатор активной записи.
Q dgColumnResize — обеспечивает возможность перемещать столбцы и менять их раз-
меры (перетаскивая правую границу заголовка столбца).
О dgColLines — отображаются линии между столбцами в сетке.
О dgRowLines — отображаются линии между строками в сетке.
О dgTabs — можно перемещаться по ячейкам сетки при помощи клавиш Tab или Shift-
Tab. Если элемента dgTabs нет в наборе параметров сетки, при нажатии клавиши Tab
(Shift-Tab) фокус ввода покинет сетку, перейдя на следующий элемент управления (при
использовании комбинации клавиш Shift-Tab — на предыдущий).
Q dgRowSelect — щелчок мышкой в любой ячейке приводит к выделению целой строки,
содержащей эту ячейку. Если этот элемент присутствует в наборе параметров сетки,
редактировать данные невозможно. В этом случае элементы dgEditing к dgAlwaysShow-
Editor игнорируются.
О dgAlwaysShowSelection — выделенные ячейки будут отображаться с прямоугольником
выделения, даже если сетка теряет фокус ввода.
О dgConfirmDelete — если пользователь удаляет строку в сетке, нажимая комбинацию кла-
виш Ctrl + Delete, на экране отобразится диалоговое окно для подтверждения удаления.
Q dgCancelOnExit — если пользователь добавил новую запись и не ввел в нее никаких
данных, то при переходе фокуса ввода с сетки на другой элемент управления формы
запись не сохраняется в наборе данных. Это предотвращает случайный ввод в набор
данных пустой записи.
Q dgMultiSelect— разрешается выбирать в сетке сразу несколько записей (множественный
выбор). Для выбора нескольких записей, наряду с мышкой используется клавиша Ctrl.
Глава 3. Использование механизма ВОЕ (продолжение) 145
Сначала в этом отрывке кода в теле цикла при помощи свойства Tag очередного флажк
определяется его порядковый номер (считая от 0), а затем этот номер приводится к тип
TDBGridOption. Это можно сделать, так как в таком же порядке определены констант]
перечислимого типа TDBGridOption. Затем проверяется наличие соответствующего эле
мента в наборе параметров сетки, а результат присваивается свойству Checked очередног
флажка (метод Contains множества Set возвращает значение true, если указанный элемен
присутствует во множестве).
Чтобы предусмотреть отклик на изменение состояния одного из флажков, нужно вое
пользоваться его событием OnClick. Так как все флажки имеют различные значения свой
ства Tag, достаточно одного обработчика события. Создайте обработчик события OnClici
для одного из флажков (например, для первого) и свяжите с ним события OnClick все?
остальных флажков. В обработчике события прежде всего необходимо привести аргумет
Sender к типу TCheckBox*, чтобы удостовериться, что событие произошло именно дл?
флажка. Если это так, следует проверить значение свойства Checked флажка. Если фла-
жок установлен, соответствующий элемент (константа типа TDBGridOption) добавляется
в набор параметров сетки, иначе — удаляется. Константу получают, приведя уменьшенное
на единицу значение свойства Tag флажка (так как константы нумеруются с нуля), к типу
TDBGridOption. Ниже представлен отрывок кода, выполняющий описанные действия:
TCheckBox* TCB;
TDBGridOptions Opt=DBGridl->Options;
TCB=dynamic_cast<TCheckBox*>(Sender);
if(TCB)
{
if(TCB->Checked) DBGridl->Options = Opt
« ((TDBGridOption)(TCB->Tag-l));
else DBGridl->Options = Opt
» ((TDBGridOption)(TCB->Tag-l));
-}
Однако следует иметь в виду, что событие OnClick будет происходить не только тогда,
когда пользователь будет менять значения флажков при помощи мышки или клавиатуры,
но и при программном изменении значения свойства Checked. Таким образом, обработчик
события OnClick будет вызван тринадцать раз при обработке события OnCreate формы.
Чтобы избежать этого, используется переменная логического типа IsFormCreate, объявленная
в разделе private класса TForml. В первой строке обработчика события OnCreate формы этой
переменной нужно задать значение false, а в последней строке обработчика -— значение true.
Итак, на момент инициализации свойства Checked флажков переменная IsFormCreate будет
иметь значение false. Тогда, введя в первой строке обработчика события OnClick:
if (! IsFormCreate) return;
удастся избежать излишнего выполнения его операторов.
Полный текст описанного здесь тестового приложения приведен в листингах 3.3 и 3.4.
Примерный вид приложения в процессе выполнения приведен на рис. 3.19.
Листинг 3.3. Файл заголовка приложения GridOptionsDemo
//
#ifndef UnitlH
#defme UnitlH
Глава 3. Использование механизма ВОЕ (продолжение) 147
II ___________________________________
void _ fastcall TForml: : FormCreate(TObject *Sender)
{
int j=0;
IsFormCreate=false;
IsFormCreate=true ;
}
II
void _ fastcall TForml: :CBEdi tingClick(TObject *Sender)
{
if ( ! IsFormCreate) return;
TCheckBox* TCB;
TDBGridOptions Opt=DBGridl->Options ;
TCB=dynamic_cast<TCheckBox*> (Sender) ;
if(TCB)
{
if (TCB->Checked) DBGridl->Options = Opt
« ((TDBGridOption) (TCB->Tag-l) ) ;
else DBGridl->Options = Opt
» ((TDBGridOption) (TCB->Tag-l) ) ;
Глава 3. Использование механизма ВОЕ (продолжение) 149
lulsi*
p Column Resize & Tabs • Confirm Delete
iU
Рис. 3.19. Приложение GridOptionsDemo после того, как был снят флажок Column Lines
нет, его придется создать при помощи метода Addlndex. Когда индекс будет не нужен, ег
следует удалить. Например, таблица Items имеет шесть полей и три постоянных индекс
заданных при ее проектировании. Чтобы можно было задавать сортировку для всех поле
таблицы, нужно при выполнении программы создать еще 9 индексов (так как для каждо!
поля потребуется два индекса: один для сортировки по возрастанию, другой — по убыв;
нию). Кроме того, все операции управления индексами следует проводить для таблищ
открытой в исключительном режиме (свойство Exclusive нужно установить в значеш-
true). В связи со всем вышеизложенным, тестовая программа-пример получится излитш
запутанной. С другой стороны, компонент TQuery позволяет задавать любой порядок ее
ртировки по любому полю или группе полей.
Логика работы тестового приложения такова. Сразу после его загрузки записи набс
ра данных (таблицы Items) будут отсортированы по первичному индексу (поле ItemNc
в порядке возрастания. Для этого необходимо при создании формы задать соответств}
ющую строку оператора SQL в свойстве SQL объекта Query 1 и затем активизировать ег
(открыть набор данных). Чтобы обозначить, по какому из столбцов в данный момен
отсортированы записи, перед подписью заголовка соответствующего столбца будет дс
бавлен или символ «>» (сортировка по возрастанию), или «<» (по убыванию).
Щелчок мышкой на заголовке столбца должен привести к тому, что записи будут отсор
тированы по полю, ассоциированному с этим столбцом. (Будем называть такой столбе
в контексте данного описания активным столбцом сортировки^ Щелчок на заголовке ак
тивного столбца меняет порядок сортировки на противоположный. Соответствующим об
разом меняется также и сигнальный символ в заголовке столбца («>» на «<» и наоборот]
При щелчке на неактивном столбце (т.е. не являющемся активным столбцом сортиров
ки), он становится активным, т.е. по полю, ассоциированному с этим столбцом, буду
отсортированы записи набора данных. Порядок сортировки — по возрастанию. Подпис!
заголовка предыдущего активного столбца восстанавливается в значение по умолчанию
а к подписи заголовка нового активного столбца добавляется символ «>».
В дальнейшем нам понадобятся две вспомогательные переменные. В описание класс;
TForml (в заголовочном файле Unitl.h), в раздел private, введите следующие объявления
private:
AnsiString SQLString;
int Curlndex;
Строка SQLString предназначена для хранения начальной части оператора SQL, ко
торый будет использоваться для формирования запроса объекта Query 1. В переменно?
Curlndex целого типа будет храниться индекс текущего активного столбца сортировю-
в коллекции Columns.
Вся логика программы будет сосредоточена внутри двух обработчиков событий:
OnCreate формы и OnTitleClick сетки DBGridl. При создании формы необходимо про-
извести первоначальную настройку объекта Query! и инициализацию вспомогательных
переменных. Ниже приведен код обработчика события OnCreate формы Forml, выполня-
ющий нужные действия.
void fastcall TForml::FormCreate(TObject *Sender)
{
SQLString=AnsiString("select * from Items");
Curlndex=0;
Queryl->SQL->Clear();
Глава 3. Использование механизма ВОЕ (продолжение) 151
else
OrderByString=" ORDER BY " + ColField +
CString=AnsiString("> ") + ColField;
}
Query l->Active=f alse;
Queryl->SQL->Clear() ;
Queryl->SQL->Add(SQLString + OrderByString);
152 Borland C++ Builder 6. Разработка приложений баз данных
Queryl->Active=true;
DBGridl->Columns->Items[ind]->Title->Caption = CString;
if(Cur!ndex != ind)
{
TColumn* Col=DBGridl->Columns->Items[Curlndex];
Col->Ti tle->Caption=Col->DisplayName;
Curlndex=ind;
}
Queryl->First() ;
DBGridl->SelectedIndex=ind;
}
В трех первых строках этого отрывка кода объявляются и инициализируются три вспо
могательные переменные. Переменной fc типа char присваивается первый символ подпи
си заголовка столбца Column, для которого произошло событие OnTitleClick. Переменна)
ColField типа AnsiString инициализируется именем поля, ассоциированного со столбцо!^
Column, а переменная ind целою типа получает значение индекса столбца Column в масси-
ве Columns. Одна из основных причин, почему эти переменные используются, в том, чтс
в дальнейшем набор данных QueryI будет закрываться. После повторного его открытия
переменная Column больше не будет ссылаться на допустимый объект. Использование
в этом случае переменной Column приведет к самым непредсказуемым ошибкам, в том
числе ошибкам нарушения доступа (access violation).
Далее объявляются еще две вспомогательные переменные типа AnsiString.
Переменная OrderByString предназначена для формирования заключительной ча-
сти оператора SQL, при помощи которой будет задаваться порядок сортировки.
В переменной CString будет храниться строка, обозначающая порядок сортировки
столбца (символ «>» или «<» и пробел).
На следующем шаге проверяется переменная^/2; (первый символ подписи заголовка). Если
fc содержит символ «>», то щелчок мышкой был произведен на заголовке того же столбца
и порядок сортировки для него был установлен по возрастанию. В этом случае нужно уста-
новить порядок сортировки по убыванию и перед подписью заголовка столбца по умолчанию
добавить строку «< »(символ «<» и пробел). Иначе или для столбца был установлен порядок
сортировки по убыванию, или щелчок был произведен на другом столбце. Независимо от при-
чин нужно для столбца, вызвавшего событие OnTitleClick, установить порядок сортировки по
возрастанию и соответствующим образом настроить подпись его заголовка.
Таким образом, в зависимости от значения переменной fc задаются значения пере-
менных OrderByString и CString. В переменной OrderByString формируется элемент
оператора SQL, в котором задаются имена полей и порядок сортировки. Этим элементом
является предложение ORDER BY (отдельные элементы оператора SQL называются пред-
ложениями). Вслед за ключевым словом ORDER BY указывается имя поля, хранящееся
в переменной ColField, и порядок сортировки для него. Если за именем поля указано
ключевое слово DESC, то порядок сортировки для поля — по убыванию, а если ничего
не указано — по возрастанию. Переменная ColField используется и для формирования
строки CString, так как по умолчанию подпись заголовка столбца совпадает с именем по-
ля, данные которого отображаются в столбце.
Далее следует настроить свойство SQL объекта Query]. Сделать это можно, если набор
данных закрыт. Как только нужная строка оператора запроса сформирована в свойстве
SQL, набор данных Query! можно вновь открыть. Так как переменная Column после за-
Глава 3. Использование механизма ВОЕ (продолжение) 153
крытая и повторного открытия набора данных больше не ссылается на допустимый объ-
ект TColumn, то для задания нужной подписи для заголовка столбца следует воспользо-
ваться свойством Columns сетки и предварительно определенной переменной ind.
iU Jj
йЭ
Vw^nivjIJ^t'ipUii,-
|l ri
•Vj }1Ч" ji •> " »| »~'
1 АИ. GDI ОЗУ г ма, ш dpi, до s p
1 А4, GQI,'ОЗУ SM&, 650 dpi, ДО! г
1 А4. GDI. ОЗУ 2(13) Мб. Ш dpi, дс
1 Л-1. GUI. ЫИ1 dpi. дм И ИМ'", nini..
э А< ОЗУ гмв.
картина (см. рис. 3.22). Этот сбой вызван тем, что при отрисовке новых ячеек изменен!
цвета или происходит неправильно, или не происходит совсем. Избежать этого можн
если менять окраску ячеек в зависимости от конкретной записи. Например, можно пров
рять значение ключевого поля ИетМои, в зависимости от его четности, использовать оди
из двух цветов. Но если удалить из таблицы несколько записей или применить к таблиг
фильтр, может случиться, что несколько подряд идущих строк будут окрашены одинаю
во. Тогда можно воспользоваться свойством RecNo компонента ТТаЫе. Это свойство С(
держит номер текущей записи. Ниже приведен исправленный вариант кода обработчик
события OnDrawColumnCell.
void fastcall TForml::DBGridlDrawColumnCell(TObject *Sender,
const TRect &Rect, int DataCol, TColumn *Column,
TGridDrawState State)
Глобальная переменная ColCol — это массив из двух элементов TColor; она объявлеш
следующим образом:
TColor ColCol []={ clSkyBlue, clMoneyGreen };
В первой строке кода извлекается номер текущей записи и при помощи операции по-
разрядного И (&) определяется его последний (младший) бит. Переменная целого типа
ind в результате получает или значение 0, или 1. В дальнейшем эта переменная использу-
ется для доступа к элементам массива ColCol. В остальном, код не претерпел изменений.
Откомпилируйте приложение и запустите его на выполнение. Можете убедиться, что те-
перь все работает правильно (см. рис. 3.23).
I
1 А4, GDI, ОЗУ 2 Мб. SOU dpi, до 0 ppm. лоток 16fj оистш, LPT. т»
I M, GDI. ОЛУ 8Мб, Ш <ipi «o4?fipm. литчк 1ЭД пиыаи. LPl,li-;f
1 М GDI ОЗУ 2(13) Мб, 600 dpt во 12 рргп, лоток 150 (650) писгов.
I A4.GD! eOfldpi aofippm, по™* Ш гнилоа LFT/!JSP -х.щаьД'А
1 ~..M, ОЗУ гМ5„6SOx«CO dpi, no 12 ppm, потах 2SO листов LPT/USB
,,-. .,
1 M user fJJJ Ifip^fiJ Ktfi, 6P,i .ir,:;:X[f>) -1p, ЙГГ, Jl0 C
Замечание
К сожалению, свойство RecNo можно применять для баз данных не всех типов. Для
баз данных в формате SQL свойство RecNo всегда содержит значение -1, независимо
от номера текущей записи. Чтобы воспользоваться этим свойством, необходимо пере-
определить методы GetRecNo и SetRecNo, применяемые для чтения и записи значения
свойства RecNo класса ТТаЬ/е.
TDBGrid) отображаются остатки товаров на складе (таблица Storage). При этом, если
количество товара подходит к концу (например, оно меньше 5), значение поля Qty будем
отображать красным, а если количество товара на складе подходит к критической отметке
(например, не превышает 10), то текст в ячейке будем отображать цветом Lime (светло-зе-
леным). В остальных случаях цвет отображения текста в ячейках стандартный.
Создайте новое приложение и разместите на его главной форме сетку, два объекта
ТТаЫе и один объект TDataSource, как всегда, оставив все имена объектов по умолчанию.
Объект ТаЫе2 подключите к таблице Items базы данных MaData, a Tablel — к таблице
Storage той же базы. Сетку DBGridl подключите к объекту DataSourcel, &DataSour-
eel — к Tablel. Отобразите на экране редактор полей (Fields Editor), дважды щелкнув на
изображении объекта Tablel в форме. Добавьте в список постоянных полей определения
полей StoreltemNo, Qty и Item из таблицы Storage. Для свойства Visible поля Item задайте
значениеуа/se, так как в итоговом наборе данных оно нам не понадобится. Далее на осно-
вании связи между таблицами Storage и Items создайте новое поле подстановки (пункт
New field). Задайте для этого поля имя ItemName, тип String и размер 50. В области Lookup
definition укажите следующие значения: Item (Key Fields), Table2 (Dataset), ItemNo (Lookup
Fields) и Item (Result Field). Нажмите кнопку OK и откройте набор данных Tablel.
В тело обработчика события OnDrawColumnCell сетки DBGridl введите следующий код:
void fastcall TForml::DBGridlDrawColumnCell(TObject *Sender,
const TRect &Rect, int DataCol, TColumn *Column,
TGridDrawState State)
{
TColor Color;
TFontStyles Style=TFontStyles();
int Qty=Tablel->FieldByName("Qty")->Value;
Color=FDefaultColor;
if(Column->FieldName == "Qty")
{
if(Qty 5) Color=clRed;
if((Qty >= 5) && (Qty = 10))
Color=clLime;
if(Qty fsBold;
}
if(State.Contains(gdSelected)) Color=clWhite;
DBGridl->Canvas->Font->Color=Color;
DBGridl->Canvas->Font->5tyle=5tyle;
DBGridl->DefaultDrawColumnCell(Rect, DataCol, Column, State);
}
В этом отрывке кода используется переменная FDefaultColor, объявленная в секции
private класса TForml следующим образом:
.
private:
TColor FDefaultColor;
Переменная FDefaultColor инициализируется при создании формы Form! цветом, ис-
пользуемым по умолчанию для отрисовки текста ячеек сетки:
FDefaultColor=DBGridl->Canvas->Font->Color;
158 Borland C++ Builder 6. Разработка приложений баз данных
<*ш
22 HPDeskJel656C 12 .
23 HP DeskJet 845C
Щ HP DeskJet 920C 33
25 HP DeskJet 940C 3
26 HP DeskJet 960C
27 HPDeskJetSSOC
26 HPDeskJet990C " 1 ijjji
29 HPPhotoSmartlllB 54
:
30 HP DeskJet 1220C •; '
31 HPPSC500 61 Щ
32 HP Office Jet G55 41 т
Некоторые соглашения
Встроенные комментарии
В операторах SQL можно использовать встроенные комментарии в стиле языка С
Любой текст между парой символов /* и */ считается комментарием и удаляется из опера
тора SQL при его синтаксическом разборе.
зуется для задания времени (РМ— до полудня, AM— после). Регистр ключевых слов AM
и РМ безразличен. Указывать модификаторы необязательно, в этом случае часть суток
определяется автоматически по значению времени. Например: «12:24:00».
Предложение Select
Предложение Select — основное в операторе выборки, так как оно задает тип операто-
ра. Элемент список_полей должен содержать перечень наименований полей, разделенных
запятой. Необходимо указать как минимум одно поле. Кроме полей из таблиц, перечислен-
ных в предложении From, в списке также указываются вычисляемые поля. Вместо списка
полей можно указать символ звездочки (*). В этом случае в итоговую выборку попадут все
поля таблиц, указанных в предложении From.
Если список должен содержать поля из разных таблиц, но с одинаковыми именами,
а также в некоторых других случаях (например, если имя поля совпадает с одним из за-
резервированных слов), перед именем поля необходимо указать через точку имя таблицы:
Items.Index. Для поля таблицы, также как и для вычисляемого поля, можно задать новое
имя. Для этого используется ключевое слово AS: ItemNo^S Number.
Если нужно, чтобы оператор выборки возвращал только несовпадающие строки, вслед
за ключевым словом Select нужно указать ключевое слово Distinct.
6 Зап. 319
162 Borland C++ Builder 6. Разработка приложений баз данных
Предложение From
Предложение From — также обязательное в операторе выборки. Вслед за ключевы
словом From нужно указать список таблиц, участвующих в запросе. Все поля, перечисле!
ные в предложении Select, должны принадлежать одной из таблиц, указанной в предложи
нии From, иначе эти поля будут считаться вычисляемыми. В простейшем случае имен
таблиц указываются через запятую. Например:
Select Item, Qty
From Items, Storage;
Замечание
По соглашению каждый оператор SQL должен заканчиваться неким разделителем
По умолчанию в качестве разделителя используется точка с запятой. Но так как объек
TQuery может инкапсулировать только один запрос, разделитель в конце строки опера
, тора SQL можно опустить.
Для таблицы, участвующей в предложении From, можно задать новое имя (псевдоним
таблицы). Псевдоним таблицы указывается вслед за именем таблицы через пробел, напри-
мер: "Items.db" It. Этот псевдоним потом нужно использовать в тексте оператора SQL
вместо имени таблицы (везде). Это удобно в нескольких случаях, например, для упроще-
ния записи вместо длинного имени таблицы можно использовать псевдоним из одной или
нескольких букв. Если имя таблицы указано как имя соответствующего файла с расши-
рением ("Items.db"), то для упрощения доступа к именам полей таблицы также удобно
использовать псевдоним. И, наконец, если в запросе участвует несколько экземпляров
одной и той же таблицы (например, в рекурсивной связи, когда таблица связывается сама
с собой по разным полям) без псевдонима просто не обойтись.
Предложение From служит также и для того, чтобы задать временные (т.е. суще-
ствующие только в рамках данного запроса) связи между таблицами. Простейший вид
связи между таблицами, указанными в предложении From, — отсутствие каких-либо
связей. Такой вид связи называется декартовым (Cartesian). Ниже приведен пример
декартовой связи:
Select * From Items, Storage;
164 Borland C++ Builder 6. Разработка приложений баз данных
цы2, будут содержать значения NULL. Оператор Right Outer (правое внешнее объедине-
ние) решает противоположную задачу. Кроме записей, отобранных на основании внутрен-
него объединения, в итоговую выборку попадут также и все остальные записи таблицы
имя_таблицы2. И, наконец, оператор Full Outer (полное внешнее объединение) к записям,
отобранным на основании внутреннего объединения, добавляет также и остальные записи
обеих таблиц. Ключевое слово Outer — не обязательно, и его можно не указывать.
Предложение Where
Предложение Where служит для задания условий отбора записей из таблиц, указанных
в предложении From. Здесь также можно задать связи между таблицами. Предложение
Where — необязательное, и если его нет, то отбираются все записи. В использовании Where
есть небольшая тонкость, о которой следует упомянуть. Указанные в нем условия применя-
ются для отбора записей до того, как они подвергнутся обработке (группировке). Для отбора
записей после группировки используется другая конструкция — предложение Having.
Чтобы ограничить отбираемые записи, нужно вслед за ключевым словом Where указать
один или несколько операторов отношений. Оператор отношения имеет следующий вид:
операнд! операция_отношения операнд!
Элемент операция ^отношения должен содержать один из знаков операций сравнения
(=, >, < и т.д.) или одно из обозначений операций отношения (Between, Exists, In, Like, Is
Null, Some, Any, All). В качестве операндов могут использоваться арифметические или ло-
гические выражения, содержащие имена полей, константы и функции SQL. Подробнее о
функциях SQL будет сказано далее. Операторы отношения могут объединяться в логиче-
ское выражение при помощи логических операторов And, Or и Not.
Следует сказать несколько слов об операциях отношения. Кроме привычных операций
сравнения к этой категории относятся еще восемь операций, упомянутых в предыдущем
абзаце. Кратко расмотрим каждую из них. Операция Between используется для проверки
вхождения значения в указанный диапазон и имееет следующий синтаксис:
значение! [Not] Between значение2 And значениеЗ
Если значение! принадлежит диапазону, границы которого — значение! и значениеЗ, то
значением операции Between будет true. Иначе операция Between будет иметь значение^а/^е.
Картина меняется на противоположную, если использовать необязательное ключевое слово
Not. Чаще всего операция Between используется для проверки значений даты и времени:
Birthday Between "01.01.01" And "01.06.01"
Операция Exists связана с применением в предложении Where подчиненных запросов.
Используется эта операция следующим образом:
Exists (подчиненный_запрос) .
Элемент подчиненный_запрос — это строка запроса на выборку. Если подчиненный запрос
возвращает непустой набор записей, то операция Exists возвращает значение true. Чаще всего
подчиненный запрос обращается к таблице, не указанной в предложении From основного за-
проса. В подчиненном запросе можно использовать имена полей из основного. Например:
Select I.Item, I.Category, I.ItemNo, I.Description, I."Index", I.Price
From Items I
Where E x i s t s (Select Item FROM Storage S Where (5.Item = I.ItemNo)
And (S.Qty > 0))
166 Borland C++ Builder 6. Разработка приложений баз данных
Ранее уже упоминалось о том, что предложение Where может использоваться для за-
дания связи между таблицами. Ниже приведены два оператора SQL, выполняющих одну
и ту же задачу, хотя и разными путями.
168 Borland C++ Builder 6. Разработка приложений баз данных
Предложение Group By
Предложение Group By служит для объединения нескольких строк набора данны
в одну при помощи групповых операций. Вслед за ключевым словом Group By указывг
ется список полей, разделенных запятой. Все поля, указанные в предложении Group В)
должны присутствовать также и в предложении Select. Все записи, имеющие для этих пс
лей одинаковые значения, объединяются в одну запись. Для остальных полей, указанны
в предложении Select, должны быть указаны групповые операции (агрегатные функции'
К числу агрегатных функций в локальном SQL относятся:
Q Avg — соответствующее поле сгруппированной записи будет содержать среднее ариф
метическое всех значений поля, просчитанное для всех записей, входящих в даннуи
группировку.
Q Count— соответствующее поле будет содержать количество записей, вошедши;
в сгруппированную запись.
О Max, Min — в результате группировки поле будет содержать максимальное (минималь
ное) значение поля, рассчитанное для всех записей, вошедших в группировку.
Q Sum — значения всех полей, входящих в группировку записей, будут суммированы.
Ниже приведен простой пример оператора SQL-запроса, используещего предложение
Group By.
Select Category .Category, Sum(Storage.Qty*Items.Price) As Cost
From Category Inner Join Items
On (Category.CategoryNo = Items.Category)
Inner Join Storage On (Items.ItemNo = Storage.Item)
Group By Category.Category
В этом примере оператор SQL возвращает стоимость товаров каждой категории, име-
ющихся в данный момент на складе. Для этого используется внутреннее объединение
таблиц Items, Storage и Category. Группирование производится по полю Category одно-
именной таблицы. Кроме того, в итоговую выборку попадут значения вычисляемого поля
Cost (стоимость), получаемые путем суммирования произведений полей Qty (количество)
и Price (цена) для всех записей, входящих в группировку.
Предложение Having
Необязательное предложение Having предназначено для того, чтобы указать усло-
вия отбора, применяемые, когда над набором данных выполнены все операции груп-
пировки. Предложение Having можно указывать только после предложения Group By.
Глава 3. Использование механизма ВОЕ (продолжение) 169
Предложение Order By
Предложение Order By — тоже необязательное — применяется для задания сорти-
ровки набора данных, возвращаемого запросом. Оно используется независимо от пред-
ложения Group By. Вслед за ключевым словом Order By следует указать перечень полей
набора данных, по значениям которых будет произведена сортировка. Если после имени
поля указать ключевое слово Asc, то по значениям поля будет произведена сортировка по
возрастанию. Ключевое слово Desc, указанное вслед за полем, задает сортировку по убы-
ванию. Если порядок сортировки не указан, подразумевается сортировка по возрастанию.
Все поля, указанные в предложении Order By (среди них и вычисляемые), должны быть
указаны и в предложении Select.
Ниже приведен простой пример использования предложения Order By.
Select Category.Category, Sum(Storage.Qty*Items.Price) As Cost
From Category Inner Join Items On (Category.CategoryNo = Items.Category)
Inner Join Storage On (Items.ItemNo = Storage.Item)
Group By Category.Category
Order By Cost Desc
В этом примере запрос возвращает итоговый набор данных, содержащий наимено-
вания категорий товаров, а также стоимость товаров данной категории, хранящихся на
складе. Набор данных будет отсортирован по стоимости товаров каждой категории по
убыванию.
оператор Select n
/
Объединенный набор данных формируется так: в конец первого набора данных до-
бавляется второй набор, затем в конец второго набора — третий, и т. д. По умолчанию,
в объединенном наборе данных совпадающие записи группируются в одну запись. Иногда
это может привести к неправильным результатам. Например, при формировании итогово-
го отчета о продажах предприятия можно использовать оператор Union для объединения
в одну выборку сведений о продажах в различных торговых точках. Потом, сгруппировав
полученный таким образом набор данных, можно получить требуемый итоговый отчет.
Однако если в отдельных наборах данных будет содержаться несколько одинаковых строк
(т.е. в нескольких торговых точках предприятия было продано одинаковое количество
какого-либо товара на одну и ту же сумму), то в объединенную выборку попадет только
одна такая строка. В результате, итоговый отчет о продажах после группировки и сумми-
рования будет содержать ошибочную информацию.
Избежать подобных трудноуловимых ошибок можно при помощи ключевого слова
АИ, следующего за ключевым словом Union. В этом случае в итоговую объединенную
выборку попадут все без исключения строки. Несмотря на то, что ключевое слово All
в операторе объединения не обязательное, во избежание непредсказуемых ошибок есть
смысл использовать его всегда.
Запросы с параметрами
Вместо значений, используемых в предложениях Where (в условных выражениях) и Set
(в выражениях обновления) операторов SQL, можно применять параметры. Параметр со-
стоит из символа двоеточия (:) и следующего за ним идентификатора, например:
Select Item, Category, Price From Items
Where Category = :CategoryNo
Передавая оператору SQL разные значения параметра CategoryNo, можно получать
разные выборки. Более подробно вопрос об использовании параметров будет рассмотрен
далее в этой главе.
точки, необходимо указать имя таблицы (в том виде, в каком оно было упомянуто поел
ключевых слов Create Table). Этот же синтаксис используется и для определения поле!
чьи имена содержат пробелы (или символы, не являющиеся алфавитно-цифровыми).
Таблица 3.3. Типы данных, поддерживаемые локальным SQL
Float(s, p) — 11 —
Задать для создаваемой таблицы первичный ключ можно также при помощи конструк-
ции Constraint. Вслед за ключевым словом Constraint нужно указать имя первичного
ключа, а затем конструкцию Primary Key. Для таблиц Paradox и dBASE имя первичного
ключа, отличное от задаваемого сервером по умолчанию, задать нельзя. Несмотря на это,
в конструкции Constraint все равно нужно указывать какой-нибудь идентификатор — он
ни на что влиять не будет.
Замечание
Нельзя создать таблицу, если в базе данных уже существует таблица с таким именем.
В этом случае, прежде чем запускать на выполнение запрос на создание таблицы, следу-
ет убедиться, что вы задали уникальное (в рамках текущей базы данных) имя таблицы.
Сделать это можно, например, при помощи свойства Exists компонента ТТаЬ/е. Можно
также организовать перехват исключительной ситуации, которая возникает при попытке
создать таблицу с уже существующим в базе данных именем.
Глава 3. Использование механизма ВОЕ (продолжение) 175
скобках после имени таблицы. В частности, таблицы Paradox могут содержать составной
индекс. Однако для таблиц dBASE можно указать одно имя поля, на основе которого i
будет создан вторичный индекс.
Если вторичный индекс должен быть уникален (т.е. набор полей, составляющих ин-
декс, не должен содержать совпадающих наборов значений), нужно указать ключевое
слово Unique. При помощи ключевых слов Asc (Ascending) и Desc (Descending) можнс
задать порядок сортировки для индекса. Если ни одно из этих ключевых слов не указано
подразумевается сортировка по возрастанию (Asc).
Ниже приведен пример создания в таблице Employee составного индекса NameNDX
основанного на значениях полей LName, FName и MName:
Create Index NameNDX On Employee (LName, FName, MName).
Как только нужная база данных указана, из комбинированного списка Table можно вы
брать одну из доступных таблиц. В результате на панели таблиц появится соответствую
щее окошко, изображающее таблицу. Заголовок этого окошка содержит название таблицы
а список — перечень всех ее полей. Если на панели таблиц уже есть таблица с таким ж^
названием, то к имени новой через подчеркивание (_) добавляется порядковый номер
Например, ltems_1 (см. рис. 3.25). Справа от заголовка окошка таблицы расположен!
кнопка, при помощи которой можно свернуть окошко в строку заголовка и развернут)
снова. Контекстное меню окошка таблицы содержит два пункта: Remove Table и Edit Tabh
Alias. Первый пункт предназначен для удаления таблицы из панели таблиц, а при помоцц
второго можно задать для таблицы новое имя (псевдоним).
Точно так же на панель таблиц можно добавить любое количество таблиц выбранное
базы данных. Более того, в комбинированном списке Database можно выбрать другук
базу данных и добавить любые таблицы из нее на панель таблиц. На рис. 3.25 изображено
окно SQL Builder, на панели таблиц которого содержится таблица Items из базы данных
MyData в формате Paradox и таблица Items из базы данных IBLocal в формате InterBase.
Как только на панели таблиц появится хотя бы одна таблица, в нижней части рабочей
области SQL Builder отобразится еще одна панель — панель запроса. Она содержит шесть
вкладок: Criteria, Selection, Grouping, Group Criteria, Sorting и Joins. Смысл этих вкладок
будет пояснен далее.
Между таблицами, расположенными на панели таблиц, можно устанавливать времен-
ные связи (т. е. действующие только в рамках данного запроса). Сделать это можно не-
сколькими способами. Простейший из них — перетащить поле одной из таблиц на поле
другой. На панели таблиц между связанными таблицами отобразится линия связи. Еще
один способ организации связей между таблицами предлагает вкладка Joins панели запро-
са. Она предоставляет средства, позволяющие организовать связь между таблицами более
гибко. В частности, вместо знака равенства между полями таблицы можно выбрать любую
другую операцию сравнения.
•••ВШИ
нии Having, Сортировку записей можно задать при помощи вкладки Sorting.
После того как вы сформируете запрос при помощи SQL Builder, его можно закрыть.
Вам будет предложено три варианта: сохранить запрос, отменить все изменения или от-
менить закрытие SQL Builder. Если выбрать первый вариант, все установки, сделанные
в SQL Builder, будут сохранены в свойстве SQL объекта TQuery.
Попытаться выполнить созданный запрос можно в любой момент. Для этого служит
пункт Run SQL меню Query (или кнопка с изображением молнии на панели инструмен-
тов). Точно так же, в любой момент конструирования запроса вы можете прибегнуть
к пункту Show SQL того же пункта меню (кнопка SQL панели инструментов), чтобы по-
смотреть строку оператора SQL, сформированную для вас SQL ВиШег'ом. При выборе это-
го пункта меню, на экране будет отображено окно SQL Query Text Entry с текстом текущего
оператора SQL (см. рис. 3.26). При помощи этого окна можно не только ознакомиться со
сформированным текстом оператора SQL, но и внести в него изменения.
•
«Живые» запросы и свойство RequestLive
Набор данных, возвращаемый объектом TQuery, по умолчанию не обновляем. Это
означает, что нельзя добавлять или удалять записи, а также нельзя модифицировать
значения полей набора данных. Представьте ситуацию, когда на форме размещена сетка
(компонент TDBGrid), подключенная к объекту DataSourcel (компонент TDataSource).
Объект DataSourcel можно подключить или к таблице (ТТаЫе), или к запросу (TQuery),
которые также размещены на форме приложения. Предположим также, что объект ТТаЫе
подключен к таблице Items базы данных MyData, а в свойстве SQL объекта TQuery, также
подключенного к базе данных MyData, содержится следующий оператор:
Select * From Items;
В этом случае сетка TDBGrid будет отображать одну и ту же информацию, независимо
от того, к какому из наборов данных сетка подключена. Однако в случае подключения
к объекту ТТаЫе набор данных (и, соответственно, нижележащую таблицу Items) можно
редактировать, модифицируя ее данные, а также добавляя или удаляя записи. С другой
стороны, если сетка (точнее, объект DataSourcel) будет подключена к объекту TQuery,
модифицировать данные таблицы Items не удастся.
Связано это с тем, что набор данных, возвращаемый объектом ТТаЫе, всегда обновля-
ем. Т.е. если нет никаких ограничений (свойство ReadOnly имеет значение false и таблица
не открыта в эксклюзивном режиме другим пользователем), набор данных ТТаЫе можно
модифицировать. Объект TQuery может вообще не возвращать набор данных. Например,
любой выполняемый запрос или запрос DDL набор данных не возвращает. Запрос на
объединение (Union) набор данных возвращает, но этот набор — не обновляем. Только
запрос на выборку (Select) может возвратить обновляемый набор данных.
Из-за того, что свойство SQL объекта TQuery может содержать строку любого опе-
ратора SQL, этот объект по умолчанию не допускает редактирование данных. Для этого
служит свойство RequestLive, объявленное в классе TQuery. По умолчанию это свойство
имеет значение false. Это гарантирует, что любой набор данных, возвращаемый объектом
TQuery, будет недоступен для модификации, даже если этот набор данных обновляем.
Если вы уверены, что набор данных обновляем, то чтобы предоставить возможность его
редактировать, необходимо задать для свойства RequestLive значение true. Это означает,
что от сервера БД (в данном случае от BDE) будет затребован обновляемый набор данных.
180 Borland C++ Builder 6. Разработка приложений баз данных
Использование параметров
Если свойство SQL объекта TQuery содержит текст параметризованного запроса (т.е.
запроса с параметрами), то прежде чем этот запрос использовать (открыть при помощи
метода Open или свойства Active, или запустить на выполнение при помощи метода
RunSQL), нужно задать для всех параметров подходящие значения. Сделать это можно
двумя способами: при помощи свойства DataSource или свойства-коллекции Params.
Свойство DataSource может содержать ссылку на объект типа TDataSource, подклю-
ченный к другому набору данных (ТТаЫе или TQuery). Если этот набор данных содержит
поля, имена которых совпадают с именами параметров, то текущие значения этих полей
будут автоматически использованы в качестве значений параметров.
Глава 3. Использование механизма ВОЕ (продолжение) 181
jU
Queryl->Prepare();
Queryl->0pen();
Вызывать метод Prepare перед открытием набора данных не обязательно, хотя и pt
комендуется. При вызове этого метода BDE или сервер БД выполняет некоторые предвг
рительные действия, в частности распределяет необходиме ресурсы. Если метод Ргераг
не вызывать, он будет вызван неявно. В паре с этим методом иногда используется мето,
UnPrepare— он выполняет обратные действия, освобождая выделенные для запрос
ресурсы. Использование этих методов может немного оптимизировать выполнение за
проса, особенно если запрос вызывается очень часто (за счет удаления лишних операцш
распределения/освобождения ресурсов). Определить, подготовлен ли запрос к выполне
нию, можно при помощи логического свойства Prepared.
Выполняемые запросы
К выполняемым относятся запросы на вставку, удаление и модификацию записей
набора данных, а также все запросы DDL. Попытка задать для свойства Active объекта
TQuery значение true (или вызвать метод Open), если его свойство SQL содержит строку
выполняемого SQL-оператора, вызовет генерацию исключения. Заставить выполняться
такой оператор можно при помощи метода RunSQL:
Queryl->RunSQL();
После того как выполнение оператора будет завершено, можно узнать, сколько записей
подверглось действию запроса (т.е. было удалено, добавлено или модифицировано). Для
этого нужно проверить свойство RowsAffected. Значение 0 предполагает, что ни одна из
записей набора данных не подверглась изменениям. Если при выполнении запроса про-
изошла ошибка, то свойство RowsAffected вернет значение -1.
Компонент TUpdateSQL
Есть одна лазейка, которая позволяет модифицировать записи таблицы (или таблиц),
полагаемой в основу необновляемого запроса (набора данных только для чтения). Для
этого понадобится режим кэширования изменений и компонент TUpdateSQL. Подход
этот прост, как и все гениальное; в основе его — особенности режима кэширования из-
менений, при котором все изменения в наборе данных сохраняются на локальном диске,
не затрагивая до поры сам набор данных. Все изменения набора данных, кэшированные
на локальном диске, все равно не могут вступить в силу, если набор данных предназначен
только для чтения. Более того, набор данных будет невозможно редактировать без помощи
объекта TUpdateSQL, даже если режим кэширования изменений включен. С другой сто-
Глава 3. Использование механизма ВОЕ (продолжение)
Opto» .SOL
Category - :Cetegory.
,:,:;.
Description * Description
Can
ColegoryNo • :OLD_CategcryNo
~1
Category и Description. Нажмите кнопку Generate SQL. После этого можно переходить на
вкладку SQL.
Пришло время сказать несколько слов о компоненте TUpdateSQL. Основные его
свойства — это три строки выполняемого SQL-оператора: DeleteSQL, InsertSQL и Modi-
fySQL. Каждая из них представляет запрос на удаление, вставку и модификацию записей
соответственно. Каждый из этих операторов будет вызываться для каждой записи в зави-
симости от примененного к ней действия (удаление, вставка, модификация). Как только
вы нажмете кнопку Generate SQL на вкладке Options редактора компонента TUpdateSQL,
будут сгенерированы все три строки операторов SQL. В этом вы можете убедиться, пере-
ключившись на вкладку SQL.
Вкладка SQL (соответствующая BDE-ориентированому набору данных) содержит три
переключателя: Modify, Insert и Delete (см. рис. 3.29). Выбирая их поочередно, вы сможете
ознакомиться с операторами SQL, сформированными для каждого из действий. Глядя на
эти операторы, вы можете убедиться в том, что все они параметризованы. Все значения,
введенные в поля записи, имеют вид :ИмяПоля. Именно на эти значения будут заменяться
текущие значения модифицированных полей. Параметры, представляющие предыдущие
значения полей (до модификации), кроме двоеточия имеют префикс OLD_. Чаще всего
такие параметры используются для идентификации записей, которые должны подверг-
нуться модификации.
В нашем примере строки операторов SQL должны содержать следующие значения:
//Modify
Update Category
Set
Category = :Category,
Description = description
Where
CategoryNo = :OLD_CategoryNo
//Insert
Insert into Category
(Category, Description)
Values
(:Category, description)
//Delete
Delete from Category
Where
CategoryNo = :OLD_CategoryNo
Чтобы придать приложению законченный вид, добавьте в верхнюю часть формы
объект TDBNavigator, подключив его к объекту DataSourcel, а также две кнопки TButton.
Для обработчика нажатия кнопки с подписью Apply Updates укажите следующий текст:
Queryl->ApplyUpdates();
Queryl->CommitUpdates();
Для кнопки Cancel Updates в соответствующем обработчике укажите только одну
строку:
Queryl->CancelUpdates() ;
186 Borland C++ Builder 6. Разработка приложений баз данных
Queryl->CommitUpdatesQ ;
break; }
case IDCANCEL:
{ CanClose=false; break; }
case IDNO:
{ Queryl->CancelUpdates(); break; }
Компонент TBatchMove
Компонент TBatchMove — непосредственный наследник класса TComponent. Объекты
типа TBatchMove позволяют выполнять групповые операции над записями набора дан-
ных. С их помощью можно:
О скопировать данные из исходного набора данных в таблицу;
О добавить записи исходного набора данных в конец целевой таблицы;
Q обновить записи таблицы на основании записей исходного набора данных;
Q удалить записи таблицы на основании их совпадения с исходным набором данных.
Основные свойства компонента TBatchMove — это Source и Destination. Они объявле-
ны следующим образом:
property TBDEDataSet* Source = {read=FSource, write=SetSource};
property liable* Destination = {read=FDestination, write=FDestination};
Обратите внимание на то, что свойство Destination может ссылаться только на таблицу
(компонент ТТаЫе), в то время как свойство Source может содержать ссылку на один из
наследников класса TBDEDataSet (ТТаЫе, TQuery, TStoredProc). Это означает, что моди-
190 Borland C++ Builder 6. Разработка приложений баз данных
фицировать можно только записи таблицы, в то время как источником обновления може!
быть таблица, запрос или хранимая процедура.
Еще одно важное свойство компонента TBatchMove — свойство Mode. С его помощьк
можно задать действия, которые будут выполнены с набором данных (таблицей), указан
ным в качестве значения свойства Destination. Можно указать одно из пяти значений пере-
числимого типа TBatchMode.
Q batAppend—записи из набора данных Source добавляются в конец таблицы Destinaton
Оба набора данных должны иметь одинаковую структуру.
Q batAppendllpdate — записи добавляются в конец таблицы-приемника (Destination).
В таблице-приемнике должен быть определен индекс, на основании которого будет
происходить обновление записей. Записи исходного набора данных (Source), для ко-
торых значения этого индекса у обоих наборов данных совпадают, заменяют соответ-
ствующие записи целевой таблицы (Destination).
G batCopy— структура и записи набора данных— источника (Source) копируются
в целевую таблицу (Destination). Если целевая таблица (т. е. таблица с таким же име-
нем) существует в базе данных, то она уничтожается и создается вновь, имея структу-
ру и данные исходного набора данных.
G batDelete— удаляются все записи целевой таблицы (Destination), совпадающие
с записями исходного набора данных (Source). Откровенно говоря, предлагаемая BDE
реализация очень важной функции группового удаления записей на основании дру-
гого набора данных не вдохновляет. Я понимаю, что нужно следовать выработанным
стандартам и т. п., но, вспоминая возможности, предоставляемые в такой ситуации MS
Access, полагаю, что можно было немного отойти от стандартов, облегчив жизнь про-
граммистов, использующих язык запросов SQL.
Q batUpdate — в целевой таблице (Destination) все записи заменяются на совпадающие
с ними записи исходного набора данных (Source).
Для согласования имен полей исходного набора данных и целевой таблицы можно ис-
пользовать свойство Mappings компонента TBatchMove. Это свойство имеет тип TStrings*
(указатель на объект — набор строк). В этом свойстве можно указать набор строк вида:
ИмяПоляЦелевойТаблицы=ИмяПоляИсходногоНабораДанных
Это свойство можно использовать, если имена соответствующих полей обоих наборов
данных не совпадают.
При выполнении объектом TBatchMove своих функций могут возникать разного рода
ошибочные ситуации. Чтобы реагировать на них, компонент TBatchMove имеет целый ряд
свойств. Ниже кратко описаны все свойства этой группы.
G AbortOnKeyViol— свойство логического типа. Если оно имеет значение true (так по
умолчанию), обновление набора данных прекращается, если может быть нарушена
целостность данных. Если задать для этого свойства значение false, то записи, вызвав-
шие нарушение целостности данных, обновлены не будут. Вместо этого информация
о таких записях будет сохранена в таблице, имя которой должно быть задано свой-
ством KeyViolTableName.
Q AbortOnProblem — еще одно свойство логического типа. Если оно имеет значение true,
то при возникновении проблем из-за несовпадения типов данных наборов данных ис-
Глава 3. Использование механизма ВОЕ (продолжение)
точника и приемника все обновления будут отменены. Значение false предполагает, что
вся информация об ошибках подобного рода будет внесена в таблицу, чье имя указано
в свойстве ProblemTableName.
Q ChangedTableName— это свойство типа AnsiString может содержать имя таблицы
Paradox, куда будут добавлены все записи, которые должны были быть обновлены
в целевой таблице, но почему-то не могли быть сохранены.
Q CommitCount — свойство целого типа. Его значение — это количество записей, кото-
рые должны были быть обновлены.
Q RecordCount — свойство целого типа, обозначающее максимальное количество запи-
сей, которые должны были быть обновлены.
Q Свойства KeyViolTableName и ProblemTableName могут содержать наименования та-
блиц, в которых будут сохранены записи, вызвавшие проблемы при обновлении.
К сожалению, вышеупомянутая ложка меду тоже не без бочки дегтя. Компонент
TBatchMove очень удобен, но следует признать, что с русским языком он не дружит. И уже
достаточно долго: практически ни одна из версий BDE не дает возможности компоненту
TBatchMove нормально работать с русским языком. Конечно, есть ситуации, когда этот
компонент с успехом используется. Но, учитывая все упомянутые трудности, для тех же
целей гораздо удобнее пользоваться компонентом TQuery.
Как всегда, продемонстрируем все удобства и неудобства компонента TBatchMove
на примере. Создайте новое приложение и разместите на его главной форме объекты
TDBGrid, TDataSource, TQuery, TTable и TBatchMove. Оставьте все имена объектов
по умолчанию. Подключите сетку DBGridl к объекту DataSourcel, a DataSourcel -
к объекту Query I. Query 1 подключите к базе данных MyData, задав для его свойства
SQL строку:
Select Items.Item, Category.Category, Storage.Qty, Items.Price,
Storage.Qty*Items.Price As Cost
From Items
Inner Join Category
On (Category.CategoryNo = Items.Category)
Inner Join Storage
On (Items.ItemNo = Storage.Item)
т
Этот оператор SQL, кроме всего прочего, содержит определение вычисляемого поля
Cost. Значение этого поля вычисляется как произведение цены товара на его количество
на складе. Чтобы это поле получало соответствующие значения, в тело обработчика со-
бытия OnCalcFields необходимо ввести:
Query!->FieIdByName("Cost")->AsCurrency =
Query!->FieldByName("Priсе")->AsCurrency *
Query!->FieldByName("Qty")->AsInteger;
Откройте набор данных, задав для свойства Active объекта Queryl значение true.
Попробуем сохранить набор данных, возвращаемый этим объектом, в произвольной таб-
лице. Потом эту таблицу можно использовать в качестве источника данных для дальней-
ших манипуляций.
В заключение, задайте для свойства TableName объекта Table! значение Report (или
любое другое), а для свойства Mode объекта BatchMovel — значение batCopy. Чтобы
192 Borland C++ Builder 6. Разработка приложений баз данных
Резюме
В этой главе было продолжено изучение основ использования BDE. Внимание
уделялось главным образом элементам визуализации и управления данными, а также
приемам использования компонента TQuery. Подробно был рассмотрен компонент
TDBGrid (сетка) — чаще всего используемый в приложениях БД элемент управления.
7 Зак. 319
194 Borland C++ Builder 6. Разработка приложений баз данных
П
ри клиент-серверной организации приложений база данных управляется специ-
альной программой (называемой СУБД, или сервером БД), в то время как клиент-
ское приложение может воздействовать на БД и содержащиеся в ней объекты или
информацию, посылая серверу БД запросы. При помощи этих запросов можно создать
базу данных, таблицы, индексы, а также другие объекты, допускаемые форматом кон-
кретной БД. Кроме того, можно получать необходимые данные из БД и воздействовать на
данные, хранящиеся в таблицах. Сервер БД вместе с базой данных обычно расположены
на удаленном сервере, однако они могут находиться и на сетевом или локальном ком-
пьютере. В зависимости от этого клиентское приложение может использовать для связи
с сервером БД локальную сеть или удаленное подключение.
Borland C++ предоставляет возможность создавать клиентские приложения любого
уровня, которые могут подключаться к серверам баз данных в формате Interbase, Informix,
Microsoft SQL Server, DB2, MySQL и Oracle, а при наличии нормально работающего драй-
вера ODBC — и к БД любого другого типа. В качестве механизма подключения можно
использовать как ВОЕ, который описывался в предыдущих двух главах, так и специально
разработанные для БД форматов SQL механизмы dbExpress, IBExpress и ADO (dbGo).
В предлагаемой вашему вниманию главе рассматриваются приемы создания клиент-
ских приложений БД. Кроме того, на примере БД типа Interbase описаны особенности БД
формата SQL. Interbase в этой главе используется по нескольким причинам. Во-первых,
вся приведенная здесь информация с учетом особенностей, присущих разным форматам,
может быть использована и для БД других серверных типов (т.е. формата SQL). Во-вто-
рых, сервер Interbase поставляется в пакете с C++ Builder (в редакции Enterprise Edition
196 Borland C++ Builder 6. Разработка приложений баз данных
с лицензией на 7 клиентов (хотя в документации фигурирует цифра 5)), так что приобр*
тать его отдельно не надо. Более того, с C++ Builder в редакциях Enterprise и Professions
поставляется локальная версия сервера Interbase (Interbase Desktop Edition). Использ>
Interbase Desktop Edition, можно разрабатывать клиентские приложения только на локал!
ном компьютере, не заботясь о сетевом или удаленном соединении. При этом локальны
сервер представляет все возможности полноценного сервера, т.е. разработанные с его пс
мощью клиентские приложения можно использовать и для связи с удаленным сервере
БД. В дальнейшем мы не будем делать различия между локальной и полной версией cef
вера, применяя термин сервер Interbase.
Начнем, пожалуй, с изложения сведений об Interbase (речь будет идти о версии 6.5).
Работа с Interbase
После инсталляции Interbase и перезапуска Windows в системном лотке (system traj
появится зеленый значок с изображением сервера на фоне треугольника. Это значок при
ложения InterBase Guardian. Это небольшое приложение, в обязанности которого входи
запуск сервера Interbase. Кроме того, InterBase Guardian отслеживает состояние сервер
и при каких-либо сбоях перезагружает его. Пока сервер Interbase не загружен, подклю
читься к базам данных этого формата не удастся. InterBase Guardian можно выгрузит
из памяти, щелкнув правой клавишей мышки на значке приложения в системном лотк
и выбрав из появившегося контекстного меню пункт Shutdown. Снова загрузить его мож»
или перезапустив компьютер, или запустив на выполнение программу ibguard.exe, кото
рая обычно находится в подкаталоге Bin каталога, в который был установлен Interbase.
Можно обойтись и без InterBase Guardian, сразу загружая в память сервер Interbase. Дш
этого нужно запустить на выполнение программу ibserver.exe, тоже находящуюся в подка
талоге Bin каталога, в который был установлен Interbase. Так можно примерно на треть со
кратить расход оперативной памяти. Если вам не нужны преимущества, предоставляемы!
приложением InterBase Guardian, можно сделать так, чтобы оно не загружалась в память npi
загрузке операционной системы. Проще всего воспользоваться для этого утилитой Сведены
о системе, к которой можно получить доступ при помощи меню Пуск \ Программы
Стандартные \ Служебные. В меню Сервис этого приложения следует выбрать Программе
настройки системы. В открывшемся окне нужно перейти на вкладку Автозагрузка и снят!
пометку с флажка слева от наименования InterBase Guardian. Точно так же можно восста
новить «статус кво». Снова установив пометку упомянутого флажка, можно добиться того
чтобы InterBase Guardian загружался в память при загрузке операционной системы. Следует
лишь заметить, что если не пользоваться InterBase Guardian, придется самостоятельно от-
слеживать состояние сервера Interbase и, при необходимости, перезапускать его.
Замечание
Наверняка вы уже заметили, что иногда употребляется название Interbase, а иног-
да — InterBase. Разница, конечно, небольшая, но, вынужден констатировать, эта пута-
ница возникла из-за сотрудников фирмы Borland. В C++ Builder используется первый
вариант наименования, в самом Interbase - второй. Я просто пытаюсь придерживать-
ся того стиля наименования, который используется в каждом конкретном случае раз-
работчиками этих продуктов.
г
на имени узла верхнего уровня (InterBase Server Name: _ Network Protocol:
Servers). В любом случае на экране будет
отображено диалоговое окно Register Ajias Name:
Server and Connect. Для регистрации
локального сервера нужно выбрать пере- ;);-^rv--. ~
ключатель Local Server (см. рис. 4.2). Тогда Description: ,.-:
вам нужно только указать имя пользователя [Локальный сервер
(User Name), пароль (Password) и нажать W Save Alias Information
кнопку ОК. По умолчанию имя пользо-
вателя— SYSDBA, а пароль— masterkey. г Login Information
Регистр символов пароля имеет значение. Г User Name: SYSDBA
Кроме того, произойдет и подключение
к локальному серверу (Login). Password:
Для регистрации удаленного сер-
вера в окне Register Server and Connect Cancel
нужно выбрать переключатель Remote
Server (см. рис. 4.3). В этом случае будут Рис. 4.2. Регистрация локального сервера
198 Borland C++ Builder 6. Разработка приложений баз данных
доступны элементы Server Name, Network Protocol и Alias Name. В элементе редактировг
ния Server Name можно указать имя сервера, или, при использовании протокола ТСР/И
IP-адрес сервера. Комбинированный список Network Protocol содержит перечень прс
токолов, зарегистрированных в Windows. Чаще всего в этом перечне наименования тре
протоколов — TCP/IP, NetBEUI и SPX. В элементе управления Alias Name нужно указат
имя псевдонима для сервера. Под этим именем сервер и будет представлен в дереве ее
единений.
f§ Register Server and Connect | Register Database and Connect
Page 'Size
Default Character Set listens
SQL Dialect
JTest
| OK I Cancel
I " i '""• ' •
.
Name
g .,,,., ^^ .
Owner
,. 1
Description
B-13 Local Server ШЗ COUNTRY SYSDBA
9-^P Databases Ш CUSTOMER SYSDBA
6"1B employee. gdb СШ DEPARTMENT SYSDBA
;•• -ИЗ Domains Ш EMPLOYEE SYSDBA
:- ТТЛ Ц5РД1
Ш EMPLOYEE_PROJECT
SYSDBA
1-Й Indexes
! (Щ Views
ПБ ITEMS SYSDBA
Ш JOB SYSDBA
; tl^i Stored Procedures
СШ PROJECT SYSDBA
' fx External Functions
% Generators Ш PROJ_DEPT_BUDGET SYSDBA
Exceptions Ш SALARY_HISTORY SYSDBA
{
? Blob Filters
^ Roles
ЕЮ SALES SYSDBA
• Щ Backup
ffl Certificates
•if) Server Log
i|§5 Users
<\ И
1 '1 1
Рис. 4.6. Список объектов базы данных employee.gdb
Пользуясь различными пунктами контекстных меню объектов, а также соответствую-
щими пунктами главного меню или действиями, можно получить информацию о свойст-
Глава 4. Клиент-серверные базы данных 201
вах объектов, а также извлекать метаданные (Metadata). Метаданные — это набор опе-
раторов SQL (на языке определения данных DDL), при выполнении которых выбранный
объект будет создан в полном объеме. Команда Extract предназначена для извлечения
метаданных для объекта, а при помощи команды View Metadata можно извлечь метадан-
ные для всей базы данных. Пользуясь метаданными, легко изменить определение объекта
или БД, отредактировав оператор SQL и выполнив группу запросов в пакетном режиме.
На рис. 4.7 приведены метаданные для таблицы Items базы данных employee.gdb, которая
поставляется вместе с C++ Builder.
.1 * (Modified i
Утилита ISQL
Утилита ISQL (сокращение от Interactive SQL) — единственный инструмент, постав
ляемый вместе с Interbase, которым можно воспользоваться для работы с базами данны;
этого формата. К сожалению, удобство и наглядность интерфейса не относятся к числ]
преимуществ ISQL. По сути, единственное назначение ISQL— предоставить возмож
ность выполнять запросы SQL к базам данных Interbase. Существует множество при-
ложений от сторонних разработчиков, имеющих привлекательный и удобный интерфейс
и позволяющих проектировать базы данных при помощи визуальных инструментов. Я не
буду описывать здесь различные программы этого типа, скажу только, что получить ин-
формацию на эту тему можно, например, зайдя на форум разработчиков на официальном
сайте компании Borland (www.borland.com).
Но пока вы не приобретете более удобный инструмент для разработки и сопровожде-
ния БД формата Interbase, вам придется пользоваться ISQL. В данном разделе кратко опи-
сываются приемы работы с этой утилитой. Возможно, привыкнув к ней, вы не захотите
использовать что-то другое.
Ранее ISQL поставлялся как отдельная утилита, а теперь является частью консоли
Interbase. Получить доступ к ISQL можно при помощи пункта Interactive SQL меню Tools
консоли. Если в момент запуска ISQL на левой панели была выбрана какая-нибудь от-
крытая база данных, то все дальнейшие операции будут отнесены именно к этой БД.
В противном случае будет доступен минимальный набор команд, при помощи которых
можно подключиться к существующей БД, создать новую БД, а также загрузить сценарий
(набор операторов SQL). Если сценарий загружен, его можно выполнить (команда Execute
меню Query). Следует заметить, что сценарий должен начинаться с оператора Connect,
осуществляющего подключение к базе данных.
Окно утилиты ISQL разделено по горизонтали на две части (рис. 4.8). В верхней рас-
положена область ввода операторов SQL. В эту область можно вводить операторы на
языке определения данных (DDL) или манипуляции данными (DML). Для выполнения за-
проса нужно воспользоваться кнопкой Execute Query (кнопка с желтой молнией и знаком
вопроса) или пунктом Execute меню Query (клавиатурная комбинация Ctrl + Е). Перед
выполнением запроса можно воспользоваться командой Prepare, чтобы вначале был про-
анализирован текст оператора, а также были подготовлены необходимые ресурсы.
ISQL сохраняет тексты всех введенных и выполненных в рамках текущей сессии
операторов SQL. При помощи кнопок панели инструментов Previous Query (с изобра-
жением желтой стрелки влево) и Next Query (со стрелкой вправо) можно перемещать-
ся по списку выполненных операторов. Это позволит повторно выполнить запрос, не
вводя текст оператора целиком, а лишь слегка его подредактировав. Список операто-
ров SQL, которые использовались во время текущей сессии ISQL, можно сохранить
Глава 4. Клиент-серверные базы данных 203
;| Interactive SQL- employee.qdb
У]
„Edit, Quety Database Transactions Windows Help
Ф? - ?0 - 9? 1 Щ, r» 10
select * from country
Ran j Statistics)
в файле с расширением hst. Для этого предназначен пункт Save SQL History меню
File. Загрузить список операторов из te-файла можно при помощи команды Load SQL
History. He путайте эти команды с командами загрузки/сохранения сценария!
Все запросы в ISQL выполняются в рамках транзакции. Новая транзакция начинается
сразу после открытия ISQL и продолжается до тех пор, пока вы не воспользуетесь ко-
мандами Commit или Rollback (меню Transaction). При помощи первой команды можно
подтвердить все изменения в БД, сделанные в рамках текущей транзакции, а при помощи
второй — отменить. Независимо от того, какой из этих двух команд вы воспользуетесь,
текущая транзакция будет завершена и автоматически начнется новая.
Вся информация о последнем выполненном запросе отображается в нижней части
окна ISQL, которая называется областью вывода результатов SQL. Эта область содержит
три вкладки: Data, Plan и Statistics. На вкладке Data отображаются в табличном виде (в
сетке) результаты выполнения запросов на выборку. На вкладке Plan отображается план
выполнения запроса, избранный оптимизатором. Можно ознакомиться с планом и до вы-
полнения запроса. Для этого нужно нажать кнопку Prepare панели инструментов. Вкладка
Statistics содержит различную информацию, связанную с выполнением запроса: время
выполнения, время подготовки, количество возвращаемых строк и т.д. Эти сведения мо-
гут понадобиться для оптимизации запросов.
Вот, в принципе и все основные сведения о консоли InterBase и утилите ISQL. Более
подробные сведения можно получить из справочной системы Interbase, а также руковод-
ствуясь собственной любознательностью. Главное в использовании этих утилит — знание
принципов построения баз данных Interbase и связанных с этим особенностей языка за-
просов SQL. Изучением этих вопросов мы и займемся в следующих разделах.
2 04 Borland C++ Builder 6. Разработка приложений баз данных
цы, что снижает производительность. В идеальном случае размер страницы должен быть
подобран так, чтобы на ней помещалась самая длинная запись базы данных.
Размер страницы может принимать одно из значений: 1 024,2 048,4 096 или 8 192 бай-
та. Если конструкция PAGEJSIZE не указана, размер страницы принимается равным 4 096
байтам. Чтобы изменить размер страницы существующей базы данных, вначале нужно
сохранить БД при помощи операции Backup, затем восстановить ее при помощи операции
Restore. При выполнении операции Restore необходимо указать новый размер страницы.
Ниже приведен пример создания БД с размером страницы 8 192 байта:
CREATE DATABASE 'D:\Data\MyData.gdb'
USER ' S Y S D B A ' PASSWORD ' m a s t e r k e y '
PAGE_SIZE 8192
Размер первичного файла БД можно ограничить при помощи конструкции LENGTH.
В этом случае необходимо объявить один или несколько вторичных файлов, иначе эта кон-
струкция не будет иметь смысла. Элемент РазмерФайла задает количество страниц, со-
ставляющих первичный файл БД. Когда первичный файл БД достигает указанного разме-
ра, открывается первый из заданных вторичных файлов. Если для него указан предельный
размер, то по его достижении будет открыт следующий вторичный файл. В противном
случае предельный размер файла будет проигнорирован.
Набор вторичных файлов задается при помощи следующей конструкции:
<ВторичныйФайл> = FILE 'СпецификацияФайла'
[<ИнформацияОФайле>]
[<ВторичныйФайл>],
где
<ИнформацияОФайле> = [LENGTH [=] РазмерФайла
{[PAGEfS]] | STARTING [AT [PAGE]] Страница }
Элемент СпецификацияФайла задает путь и имя вторичного файла. Вторичный
файл не обязательно должен располагаться на том же диске, что и первичный. Элемент
ИнформацияОФайле, если он указан, задает предельный размер вторичного файла. Размер
можно указать или в страницах (ключевое слово PAGEfSJ), или в виде номера страницы,
по достижении которой будет открыт указанный вторичный файл (конструкция STARTING
[AT[PAGE]]). Ниже приведен пример использования вторичных файлов.
CREATE DATABASE 'C:\Data\MyData.gdb'
USER 'SYSDBA' PASSWORD 'masterkey'
LENGTH 25000
FILE 'D:\Data\MyDatal.gdb'
FILE 'E:\Data\MyData2.gdb' STARTING AT 25001 LENGTH 25000 PAGES
FILE 'F:\Data\MyData3.gdb'
Этот запрос создает первичный файл MyData.gdb базы данных размером 25 000 стра-
ниц на диске С и вторичный файл MyDatal на диске D. При достижении страницы с номе-
ром 25 001 информация будет сохраняться в файле MyData2, который создается на диске
Е. Предельный размер этого файла — 25 000 страниц. По его достижении сервер Interbase
будет использовать для сохранения информации вторичный файлМу£>агаЗ, который будет
создан на диске F. Размер последнего вторичного файла не ограничен (точнее, ограничен
дисковым пространством или максимальным размером файла БД, который составляет 2
Гб для Windows и 4 Гб для некоторых других платформ).
208 Borland C++ Builder 6. Разработка приложений баз данных
Следует сказать, что управлять тем, какая информация в каком файле будет сохранена
невозможно. Сервер Interbase делает это автоматически, и повлиять на это нельзя.
И последняя конструкция, которая может быть использована в тексте оператора SQI
для создания базы данных, — DEFAULT CHARACTER SET НаборСимволов. При помо
щи этой конструкции можно задать для базы данных набор символов по умолчанию. Oi
определяет, какие символы могут использоваться в значениях типов CHAR, VARCHAb
и BLOB (подтип text), a также задает порядок сравнения по умолчанию при операция>
сортировки. Описание допустимых значений для аргумента НаборСимволов можно найта
в руководстве Data Definition Guide, в главе 13. Для того чтобы информация сохранялась
и извлекалась в точности так, как ее ввели, следует использовать конструкцию DEFAULT
CHARACTER SET NONE.
Добавить описание вторичных файлов в определение существующей БД можно при
помощи оператора ALTER DATABASE:
ALTER {DATABASE | SCHEMA}
ADD <ВторичиыйФайл>;
Конструкция <ВторичныйФайл> описывает набор вторичных файлов для текущей БД;
синтаксис у нее точно такой же, как и у соответствующей конструкции оператора CREATE
DATABASE. Оператор ALTER DATABASE требует эксклюзивного доступа к базе данных.
Выполнить, этот оператор может только создатель БД, пользователь SYSDBA или любой
пользователь, наделенный полномочиями администратора. Ниже приведена пара операто-
ров SQL для создания БД (первичного файла) и добавления двух вторичных файлов:
'Создаем первичный файл БД MyData.gdb
CREATE DATABASE 'D:\Data\MyData.gdb'
USER 'SYSDBA' PASSWORD 'masterkey';
'Добавляем два вторичных файла
ALTER DATABASE
ADD FILE 'E:\Data\MyDatal.gdb' STARTING AT PAGE 25001
ADD FILE 'E:\Data\MyData2.gdb' STARTING AT PAGE 25001
При помощи оператора ALTER DATABASE нельзя резделить первичный файл БД на не-
сколько файлов меньшего размера. Например, если размер первичного файла БД — 8 000
страниц, то использование конструкции
ADD FILE 'E:\Data\MyDatal.gdb' STARTING AT PAGE 5001
приведет к тому, что новый файл будет открыт, начиная со страницы 8 001 (а не 5 001).
Чтобы разделить первичный файл на несколько частей меньшего размера, следует сохра-
нить БД (операция Backup), а затем восстановить ее (операция Restore), указав нужную
информацию о вторичных файлах.
С базой данных может быть связано еще несколько файлов. Это так называемые фай-
лы-тени, или теневые файлы (Shadow). Они содержат в точности ту же информацию, что
и база данных. Любые изменения, вносимые в БД, автоматически отображаются в тене-
вом файле. При неожиданной потере данных (например, вследствие ошибки записи на
диск или при других подобных сбоях) базу данных можно восстановить при помощи
ее теневых файлов. Создать «тень» можно посредством оператора CREATE SHADOW.
Дублирование информации активизируется сразу после создания «тени» и завершается
сразу после ее уничтожения (при помощи оператора DROP SHADOW). Более подробно об
использовании теневых файлов можно узнать в справочной системе Interbase.
Глава 4. Клиент-серверные базы данных 209
Удалить базу данных можно при помощи оператора DROP DATABASE. Этот оператор,
однако, доступен только при использовании утилиты ISQL. После удаления БД удаляются
также и все связанные с ней вторичные и теневые файлы.
Домены (Domains)
Если база данных содержит много таблиц, в которых часто используются одинаковые
определения полей, можно воспользоваться доменами. Домен (Domain) — это глобальное
в рамках данной БД определение поля, которое может применяться в операторах созда-
ния и модификации таблиц вместо типа данных для поля. Это напоминает использование
ключевого слова typedef в языках C/C++. Вы один раз определяете именованный набор
атрибутов, а затем используете его имя при определении полей таблиц.
Создать домен можно при помощи оператора CREATE DOMAIN. Ниже приведен его
полный синтаксис:
CREATE DOMAIN ИмяДомена [AS] <ТипДанных>
[DEFAULT {Константа | NULL | USER}]
[NOT NULL]
[CHECK (<Условие>)]
[COLLATE ПорядокСортировки];
Конструкция ТипДанных задает один из допустимых типов данных для домена. Вслед
за типом данных может следовать объявление массива. Если для домена указан один из
текстовых типов (CHAR, VARCHAR, BLOB), то в конце конструкции ТипДанных можно
указать набор символов (после ключевого слова CHARACTER SET). Ниже приведено не-
сколько примеров создания доменов.
'Домен типа VARCHAR до 15 символов; набор символов - WIN1251
CREATE DOMAIN FIRSTNAME AS VARCHAR(IS)
CHARACTER SET WIN1251;
'Домен типа массив значений VARCHAR до 20 символов
'Индекс массива может меняться от 1 до 3
CREATE DOMAIN EMPLOYEE AS VARCHAR(20)[1:3];
'Домен типа BLOB, подтип TEXT; размер сегмента - 120 байтов
CREATE DOMAIN DESCRIPTION AS BLOB SUB_TYPE TEXT
SEGMENT SIZE 120;
Вслед за ключевым словом DEFA ULT можно указать значение, которое будет присво-
ено полю, если для него не были введены данные. Можно указать константу соответству-
ющего типа, а также одно из специальных значений NULL или USER. Если используется
ключевое слово USER, поле получит значение имени текущего пользователя. В этом слу-
чае необходимо правильно подобрать тип данных и набор символов для поля.
Если указана конструкция NOT NULL, то для поля, ассоциированного с данным доме-
ном, обязательно нужно указать значение. Эта конструкция не должна входить в противо-
речие с конструкцией DEFAULT, если в ней указано ключевое слово NULL.
При помощи конструкции CHECK можно задать ограничения, налагаемые на вводи-
мые значения. Полный синтаксис элемента <Условие> выглядит несколько громоздко,
в реальности все намного проще:
210 Borland C++ Builder 6. Разработка приложений баз данных
<Условие> =
VALUE <оператор> <выражение>
| VALUE [NOT] BETWEEN <выражение> AND <выражение>
| VALUE [NOT] LIKE <выражение> [ESCAPE <выражение>]
| VALUE [NOT] IN (<выражение> [, <выражение> ...])
| VALUE IS [NOT] NULL
| VALUE [NOT] CONTAINING <выражение>.USING CREATE DOMAIN
| VALUE [NOT] STARTING [WITH] <выражение>
| (<Условие>)
| NOT <Условие>
| <Условие> OR <Условие>
| <Условие> AND <Условие>,
где<оператор> = {= | < | > | <= | >= | ! < | ! > | <> | !=}
Нет смысла пояснять каждую из строк приведенного описания конструкции <Условиё>,
с этим несложно разобраться и самому. Существует два ограничения, о которых следует
упомянуть. В конструкции CHECK нельзя ссылаться на другой домен или поле табли-
цы, кроме того, в определении домена может присутствовать только одна конструкция
CHECK. Ниже приведено несколько примеров использования конструкций DEFAULT
и CHECK.
'Домен, описывающий месяц года, определяет диапазон
'допустимости вводимых значений от 1 до 12
'Значение по умолчанию - Я (январь)
CREATE DOMAIN MONTHNO AS SMALLINT
DEFAULT 1
CHECK(VALUE BETWEEN 1 AND 12);
'Домен допускает ввод номера телефона, который начинается с символа 8
CREATE DOMAIN PHONE AS VARCHAR(IS)
CHECK(VALUE STARTING WITH '8');
Работа с таблицами
Прежде чем приступить к созданию таблицы, следует определить все домены, которые
должны использоваться при объявлении ее полей. Непосредственно создание таблицы БД
осуществляется оператором CREATE TABLE:
CREATE TABLE ИмяТаблицы [EXTERNAL [FILE] 'СпецификацияФайла']
(<ОпределениеПоля> [, <ОпределениеПоля> | <0граничение> . . . ] ) ;
Имя таблицы должно быть уникальным для текущей базы данных. Кроме того, не-
обходимо указать хотя бы одно определение поля. Для определения поля используется
следующий синтаксис:
<ОпределениеПоля> = ИмяПоля {ТипДанных |
COMPUTED [BY] (<выражение>) | домен}
[DEFAULT {константа | NULL | USER}]
[NOT NULL] [<ОграниченияДляПоля>]
[COLLATE ПорядокСравнения]
Имя поля должно быть уникальным для данной таблицы. Вслед за ним можно ука-
зать или тип данных, или имя домена. Тип данных, кроме непосредственно имени типа
данных, может включать (если необходимо) размерность для массива, а для текстовых
типов — набор символов, которые могут использоваться в поле. При помощи конструк-
ции DEFAULT можно задать значение по умолчанию для поля, конструкция COLLATE,
если она указана, задает порядок сортировки, а если используются ключевые слова NOT
NULL, поле требует ввода значения. Если в качестве типа данных указан домен, все ха-
рактеристики поля переопределяют соответствующие характеристики домена. Например,
если в определении домена не указаны ключевые слова NOT NULL, а в определении по-
ля — указаны, ввод в поле считается обязательным.
Для определения поля используется практически тот же синтаксис, что и для опреде-
ления домена. Но все же есть несколько существенных различий. В качестве типа дан-
ных для поля можно указать имя домена, в то время как для домена этого делать нельзя.
В конструкции CHECK вместо ключевого слова VALUE нужно использовать имя опреде-
ляемого поля (ключевое слово VALUE вызовет ошибку). При определении нельзя исполь-
зовать ключевое слово AS (это тоже вызовет ошибку). Кроме того, поле можно опреде-
лить как вычисляемое. Для этого в качестве типа данных нужно указать ключевые слова
COMPUTED [BY] (слово BY можно опустить, о чем свидетельствуют квадратные скобки).
Вслед за этим нужно указать выражение, значение которого будет присваиваться полю.
Тип данных для выражения указывать не нужно, сервер Interbase будет определять его
автоматически, присваивая этот же тип и вычисляемому полю. Выражение обязательно
нужно заключать в круглые скобки.
Выражение будет вычисляться всякий раз, когда к полю какой-либо записи потребует-
ся доступ в процессе выполнения программы. В выражении можно использовать имена
полей таблицы, но эти поля должны быть определены в операторе CREATE TABLE ранее.
Ниже приведено несколько примеров создания таблиц.
'Создание доменов, которые будут использованы при создании таблицы
CREATE DOMAIN EmpNo AS INTEGER CHECK(VALUE > 0) NOT NULL;
CREATE DOMAIN FirstName AS VARCHAR(IS) ;
212 Borland C++ Builder 6. Разработка приложений баз данных
Далее, вы можете указать или ключевое слово UNIQUE, или PRIMARY KEY. В первом
случае все значения поля должны быть уникальными, т.е. ввод в это поле значения, уже
имеющегося в нем, вызовет ошибку. Ключевые слова PRIMARY KEY означают, что на
основании поля будет создан первичный ключ. Первичный ключ, основанный на значени-
ях одного или нескольких полей, можно создать на уровне таблицы.
Конструкция <Ограничения> указывается вслед за определением последнего поля
в операторе CREATE TABLE. С ее помощью можно задать первичный и внешние ключи
таблицы, поля с уникальными значениями, а также дополнительные ограничения, налага-
емые на значения полей всей таблицы. Синтаксис этой конструкции следующий:
<0граничения> = [CONSTRAINT ИмяОграничения] <0граничение>
[<0граничение> . . . ]
<0граничение> = {PRIMARY KEY | UNIQUE} (поле [, поле ...])
| FOREIGN KEY (поле [, поле ...])
REFERENCES ДругаяТаблица [(поле [, поле ...])]
[ON DELETE {NO ACTION[CASCADE|SET DEFAULT]SET NULL}]
[ON UPDATE {NO ACTION[CASCADE|SET DEFAULT]SET NULL}]
| CHECK (<Условия>)
Хотя конструкция CONSTRAINT не обязательна, рекомендуется ее использовать, так
как в этом случае можно задать для ограничения какое-нибудь вразумительное имя. Далее
можно указать перечень отдельных элементов ограничения, к которым относятся:
Q PRIMARY КЕУ(поле1, ... , полеЩ. Все поля, указанные в круглых скобках, входят
в первичный индекс.
G UNIQUE(nonel, ... , noneN). Поля, указанные в круглых скобках, должны иметь уни-
кальные значения.
О FOREIGN KEY (поле!, ... , noneN) REFERENCES ДругаяТаблица [(nonel, ... , noneN)].
При помощи этой конструкции можно задать связь между таблицами. Поля, указанные
в круглых скобках вслед за ключевыми словами FOREIGN KEY, входят в состав внеш-
него ключа. Внешний ключ ссылается на первичный ключ таблицы ДругаяТаблица,
имя которой указано после ключевого слова REFERENCES. Такая таблица к этому
моменту уже должна существовать. Далее в круглых скобках могут быть указаны имена
полей таблицы ДругаяТаблица, составляющие ее первичный ключ. Если имена полей
внешнего и первичного ключей совпадают, поля первичного ключа можно не указывать.
В рамках этой конструкции можно задать действия, которые будут выполняться со
значениями внешнего ключа, если соответствующие значения первичного ключа связан-
ной таблицы будут изменены (ON UPDATE) или удалены (ON DELETE). Набор действий
в обоих случаях одинаков:
Q никакие действия предприняты не будут (NO ACTION);
Q в случае ON UPDATE значения полей, составляющих внешний ключ, будут изменены
в соответствии с новым значением первичного ключа (CASCADE);
О в случае UNDELETE будут удалены все записи, внешний ключ которых содержит зна-
чение, совпадающее со значением удаленного первичного ключа (CASCADE);
Q поля, составляющие внешний ключ, получат свои значения по умолчанию;
О поля, составляющие внешний ключ, получат значения NULL.
214 Borland C++ Builder 6. Разработка приложений баз данных
'Таблица-справочник Category
CREATE TABLE Category
(
CategoryNo PKeyField,
Category VARCHAR(25) NOT NULL,
Description VARCHAR(50),
PRIMARY KEY (CategoryNo),
UNIQUE (Category)
);
'Таблица Items имеет основанный на поле Category внешний ключ,
'предназначенный для организации связи с первичным ключом
'CategoryNo таблицы Category
CREATE TABLE Items
(
ItemNo PKeyField PRIMARY KEY,
Item VARCHAR(50) NOT NULL,
Category FKeyField,
Description VARCHAR(100),
Itemlndex VARCHAR(S) NOT NULL,
Price NUMERIC(8,2) NOT NULL,
CONSTRAINT ItemsToCategory
FOREIGN KEY(Category) REFERENCES Category(CategoryNo)
ON DELETE CASCADE
ON UPDATE CASCADE
);
Можно создать таблицу, которая будет храниться не в базе данных Interbase, а в отдель-
ном внешнем файле. Для этого следует воспользоваться конструкцией:
EXTERNAL FILE 'СпецификацияФайла'
Эта конструкция указывается вслед за именем таблицы в операторе CREATE TABLE.
СпецификацияФайла — имя и путь к текстовому файлу, в котором информация должна
храниться в табличном виде, т.е. все записи должны быть одинаковой длины. В качестве
типа данных для всех полей лучше всего выбрать CHAR (строка фиксированной длины),
кроме того, необходимо предусмотреть одно дополнительное поле для символа конца
строки (EOL). В зависимости от операционной системы это может быть один или два
Глава 4. Клиент-серверные базы данных 215
символа. Ниже приведен тестовый пример создания таблицы, хранящей список номеров
и названий месяцев года:
CREATE TABLE MonList EXTERNAL FILE ' D : \ D a t a \ M o n L i s t . t x t '
(
Nmon CHAR(2),
NameMon CHAR(12),
EOL CHAR(2)
);
Если к моменту выполнения приведенного выше оператора файла MonList.txt в указа-
нном каталоге нет, он будет создан (нулевой длины). В противном случае никаких изме-
нений в файле не произойдет. Редактировать информацию во внешнем файле можно как
в рамках сервера Interbase, так и любыми другими средствами (например, при помощи
любого текстового редактора). Пользуясь услугами сервера Interbase, можно скопировать
информацию из внешнего файла в таблицу (внутреннюю) Interbase и наоборот. Таким
образом можно перемещать информацию из текстового файла в БД и из БД — в текс-
товый файл. Следует заметить, что запросы на выборку и вставку записей используются
для внешней таблицы точно так же, как и для внутренней. Однако запросы на удаление
и обновление к внешней таблице неприменимы. Кроме того, все изменения внешней
таблицы немедленно вступают в силу, так как применяются вне каких-либо транзакций.
Отказаться от изменений будет невозможно.
Вместо внешних таблиц безопасней и удобней пользоваться клиентскими наборами
данных.
Модифицировать структуру существующей таблицы можно посредством опера-
тора ALTER TABLE. С его помощью можно добавить новое поле в таблицу, удалить
определение поля из таблицы, удалить ограничения на целостность данных, связанные
с отдельным полем или таблицей в целом, изменить имя поля, тип его данных и положе-
ние в списке полей таблицы. Перед модификацией структуры таблицы нужно убедиться,
что у вас есть достаточные для этого права, и что вы сохранили все данные, хранящиеся
в таблице. После этого вы должны удалить все ограничения, связанные с модифициру-
емой таблицей. Особенно важен этап сохранения данных таблицы: после применения
оператора ALTER TABLE данйые будут утеряны. Поэтому вначале нужно сохранить дан-
ные модифицируемой таблицы во временной таблице, а после модификации структуры
скопировать их обратно.
Ниже приведен синтаксис оператора ALTER TABLE:
A L T E R TABLE ИмяТаблицы < 0 п е р а ц и я > [, <0перация> . . . ] ;
Конструкция <Операция> может задавать одну из следующих операций:
Q ADD <Ограничение> — добавляет ограничение на уровне таблицы. Синтаксис огра-
ничения тот же, что и при создании таблицы.
CJ ALTER [COLUMN] ИмяПоля <НовоеОпределениеПоля> — изменяет определение по-
ля ИмяПоля. Конструкция <НовоеОпределениеПоля> выглядит так:
<НовоеОпределениеПоля> = {ТО НовоеИмяПоля
| TYPE НовыйТипДанных
| POSITION НоваяПозиция}
Q DROP ИмяПоля — удаляет поле из структуры таблицы.
216 Borland C++ Builder 6. Разработка приложений баз данных
Использование индексов
В предыдущих главах, посвященных локальным базам данных, рассказывалось о том,
зачем нужны индексы, в каких случаях их нужно применять, а в каких — нет. Большей
частью эта информация верна и для клиент-серверных БД. Индексы позволяют значи-
тельно ускорить извлечение информации из таблиц, сортировку, перемещение по набору
данных. С другой стороны, большое количество индексов, определенных для таблицы,
приводит к излишней потере времени при вставке, модификации или удалении записей.
Это связано с необходимостью обновлять большой объем информации об индексах.
Нелишне еще раз напомнить о тех ситуациях, когда использовать индексы вполне
уместно:
G если поле очень часто применяется в критериях для поиска, для него необходимо
определить индекс;
О поле или группа полей служит внешним ключом. Во многих форматах БД для внешне-
го ключа автоматически создается индекс;
Q поле часто используется в предложении ORDER BY для сортировки данных.
Напротив, не нужно создавать индекс для полей, которые:
О редко используются в критериях поиска;
О подвержены частой модификации;
Q имеют небольшое количество возможных значений.
Для таблицы Interbase можно создать до 64 индексов. Следует иметь в виду, что сервер
Interbase автоматически создает индексы для полей (или группы полей), объявленных как
первичный или внешний ключ, а также для полей, объявленных уникальными (с исполь-
зованием ключевого слова UNIQUE).
Создать новый индекс для таблицы можно при помощи оператора CREATE INDEX.
Полный синтаксис этого оператора таков:
CREATE [UNIQUE] [ASCfENDING] | DESCENDING] ] INDEX ИмяИндекса
ON ИмяТаблицы (поле [, поле . . . ] ) ;
В отличие от локальных БД, в базах данных Interbase индексы являются объектами БД,
а не таблицы. При помощи приведенного выше оператора создается индекс ИмяИндекса
Глава 4. Клиент-серверные базы данных 217
для таблицы ИмяТаблицы, основанный на полях, чьи имена перечислены в круглых скоб-
ках. Если использовать ключевое слово UNIQUE, то индекс должен содержать уникальные
(несовпадающие) значения. Кроме того, для индекса можно задать порядок сортировки.
Ключевое слово ASC задает порядок сортировки по возрастанию, DESC— по убыванию.
Если не указано ни одно из этих слов, применяется сортировка по возрастанию (ASC).
В обоих случаях для приличия можно использовать ключевое слово ENDING. Ниже при-
веден пример создания индекса ItemNDX для таблицы Items на основе поля Item:
CREATE UNIQUE ASC INDEX ItemNDX ON I terns(I tern);
Для таблицы Items кроме индекса ItemNDX автоматически будут созданы также индек-
сы на основе первичного (поле ItemNo) и внешнего (поле Category) ключей.
При помощи оператора ALTER INDEX можно (нет, не модифицировать!) активизиро-
вать или деактивизировать указанный индекс. Синтаксис этого оператора следующий:
ALTER INDEX ИмяИндекса {ACTIVE | INACTIVE};
При помощи ключевого слова ACTIVE можно активизировать индекс, а посредством
INACTIVE— деактивизировать. Например, перед тем как вставить или модифицировать
большое количество записей таблицы, можно деактивизировать индекс, а после окон-
чания этого процесса — снова активизировать. Это поможет избежать больших потерь
времени на обновление значений полей, составляющих индекс. Активизация индекса при-
водит к его полной перестройке.
Уничтожить индекс можно при помощи следующего оператора:
DROP INDEX ИмяИндекса;
Посредством этого оператора можно удалить индекс, созданный при помощи опера-
тора CREATE INDEX. Индексы, автоматически созданные сервером Interbase на основе
первичных или внешних ключей, или полей с уникальными значениями, при помощи
оператора DROP INDEX удалить нельзя.
'
Глава 4. Клиент-серверные базы данных 225
END
END
Л
л
SET TERM ;
В этой процедуре используется оператор SELECT, выбирающий данные из связанных
между собой таблиц Items и Storage. Выбранные данные сохраняются в выходных ар-
гументах ItemNo, Item, Qty и Notes, объявленных в конструкции RETURNS. Затем в сос-
тавном операторе конструкции DO проверяется текущее значение поля Qty (количество
товара на складе). Если количество меньше 5, в поле Notes вносится значение 'Срочно
доставить!'. Если количество товара больше 5, но меньше 10, поле Notes получает зна-
чение 'Заказать!'. В остальных случаях поле Notes получает значение " (пустая строка).
Если в качестве элемента визуализации данных для этой процедуры применяется сетка
(TDBGrid), можно воспользоваться ее событием OnDrawColumnCell, чтобы, например,
раскрасить все значения поля Notes в разные цвета (см. главу 3).
Обратите внимание на использование оператора SUSPEND. На самом деле он имеет
решающее значение в данном контексте. Ранее уже упоминалось о том, что оператор
SUSPEND приостанавливает получение данных из источника, передавая управление
хранимой процедуре. Таким образом, в приведенном выше примере оператор SELECT
получает очередную строку данных, помещая значения полей в переменные, указанные
в конструкции INTO. Если над этими переменными нужно произвести некоторые дей-
ствия, а также если необходимо, чтобы хранимая процедура реально возвращала значе-
ния, следует воспользоваться оператором SUSPEND.
Лучше всего особенности использования оператора SUSPEND в разных ситуациях
видны на примерах. Приведенный выше пример хранимой процедуры полностью решает
поставленную перед ним задачу. Если поместить оператор SUSPEND перед ключевым
словом DO, будет возвращен нужный набор данных, однако над значениями поля Notes
никакие действия произведены не будут. Если оператор SUSPEND разместить перед за-
ключающим процедуру оператором END, будет возвращена только последняя строка
набора. И, наконец, если оператор SUSPEND вообще не использовать, никакие данные
процедурой возвращены не будут.
Выполнимые (Executable) хранимые процедуры предназначены для выполнения ка-
ких-либо действий. В принципе, эти действия не обязательно должны быть направлены
на извлечение или модификацию информации таблиц БД. Кроме того, процедуры этого
типа не должны (хотя и могут) возвращать какие-либо данные. Главное их назначение, как
и следует из их названия, — производить необходимые действия. К числу этих действий
можно отнести стандартные операции SQL: вставку, удаление или модификацию записей.
Однако этим джентльменским набором функции хранимых (выполнимых) процедур не
ограничиваются. Хранимые процедуры этого типа можно с успехом использовать в любом
контексте, для выполнения любой задачи.
Ниже приведен пример хранимой процедуры, которую можно использовать вместо
триггера для заполнения поля первичного ключа уникальным значением.
CREATE PROCEDURE Get_Emp_Gen
RETURNS(NumCode INTEGER)
AS
BEGIN
NumCode=GEN_ID(Emp_GEN,l);
END
8 Зак. 319
226 Borland C++ Builder б. Разработка приложений баз данных
Представления (Views)
Клиентам обычно не нужно работать со всеми столбцами и всеми записями таблицы
сразу. Чаще всего необходим доступ к подмножеству записей одной или нескольких свя-
занных между собой таблиц. Например, бухгалтеру для работы необходим перечень до-
кументов, датированных текущим месяцем, а программа типа «Клиент — банк» должна
предоставлять доступ только к документам, которые имеют отношение к данному кли-
енту. Interbase (как и остальные БД формата SQL) имеет в своем составе удобное и наде-
жное средство для решения таких и подобных задач — объекты типа представление
(View). Иногда используются термины вид (дословный перевод с английского языка) или
виртуальная таблица.
С точки зрения пользователя представление выглядит как обычная таблица. Имя пред-
ставления можно применять везде, где обычно используется имя таблицы, — в запросах,
хранимых процедурах и т.д. Представление может возвращать как обновляемый набор
Глава 4. Клиент-серверные базы данных 227
данных, так и необновляемый (набор данных только для чтения). В первом случае пред-
ставление может использоваться для редактирования лежащих в его основе таблиц.
С точки зрения реализации представления отличаются от таблиц. Набор данных,
возвращаемый представлением, — временный и не сохраняется в базе данных. В базе
данных хранится только определение представления. При необходимости на основе это-
го определения формируется временный набор данных, к которому и получает доступ
пользователь. Однако не следует забывать о том, что любые изменения, вносимые в запи-
си обновляемого представления, немедленно отображаются в исходных таблицах. Верно
и обратное — любые изменения, внесенные в исходные таблицы, отображаются в наборе
данных, возвращаемом представлением.
Для ограничения доступа к данным и количества возвращаемых записей можно вос-
пользоваться возможностями, предоставляемыми таблицами и запросами. Однако ис-
пользование представлений имеет ряд преимуществ. Представление может служить осно-
вой (источником данных) для запросов, хранимых процедур или других представлений.
Это немалое превосходство, особенно если учесть, что представление может возвращать
сложный набор данных, основанный на большом количестве связанных между собой
таблиц. Так как представление хранится на стороне сервера, то использование его в каче-
стве источника данных для других объектов может значительно повысить устойчивость
и производительность приложения.
Еще одно преимущество заключается в том, что при помощи представлений можно
организовать доступ клиента только к тем данным, которые нужны именно ему. Клиенту
не придется просматривать большое количество информации в поисках необходимых
сведений. Кроме того, представления позволяют задать уровень доступа к информации
(чтение, редактирование, удаление, добавление).
Представления можно рассматривать как интерфейс для доступа к данным, хранящимся
в таблицах БД. Администратор БД может изменить модель данных, перераспределив инфор-
мацию между таблицами, добавив новые поля или даже таблицы, участвующие в определе-
нии представления. Подкорректировав нужным образом определение представления, админи-
стратор БД может добиться того, что клиент даже не заметит никаких изменений.
Для создания нового представления в базе данных используется оператор языка опре-
деления данных CREATE VIEW:
CREATE VIEW ИмяПредставления
[(ПолеПредставления [, ПолеПредставления . . . ] ) ]
AS <0neparop5elect> [WITH CHECK OPTION];
Набор данных, возвращаемый представлением, определяется конструкцией
OmpamopSelect. Это обычный оператор SQL на выборку данных. В этом операторе обя-
зательно нужно использовать конструкции SELECT и FROM, можно также ограничить
набор возвращаемых записей при помощи конструкции WHERE. В определении пред-
ставления нельзя использовать конструкцию ORDER BY. Несколько простых операторов
на выборку можно объединить в один набор данных при помощи операции UNION [ALL].
Следует иметь в виду, что создать такие представления в рамках утилиты ISQL не удастся.
Для этого необходимо воспользоваться собственным приложением. Один из существен-
ных недостатков представлений — отсутствие поддержки параметров, что сильно ограни-
чивает область их применения.
После имени представления могут быть указаны новые имена для полей, перечислен-
ных в конструкции SELECT. Это полезно, если в операторе выборки определяются вычис-
228 Borland C++ Builder 6. Разработка приложений баз данных
ляемые поля. Если список полей представления не указан, в этом качестве используют*
имена полей, указанные в конструкции SELECT.
Замечание
В определении представления нельзя использовать в качестве источника данных н;
бор строк, возвращаемых хранимой процедурой.
Исключения (Exceptions)
Исключение — это объект базы данных Interbase, представляющий собой именован-
ное сообщение об ошибке. Исключения используются для отображения сообщения об
ошибке, происшедшей в теле триггера или хранимой процедуры. Кроме того, исключение
может прервать выполнение процедуры и возвратить управление вызвавшему приложению.
Как и остальные объекты БД, исключения хранятся на стороне сервера. Это позволяет
организовать систему обработки ошибок, минимально затрагивая клиентские приложе-
ния. Создать новое исключение можно при помощи оператора CREATE EXCEPTION:
CREATE EXCEPTION ИмяИсключения 'ТекстСообщения';
Текст сообщения может содержать не более 78 символов. Изменить его можно при по-
мощи оператора ALTER EXCEPTION, a удалить исключение из базы данных можно при
помощи оператора DROP EXCEPTION. Однако удалить исключение, задействованное
в каких-либо триггерах и процедурах БД, не удастся. Сначала необходимо удалить или
модифицировать все зависимые от исключения объекты, и только потом можно удалять
само исключение.
Сгенерировать исключение в теле хранимой процедуры или триггера можно при помо-
щи оператора EXCEPTION. Чаще всего этот оператор используется в контексте условного
оператора:
IF (УсловноеВыражение) THEN EXCEPTION ИмяИсключения;
При возбуждении исключения происходят следующие события (если не используется
оператор WHEN... DO):
Q прерывается выполнение процедуры, в которой было возбуждено исключение;
G отменяются все изменения, которые были явно или неявно сделаны при текущем вы-
зове процедуры или триггера;
Q на экране демонстрируется заданное сообщение, после чего управление передается
в точку вызова процедуры.
Исключение может быть обработано в теле вызвавшей его хранимой процедуры или триг-
гера. Для этого используется оператор WHEN... DO. Если в теле процедуры было возбуждено
исключение при помощи оператора EXCEPTION ИмяИсключения, то далее в теле процедуры
можно указать оператор WHEN, который в данном контексте имеет следующий вид:
WHEN EXCEPTION ИмяИсключения DO
BEGIN
Операторы
END
Если в теле процедуры или триггера было возбуждено исключение, в первую очередь
отыскивается соответствующий этому исключению оператор WHEN EXCEPTION... DO.
230 Borland C++ Builder 6. Разработка приложений баз данных
Если такого оператора нет, выполнение процедуры прерывается и выполняется набор дей-
ствий, который описывался чуть выше. В противном случае выполняются все операторы,
указанные в операторных скобках BEGIN... END оператора WHEN. После этого управле-
ние передается в составной оператор процедуры, следующий за составным оператором,
в котором произошла ошибка.
В общем случае оператор WHEN имеет следующую структуру:
WHEN
{EXCEPTION ИмяИсключения | SQLCODE КодОшибкиБО^ |
GDSCODE КодОшибки!п1егЬа5е | ANY}
DO <СоставнойОператор>
Оператор WHEN SQLCODE обрабатывает ошибки SQL. Составной оператор вы-
полняется в том случае, если при выполнении запроса SQL произошла ошибка, код
которой совпадает со значением KodOuiu6mSQL. Форма оператора WHEN GDSCODE
предназначена для обработки ошибок Interbase. Глобальные переменные SQLCODE и
GDSCODE содержат коды ошибок, соответственно SQL и Interbase, последней выпол-
ненной операции, или О — если ошибка не произошла. И, наконец, последняя, четвертая
форма оператора — WHEN ANY— предназначена для обработки ошибок одного из трех
вышеперечисленных типов.
После ключевых слов ON [TABLE] нужно указать имя таблицы или представления,
к которым задаются права доступа. После ключевого слова ТО перечисляются те объекты,
которым эти права присваиваются. Конструкция <Объект> присваивает права доступа
для хранимых процедур, триггеров и представлений. Возможны следующие варианты:
Q PROCEDURE ИмяПроцедуры — хранимая процедура ИмяПроцедуры получает соот-
ветствующие права доступа к таблице или представлению;
О TRIGGER ИмяТриггера — триггер ИмяТриггера получает соответствующие права до-
ступа к таблице или представлению;
О VIEW ИмяПредставления — представление ИмяПредставленш получает соответ-
ствующие права доступа к таблице или представлению;
Q PUBLIC -— если указано это ключевое слово, то права доступа присваиваются всем без
исключения пользователям, процедурам, триггерам, представлениям и ролям (доступ
для всех без исключения).
Конструкция СписокПользователей задает перечень пользователей, которым присваи-
ваются права доступа. Здесь можно указать имя пользователя ([USER] ИмяПользователя)
или имя пользователя UNIX (ИмяПользователяиШХ). Кроме того, вслед за ключевым
словом ТО может следовать имя группы пользователей UNIX. После имени пользователя
могут быть ключевые слова WITH GRANT OPTION. В этом случае пользователь получает
право на предоставление прав другим пользователям.
Далее представлены несколько примеров задания прав доступа для объектов или поль-
зователей с использованием вышеприведенной формы оператора GRANT.
'Клиенту Accountant! предоставляются все права доступа
'к таблице Orders, в том числе и на предоставление всех прав
'другим пользователям
GRANT ALL ON Orders TO Accountantl WITH GRANT OPTION;
'Клиенту Accountant2 присваиваются права на просмотр таблицы
'Orders и редактирование ее полей Client и Quantity
GRANT SELECT, UPDATE(Client. Quantity) ON Orders TO Accountant2;
'Процедуре AddEmp предоставляется право на добавление новых
'записей в таблицу Employee
GRANT INSERT ON Employee TO PROCEDURE AddEmp;
При помощи оператора GRANT можно предоставить право на выполнение процедур.
Для этого используется следующая форма оператора:
GRANT EXECUTE ON PROCEDURE ИмяПроцедуры
TO {<0бъект> | <СписокПользователей>}
Из этих примеров можно сделать вывод, что планирование системы безопасности при
помощи оператора GRANT— довольно тяжелое и хлопотное дело. Для облегчения его
предназначен объект Interbase, который называется ролью (Role). Роль представляет собой
набор прав на доступ различного уровня к разным объектам БД, предоставляемый ука-
занным пользователям. Другими словами, она, как и следует из ее названия, определяет
ту роль (извините за тавтологию), которую будет играть пользователь, подключившись
к базе данных. Например, можно определить роль BookKeeper, которая будет задавать на-
бор прав доступа для всех бухгалтеров. Чтобы иметь возможность действовать в рамках
какой-либо роли, необходимо указать ее при подключении к базе данных:
232 Borland C++ Builder 6. Разработка приложений баз данных
Cancel Hep
New Connection
Qriver Name
jlnterbase
Connection Name
MylBConnect
OK Cancel htelp
I JLJ Browse!
atafaase:
:JD:\Data\Myibdatagdb
•Database Paranv
User Name'.
SYSDBA user_name=SYSDBA
Password: password=masterkey
jmasterkey
SflLRole:
Character Set
JNone •*•)
R Login Prompt
для дополнительной настройки соединения. Задать путь и имя файла БД можно при помс
щи свойства DatabaseName, а отредактировать настройки соединения можно при помощ!
редактора списка строк, прикрепленного к свойству Params.
Кроме того, компонент TIBDatabase имеет несколько свойств, специфичных для ба
данных Interbase. Свойство SQLDialect позволяет задать один из трех диалектов SQL
используемых БД (о диалектах SQL Interbase говорилось ранее в этой главе). Свойств*
IdleTimer целого типа задает интервал ожидания, в течение которого будет поддержи
ваться связь с базой данных, если нет ни одного активного набора данных, связанногс
сданным объектом TIBDatabase. Это свойство напоминает свойство логического типа
KeepConnection компонентов TDatabase (BDE) и TSQLConnection (dbExpress). Значение
О свойства IdleTimer подразумевает, что соединение обрываться не будет в любом слу-
чае. Этому соответствует значение true свойства KeepConnection компонентов TDatabase
и TSQLConnection. Значение false свойства KeepConnection соответствует ненулевому
значению свойства IdleTimer. Таким образом, в рамках механизма Interbase Express можно
более гибко управлять интервалом сохранения неактивных соединений.
Еще одно интересное свойство объекта TIBDatabase — AllowStreamedConnected. Это
свойство логического типа управляет тем, будет ли соединение с БД устанавливаться
автоматически, при запуске приложения. Если установить свойство Connected объекта
TIBDatabase в значение true в режиме конструктора, то соединение с базой данных будет
поддерживаться на все время работы в этом режиме. Однако при запуске приложения на
выполнение соединение с БД не обязательно будет установлено автоматически, даже ес-
ли свойство Connected в режиме конструктора получит значение true. Если для свойства
AllowStreamedConnected задано значение false, то для активизации связи с БД необходимо
при выполнении приложения программным путем задать для свойства Connected значение
true (или вызвать метод Open). Такой подход может быть удобен, когда вы разрабатываете
клиентское приложение для локального сервера Interbase, но в дальнейшем планируете
использовать его для подключения к удаленному серверу.
Посредством свойства-множества TraceFlags можно задать, какие операции с базой
данных будут доступны для трассировки при помощи утилиты SQL Monitor, входящей
в поставку C++ Builder. Описание этой утилиты выходит за рамки книги, скажу только,
что ей удобно пользоваться для отслеживания и отладки всех операций с базами данных
в формате SQL. На рис. 4.12 изображено окно утилиты SQL Monitor после того, как в SQL
Explorer'e была открыта база данных MylBData и развернут узел, соответствующий та-
блице ITEMS.
Свойство TraceFlags может содержать произвольный набор из десяти значений типа
TTraceFlag:
Q tfQPrepare — оператор Prepare;
Q tfQExecute — оператор ExecSQL;
О tfQFetch — оператор Fetch;
CD tfError — сообщения сервера об ошибках;
Q t/Stmt — все операторы SQL;
G tfConnect — операции подключения и отключения от БД, в том числе операции захвата
и освобождения ресурсов;
Q tjTransact — операторы StartTransaction, Commit и Rollback;
Q (/Blob — все операции с данными Blob;
Глава 4. Клиент-серверные базы данных 239
U: i SQL Monitor
View Clients Options Help
После того как соединение с базой данных при помощи объекта TIBDatabase настро-
ено, необходимо сделать еще один шаг, прежде чем пользоваться компонентами наборов
данных механизма Interbase Express. Дело в том, что для этих компонентов требуется ука-
зать не только объект базы данных (типа TIBDatabase), но и объект транзакции, в рамках
которой будет функционировать соответствующий набор данных. Механизм Interbase
Express поддерживает специальный объект TIBTransaction, представляющий собой от-
дельную транзакцию.
В простейшем случае на форме нужно разместить один объект TIBTransaction
и настроить его. Для этого можно воспользоваться редактором транзакций, доступ-
ным через пункт Transact Editor контекстного меню объекта. Этот редактор позволяет
выбрать один из четырех предопределенных наборов свойств транзакции. Более полно
транзакции можно настроить при помощи свойства Params. О том, какие свойства
240 Borland C++ Builder 6. Разработка приложений баз данных
транзакции можно указывать и какие значения для них задавать, можно узнать и:
справочной системы InterBase.
В заключение настройки объекта TIBTransaction следует указать в качестве значения его
свойства DefaultDatabase объект TIBDatabase и задать для свойства Active значение true.
Если оба объекта настроены правильно и объект базы данных активизирован, то все пройдет
нормально. Может показаться странным, что набор данных должен ссылаться на объект базы
данных и транзакцию и, кроме того, транзакцию также необходимо связать с объектом базы
данных. Дело в том, что Interbase (и механизм Interbase Express) предусматривает достаточно
сложные взаимоотношения между базами данных и транзакциями. В частности, в контексте
одной БД может одновременно выполняться несколько конкурирующих транзакций. Вместе
с тем, одна транзакция может взаимодействовать сразу с несколькими базами данных (напри-
мер, если в рамках транзакции выполняется запрос, воздействующий сразу на несколько та-
блиц, размещенных в разных БД). Однако обсуждение этих вопросов выходит за рамки книги.
Снова отсылаю вас к документации по Interbase.
Formb>ADOConnection1 ConnectionString
Source of Connection
OK 1 Cancel fctelp
Рис. 4.13. Окно для задания источника сведений для соединения ADO
Provider for ODBC Drivers. После этого нужно перейти на вкладку Подключение или на-
жать кнопку Далее (что одно и то же).
Отмена ; Справка
Рис. 4.14. На вкладке Поставщик данных нужно выбрать подходящий провайдер OLE DB
242 Borland C++ Builder 6. Разработка приложений баз данных
На этой вкладке можно выбрать имя источника данных (они перечислены на вклад»
Пользовательский DSN приложения Администратор источников данных ODBC) или дви
гаться далее по пути построения строки подключения. Последний вариант нам подходит
Выберите переключатель Использовать строку подключения и нажмите кнопку Сборке
(см. рис 4.15). На экране будет отображено окно Выбор источника данных (см. рис. 4.16)
Здесь снова предлагается выбрать подходящий dsn-файл, но так как его все еще нет, нужнс
нажать кнопку Создать. В следующем окне предлагается список всех доступных драйве-
ров ODBC (рис. 4.17). Сделайте свой выбор и нажмите кнопку Далее. В следующем окне
укажите имя для dsn-файла и снова нажмите кнопку Далее, а в очередном окне — кнопку
Готово.
Цр Свойства связи с данными
Йспо льэрватьстроку^подключения]
Строка подключения:
Сборка...;
••; • . ..;.' ' ' •
Пародь:
Г* Пустой пароль Г" Разрешить сохранение пароля
ок Отмена Сп,
(Data Sources
'
ОК Отмена Справка
: Password;
::Role; Г"
Q Нельзя использовать фильтры, так как они работают с множеством записей, а это тр«
бует буферизации. Попытка применить фильтр к однонаправленному набору данны
вызовет генерацию исключения. Ограничить число записей набора данных можно пр
помощи запроса SQL, содержащего соответствующее предложение WHERE.
Q Нельзя использовать поля подстановки. Для однонаправленного набора данных можн
создать поле подстановки, однако правильно работать оно не будет.
U Нельзя подключить к однонаправленному набору данных (конечно, посредством обь
екта TDataSource) элемент управления TDBGrid (сетка). При попытке подключит
сетку будет сгенерировано исключение, как в режиме конструктора, так и при выпол
нении приложения.
Механизм dbExpress поддерживает четыре типа однонаправленных наборов данных
TSQLTable, TSQLQuery, TSQLStoredProc и TSQLDataSet.
Компонент TSQLTable
Компонент TSQLTable представляет отдельную таблицу базы данных. Для того чтобы
подключиться к нужной таблице, вначале необходимо связать объект TSQLTable с одним
из доступных в приложении объектов TSQLConnection. Если объект TSQLConnection пра-
вильно настроен на подключение к какой-либо базе данных, список свойства TableName
будет содержать перечень всех хранящихся там таблиц. В зависимости от значения свой-
ства TableScope соединения, в списке свойства TableName могут быть также и системные
таблицы и представления. Выбрав нужную таблицу, можно открыть набор данных, задав
для свойства Active значение true.
Теоретически к одному объекту TSQLConnection можно подключить любое количе-
ство объектов TSQLTable (или других наборов данных). Однако на самом деле сервер БД
может ограничивать количество операторов SQL, одновременно выполняющихся в рам-
ках одного соединения. Любой набор данных, подключенный к БД, формирует запрос
SQL к серверу БД. Например, если объект TSQLTable подключен к таблице Category,
к серверу БД будет сформирован следующий запрос:
Select * From Category;
Таким образом, количество связанных с объектом TSQLConnection наборов данных бу-
дет соответствовать количеству одновременно выполняющихся в его контексте операторов
SQL. Кроме того, в это число входят также операторы, выполняемые при помощи метода
Execute соединения. У компонента TSQLConnection есть свойство MaxStmtsPerConn типа
long. Если свойство Connected соединения имеет значение true, то это свойство содержит
максимальное количество одновременно выполняющихся операторов SQL. Значение О
подразумевает, что сервер никаких ограничений на запросы не налагает. В этом случае
с объектом TSQLConnection можно связать любое количество наборов данных.
Если свойство MaxStmtsPerConn содержит ненулевое значение, то к объекту
TSQLConnection можно подключить самое большее столько наборов данных, сколько за-
дает это значение. Что произойдет, если попытаться подключить сверх лимита еще один
набор данных? Это зависит от еще одного свойства компонента TSQLConnection. Если
свойство AutoClone имеет значение true (это значение по умолчанию), то при превышении
максимального числа операторов SQL автоматически создается еще одно соединение,
полностью копирующее все свойства текущего (т.е. соединение клонируется). С этим со-
Глава 4. Клиент-серверные базы данных 247
единением и ассоциируется новый набор данных. Таким образом, пользователь даже не
замечает, что сервер БД налагает какие-либо ограничения.
Новое соединение, автоматически создаваемое при нарушении ограничения, требует
новых ресурсов, что может быть нежелательно. Чтобы предотвратить автоматическое
клонирование соединений, свойству AutoClone нужно задать значение false. Тогда при на-
рушении ограничения будет сгенерировано исключение. В этом случае может пригодить-
ся свойство соединения ActiveStatements. Оно возвращает количество активных в теку-
щий момент операторов SQL. Сравнивая значение этого свойства со значением свойства
MaxStmtsPerConn, можно судить о допустимости подключения еще одного набора данных
к объекту TSQLConnection (или выполнения оператора SQL при помощи метода Execute).
По приемам дальнейшего использования объект TSQLTable почти не отличается от других
объектов — наборов данных. На форму (или в модуль данных) помещается экземпляр компо-
нента TDataSource и при помощи свойства DataSet связывается с объектом TSQLTable. Далее
к объекту TDataSource можно подключить любые элементы визуализации и управления дан-
ными, за исключением компонента TDBGrid (сетка). При попытке подключить сетку к одно-
направленному набору данных вы получите сообщение об ошибке: «Operation not allowed on a
unidirectional dataset» («Для однонаправленных наборов данных операция недопустима»).
Чтобы разобраться во всех ограничениях, налагаемыех на однонаправленный на-
бор данных, можно создать небольшое приложение. Разместите на форме экземпляры
компонентов TSQLConnection, TSQLTable и TDataSource, оставив все имена объектов по
умолчанию. Настройте соединение SQLConnectionl, указав базу данных MylBData.gdb
в качестве источника информации (эта БД Interbase содержит те же таблицы и ту же ин-
формацию, что и БД MyData). Объект SQLTablel свяжите с SQLConnectionl, после чего
из списка свойства TableName выберите таблицу Category и откройте набор данных.
Укажите объект SQLTablel в качестве значения свойства DataSet объекта DataSourcel.
Двойным щелчком на объкте SQLTablel откройте редактор полей и добавьте определе-
ния всех полей таблицы Category. Далее, разместите на форме панель (TPanel) и перета-
щите на нее все поля из редактора полей. В завершение добавьте в верхнюю часть формы
панель навигации (TDBNavigator), подключив ее к объекту DataSourcel. Выровняйте все
элементы управления примерно так, как на рис. 4.19. Откомпилируйте получившееся при-
ложение и запустите его на выполнение.
SQLTable Demo
CATEGORYNO
И
CATEGORY
Модули ___..
памяти
Первое, что бросается в глаза, — заблокированы все кнопки панели навигации, связан-
ные с модификацией набора данных. Нельзя добавлять или удалять записи, нельзя переве-
сти набор данных в режим редактирования и т.д. Кнопки навигации доступны все, но по-
пытка воспользоваться кнопкой Prior или Last вызовет генерацию исключения. Попытка
отредактировать значение одного из полей набора данных в элементе управления TDBEdit
закончится неудачей. Элемент управления TDBEdit будет вести себя так, как будто его
свойство ReadOnly имеет значение true.
Выше уже упоминалось о том, что можно организовать редактирование данных табли-
цы и навигацию по ее записям в любом направлении, основываясь на объекте TSQLTable.
Один из вариантов — использовать клиентский набор данных (компонент TClientDataSet),
подключенный к провайдеру (TDataSetProvider). Для иллюстрации этого подхода можно
воспользоваться приложением, которое рассматривалось чуть ранее (см. рис. 4.19). На
форме разместите дополнительно по одному экземпляру компонентов TDataSetProvider
и TClientDataSet, оставив их имена по умолчанию. Оба компонента можно найти на вклад-
ке Data Access палитры компонентов. Объекты SQLConnectionl и SQLTablel оставьте без
изменений, а объект DataSourcel свяжите с ClientDataSetl (свойство DataSet). Далее, для
свойства ProviderName объекта ClientDataSetl укажите значение DataSetProviderl, а для
свойства DataSet объекта DataSetProviderl — значение SQLTablel. Получилась довольно
длинная информационная цепочка следующего вида:
DataSourcel --> ClientDataSet --> DataSetProviderl -->
SQLTablel --> SQLConnectionl
Откройте оба набора данных (TClientDataSet и TSQLTable), установив для свойства
Active значение true. Видно, что на панели навигации (TDBNavigator) стали доступны
те кнопки, которые ранее доступны не были. Откомпилируйте приложение и запустите
его на выполнение. Пользуясь панелью навигации, можно убедиться, что теперь можно
перемещаться по набору данных в любом направлении и, кроме того, добавлять, удалять
и модифицировать записи.
Секрет этого нехитрого превращения в том, что клиентский набор данных работает
с собственной копией набора данных, для которой используется режим буферизации
записей. Это, в свою очередь, позволяет воспользоваться всеми возможностями, объяв-
ленными в классе TDataSet (навигация во всех направлениях, встроенная поддержка ре-
дактирования и т.д.). Таким образом, этот подход — не более чем ловкость рук: на самом
деле вы работаете с клиентским набором данных, который предоставляет необходимый
интерфейс. Если вы работаете с базой данных, размещенной на локальном компьютере,
такой подход не имеет смысла. Гораздо проще напрямую воспользоваться специализиро-
ванным клиентским набором данных из набора dbExpress (компонент TSQLClientDataSef).
В этом случае вы получаете все возможности, не прибегая к компонентам TSQLTable,
TClientDataSet и TDataSetProvider.
Но такой подход очень удобен и надежен при использовании в многоярусных (mul-
ti-tired) приложениях. Например, в трехзвенной архитектуре (тонкий клиент — сервер
приложений— сервер БД) на стороне клиента располагаются экземпляры объектов
TDataSource, TClientDataSet и TDataSetProvider. Объект TDataSetProvider передает дан-
ные от клиентского приложения набору данных (например, TSQLTable), который располо-
жен на сервере приложений, и наоборот. На сервере приложений располагается и объект
TSQLConnection, который может использоваться для подключения к базе данных, рас-
положенной на удаленном сервере БД.
Глава 4. Клиент-серверные базы данных 249
ных. Об этом придется позаботиться самому. Ниже приведен код обработчика события
AfterScroll набора данных, решающий эту задачу:
void _ fastcall TForml: : SQLTablelAfterSci"oU (TDataSet *DataSet)
{
if (!SQLTablel->FieldByName("CategoryNo")->IsNull)
{
ECatNo->Text=SQLTablel->
FieldByNameC "CategoryNo") ->AsSt ring;
ECategory->Text=SQLTablel->
FieldByName( "Category ")->AsStr ing;
EDescription->Text=SQLTablel->
Fi eldByName("Descript ion") ->AsSt ring;
ключа на значение NULL). В результате такого перехода кнопки Next и Last панели на-
вигации становятся недоступны.
В методе MoveToRec используется переменная логического (boot) типа Process. Эта
переменная объявлена в секции private класса TForml и в обработчике события OnCreatt
формы инициализирована значением false. Первый оператор метода MoveToRec при-
сваивает переменной Process значение true, а последний оператор метода возвращает ей
значение false. Таким образом, во время перемещения на нужную запись набора данных
переменная Process имеет значение true. Это пригодится нам в дальнейшем.
Откомпилируйте приложение и запустите его на выполнение. Можете убедиться, что
теперь по набору данных можно перемещаться в любом направлении. Но редактировать
данные набора все еще нельзя. Попробуем обойти и это ограничение. Справа от панели
навигации разместите пять кнопок (TButton) и задайте для них следующие имена и подпи-
си: Bins и Ins, BDel и Del, BEdit и Edit, BPost и Post, BCancel и Cancel соответственно (см.
рис. 4.20). Эти кнопки будут эмулировать выполнение функций соответствующих кнопок
панели навигации, убранных с экрана.
SQLTable Demo
Удш
м < Ins Del Edit Post Cancel :
\ CATEGORYNQ
CATEGORY
Видеокарты
DESCRIPTION
Нажатие кнопок BEdit, Bins и BDel эмулирует перевод набора данных в режим редак-
тирования, вставки или удаления записи. Чтобы облегчить задачу, удобно воспользовать-
ся типом данных, значения которого описывают возможные состояния набора данных.
В файле Unitl.h перед объявлением класса TForml укажите следующее объявление:
enum TKindOfState { kosBrowse, kosEdit, koslnsert, kosDelete };
В секции private класса TForml укажите объявление переменной KindOJState типа
TKindOfState и в обработчике события OnCreate формы инициализируйте ее значением
kosBrowse. Это означает, что набор данных первоначально находится в режиме просмотра.
Далее приведен код обработчика события нажатия кнопки BEdit:
void fastcall TForml::BEditClick(TObject *Sender)
{
ECategory->ReadOnly=false;
EDescript!on->ReadOnly=false;
ECategory->SetFocus();
Глава 4. Клиент-серверные базы данных 253
KindOfState=kosEdit;
}
Как видим, в коде обработчика события OnClick кнопки BEdit не происходит ничего
особенного. Вначале снимается запрет на редактирование текста в элементах управления
ECategory и EDescription (значение первичного ключа редактировать нельзя, поэтому
свойство ReadOnly элемента ECatNo неизменно). Затем фокус ввода для удобства перено-
сится на элемент редактирования ECategory (метод SetFocus). В заключение переменная
KindOfState получает значение kosEdit. Это означает, что набор данных теперь находит-
ся в режиме редактирования и вы можете модифицировать значения полей в элементах
ECategory и EDescription.
Пока набор данных находится в режиме редактирования (kosEdii), возможны следу-
ющие варианты: пользователь захочет сохранить изменения, отменить изменения, попы-
таться перейти к другой записи, попытаться закрыть форму. Для первых двух функций
предназначены кнопки BPost и BCancel. С обработчиками событий нажатия обеих кнопок
можно связать один и тот же обработок:
void fastcall TForml::BPostClick(TObject *Sender)
{
if((KindOfState != kosEdit) && (KindOfState != koslnsert))
return;
int Repl;
Repl=Application->MessageBox(
"Сохранить изменения?",
"Внимание!",
MB_YESNO + MB_ICONQUESTION);
if (Repl == IDYES) PostingO;
else CancelingQ ;
}
В начале этого обработчика предотвращается попытка выполнить входящие в него
операторы, если набор данных не находится в режиме редактирования или вставки запи-
си. Далее у пользователя запрашивается подтверждение на сохранение данных в таблице.
Если подтверждение получено, вызывается метод Posting, в противном случае вызывается
метод Canceling. Оба метода объявлены в секции private класса TForml. Ниже приведен
текст метода Posting:
void fastcall TForml::Posting(void)
{
if(KindOfState == kosBrowse) return;
switch(KindOfState)
{
case kosEdit:
{
SQLText="Update Category Set Category='" +
ECategory->Text +
"', Description='" + EDescription->Text +
"' Where CategoryNo=" + StrToInt(ECatNo->Text) + ";";
break;
254 Borland C++ Builder 6. Разработка приложений баз данных
case koslnsert:
SQLText="INSERT INTO Category
(Category, Description) VALUES ('" +
ECategory->Text + "', '" + EDescription->Text
NRec=SQLTablel->RecordCount+l ;
break;
case kosDelete:
SQLText="Delete From Category Where CategoryNo="
ECatNo->Text + ";";
NRec-=l;
KindOf State=kosBrowse;
}
В первой части метода Posting в зависимости от режима набора данных формируется
соответствующая строка оператора запроса SQL. Затем, если набор данных находится
в режиме koslnsert, то значение переменной NRec увеличивается на 1 , а если из набора
данных была удалена запись, то NRec уменьшается на 1 . Далее, на основании сформиро-
ванной строки оператора SQL выполняется запрос на изменение (на обновление, добавле-
ние или удаление записей). Для выполнения запросов к таблицам, хранимым процедурам
или представлениям можно воспользоваться методом Execute объекта TSQLConnection.
Этот метод объявлен следующим образом:
int _fastcall Execute(const AnsiString SQL, TParams Params,
void *ResultSet = NULL);
Первый аргумент этого метода, строка SQL, должен содержать текст допустимого
оператора SQL. Второй аргумент задает набор параметров для запроса. Если запрос не
имеет параметров, следует указать значение NULL. И, наконец, третий аргумент метода
используется, если запрос должен возвращать набор данных.
В заключение, элементы редактирования ECategory и EDescription снова переводятся
в режим запрета модификации значений. Если набор данных находится в режиме редак-
тирования или вставки, выполняется переход на соответствующую запись (на новую за-
Глава 4. Клиент-серверные базы данных 255
пись в режиме вставки и на предыдущую запись (по возможности) для режима удаления).
Последний оператор переводит набор данных в режим просмотра (kosBrowse).
Метод Canceling решает задачу возвращения набора данных в то состояние, в котором
он был до перевода в один из режимов модификации (kosEdit, koslnsert или kosDelete).
Элементам редактирования, расположенным на форме, присваиваются значения полей
текущей записи набора данных, после чего они переводятся в режим только для чтения
(Read Only). В заключение набор данных переводится в режим просмотра (переменной
KindOfState присваивается значение kosBrowse).
Пользователь может попытаться перейти к другой записи набора данных при помощи
кнопок панели навигации, несмотря на то что набор данных не находится в режиме про-
смотра. В этом случае могут быть потеряны все внесенные изменения, кроме того, может
нарушиться правильная работа приложения. Избежать всех этих неприятностей можно,
воспользовавшись обработчиком события BeforeScroll набора данных, которое проис-
ходит непосредственно перед переходом к другой записи. Прежде всего, в теле обработ-
чика этого события нужно проверить значение переменной Process. Если значение этой
переменной — true, то в данный момент выполняется переход по записям, выполняемый
методом MoveToRec. В этом случае выполнять остальные операторы обработчика события
BeforeScroll нельзя. Далее у пользователя запрашивается, нужно ли сохранять несохра-
ненные данные или нет. В первом случае вызывается метод Posting, во втором — метод
Canceling. Сходные действия предпринимаются и при попытке закрыть форму.
Полный текст описываемого приложения-примера приведен в листингах 4.1 и 4.2.
Откомпилируйте его и запустите на выполнение. Теперь можно не только перемещаться
по однонаправленному набору данных в любом направлении, но и редактировать, встав-
лять или удалять записи.
Листинг 4.1. Файл заголовка приложения SQLTableDemoZ
#ifndef UnitlH
#define UnitlH
#include <Classes.hpp>
^include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
^include <DB.hpp>
#include <DBCtrls.hpp>
#include <DBXpress.hpp>
#include <ExtCtrls.hpp>
#include <FMTBcd.hpp>
#include <Mask.hpp>
#include <SqlExpr.hpp>
enum TKindOfState { kosBrowse, kosEdit, koslnsert, kosDelete };
TIntegerField *SQLTablelCATEGORYNO;
TStringField *SQLTabTelCATEGORY;
TStringField *SQLTablelDESCRIPTION;
TLabel *Labell;
TLabel *Label2;
TLabel *Label3;
TPanel *Panell;
TDBNavigator *DBNavigatorl ;
TButton *BEdit;
TPanel *Panel2;
TPanel *Panel3;
TButton *BIns;
TButton *BDel;
TButton *BPost;
TButton *BCancel;
TEdit *ECatNo;
TEdit *ECategory;
TEdit *EDescription;'
void _ fastcall SQLTablelAf terScroll(TDataSet *DataSet) ;
void _ fastcall DBNavigatorlBeforeAction(TObject *Sender,
TNavigateBtn Button);
void _ fastcall FormCreate(TObject *Sender)
void _fastcall BEdi tClick(TObject *Sender)
void _ fastcall BPostClick(TObject *Sender)
void _ fastcall SQLTablelBeforeScroll(TDataSet *DataSet) ;
void _ fastcall BInsClick(TObject *Sender);
void _fastcall BDelClick(TObject *Sender);
void _ fastcall FormClose(TObject *Sender, TCloseAction &Action);
private: // User declarations
long NRec;
TKindOfState KindOfState;
AnsiString SQLText;
bool Process;
void _ fastcall Posting(void) ;
void _ fastcall Canceling(void) ;
void _ fastcall MoveToRec(void) ;
public: // User declarations
_ fastcall TForml(TComponent* Owner);
^include <vcl.h>
#pragma hdrstop
#include "Unitl.h"
#pragma package(smart_ini t)
#pragma resource "*.dfm"
Глава 4. Клиент-серверные базы данных 257
TForml *Forml;
switch(Button)
AbortO;
finally
MoveToRecO ;
9 Зак.319
258 Borland C++ Builder 6. Разработка приложений баз данных
ECategory->ReadOnly=false;
EDescription->ReadOnly=false;
ECategory->SetFocus();
KindOfState=kosEdit;
KindOf State=kosBrowse;
Глава 4. Клиент-серверные базы данных 259
ECategory->SetFocus() ;
Замечание
Приведенный в листингах 4.1 и 4.2 пример приложения далек от совершенства, и его
вряд ли можно использовать на практике. Этот пример призван проиллюстрировать
некоторые приемы работы с однонаправленными наборами данных. Придется прило-
жить еще некоторые усилия, чтобы довести приведенный код до приемлемого уровня.
Особенно это касается случаев использования больших наборов данных.
-'
EMPLOYEE
INVOICES
ITEMS
MONLIST
STORAGE
CATEGORY
CATEGORYNO
DESCRIPTION
CommandType можно указать, какой из объектов базы данных будет представлять объект
TSQLDataSet. Из списка, прикрепленного к этому свойству, можно выбрать одно из трех
значений: ctQuery, ctTable и ctStoredProc.
Если выбрано значение ctTable, то в списке свойства CommandText будут перечислены
имена всех таблиц и представлений базы данных (впрочем, состав объектов, перечислен-
ных в этом списке, зависит от значения свойства TableScope объекта TSQLConnection).
Если для свойства CommandType выбрано значение ctStoredProc, то список свойства
CommandText будет содержать перечень всех хранимых процедур. И, наконец, если вы-
брано значение ctQuery, то свойство CommandText вместо кнопки развертывания списка
будет снабжено кнопкой построителя. При нажатии на эту кнопку на экране будет ото-
бражено окно редактора свойства CommandText (см. рис. 4.21). Редактор этот, конечно,
достаточно корявенький, но главное, строку запроса SQL любой сложности в него ввести
можно — например, скопировав из более удобного редактора SQL запросов.
Дальнейшие приемы использования компонента TSQLQuery практически ничем не
отличаются от приемов использования других наследников класса TDataSet из набора
dbExpress. Если объект TSQLDataSet возвращает набор данных, то получить доступ к нему
можно, задав для свойства Active значение true (или воспользовавшись услугами метода
Open). Для выполнения каких-либо действий можно воспользоваться методом ExecSQL.
Кроме того, загрузить данные из файла в клиентский набор данных можно при помо-
щи метода LoadFromFile, который объявлен следующим образом:
void fastcall LoadFromFile(const AnsiString FileName = "");
Единственный аргумент метода — имя и полный путь к файлу, в котором пред-
варительно был сохранен нужный набор данных. В документации говорится, что если
имя файла не указано, данные загружаются из файла, спецификация которого указана
в свойстве FileName. Однако я не смог добиться желаемого результата при таком приме-
нении метода LoadFromFile. Ниже приведен пример, в котором набор данных загружается
из файла d:\Data\Category.cds:
SQLClientOataSetl->LoadFromFile("D:\\Data\\Categ.cds");
SQLClientData5etl->Open():
Метод LoadFromFile объявлен в классе TCustomCachedDataSet, который является непо-
средственным предком класса TSQLClientDataSet. Метод SaveToFile, при помощи которого
можно сохранить во внешнем файле набор данных, связанный с клиентским набором данных,
объявлен в классе TCustomClientDataSet, который, в свою очередь является непосредствен-
ным предком для класса TCustomCachedDataSet. Объявление этого метода выглядит так:
void fastcall SaveToFile(const AnsiString FileName = "",
. TDataPacketFormat Format = dfBinary);
Аргумент FileName типа AnsiString должен содержать имя и полный путь к файлу,
в котором будет сохранен внутренний набор данных объекта TSQLClientDataSet. Второй
аргумент — Format типа TDataPacketFormat — задает формат внешнего файла, в котором
будет сохранен набор данных. Возможные значения этого аргумента таковы: dfBinary (ин-
формация будет сохранена в двоичном формате в файле с расширением cds), dJXML (xml-
файл с расширенным набором символов с использованием еусаре-гюследокательностей),
dfXMLUTFS (хт/-файл с расширенным набором символов с использованием UTF8).
Механизм MyBase можно использовать для организации баз данных, информация
которых хранится только во внешних файлах (файл-ориентированные БД). Кроме того,
механизм MyBase можно использовать для организации комбинированной модели до-
ступа к данным, которая называется портфельной (briefcase model). Эта модель особенно
удобна при использовании ноутбуков. Идеология такого подхода заключается в следую-
щем. Вы подключаетесь к определенному набору данных на сервере БД, сохраняете этот
набор данных на локальном диске (например, на диске ноутбука), затем отключаетесь
от сервера БД. С набором данных, сохраненным на вашем локальном диске, вы можете
работать сколь угодно долго, модифицируя его записи, добавляя их или удаляя. Затем вы
можете снова подключиться к серверу БД и сохранить в соответствующих таблицах все
изменения, сделанные вами с момента последнего отключения.
Чтобы показать простейший способ использования портфельной модели, создадим
простое тестовое приложение. Откройте новое приложение и на его главной форме раз-
местите экземпляры компонентов TDBGrid, TDataSource, TSQLClientDataSet и TSQLCon-
nection, оставив для них все имена, принятые по умолчанию. Соединение SQLConnectionl
подключите к базе данных MylBData. Сетку DBGridl подключите к объекту DataSourcel,
а его, в свою очередь, подключите к объекту SQLClientDataSetl. В верхней части формы
разместите три кнопки (TButton), задав для них следущие имена и подписи (свойство
Caption): BGetData и Получить данные, BOnOffu Подключить, BSaveData и Сохранить дан-
ные соответственно. Все должно выглядеть примерно так, как изображено на рис. 4.22.
Глава 4. Клиент-серверные базы данных 265
ITEMNITEM ICATEGJCJESCRIPTION
[Samsung ML-4500 11 AACaDI, ОЗУ 2Мб, 600jdpi.jil
2i Samsung ML-1210 11A4, GDI ОЗУ'в Ш. 600 dpi д | || •
З'ЕрвопЕРЬ^ЭОоГ
1]А4ДЗр1,600 dpi, до 8 ppm. л(**^
5 (Brother HL-1^40 Mono Laser
1
6 [Epson AcuLaser C1 OOOw | A4, цвет, ОЗУ 16(256) Мб. Г ***
7 (Epson AcuLaser C2000
8 HP LaserJet 10OOw
1JHP Laser Jet 1200 _^ л ^.^А!!??0^!!, !
ЩНР LaserJet 1220 1 i A4, сканер 600 dpi - 24 бит,
repl=Application->MessageBox(
"Файл существует. Заменить?",
"Внимание!",
MB_YESNO + MB_ICONQUESTION);
Предполагается, что имя внешнего файла портфельного набора и путь к нему хранятся
266 Borland C++ Builder 6. Разработка приложений баз данных
SQLClientDataSetl->Act1ve=false;
BOnOff->Caption = "Подключить";
В коде обработчика вначале проверяется текст подписи кнопки. Если кнопка имеет
подпись Подключить, выполняется подключение к файлу с набором данных. Для этого
очищается свойство DBConnection (на всякий случай, ведь неизвестно в какой ситуа-
ции может быть нажата кнопка), а затем в свойство FileName записывается имя и путь
к нужному файлу. В заключение набор данных открывается и текст подписи кнопки ме-
няется на Отключить. Для отключения от файлового набора данных достаточно закрыть
набор данных.
Кнопка BSaveData с текстом подписи Сохранить данные предназначена для синхро-
Глава 4. Клиент-серверные базы данных 267
if(FileExists(DataFileName))
{
repl=Application->MessageBox(
"Сохранить данные на сервере?",
"Внимание!",
MB_YE5NOCANCEL + MB^ICONQUESTION);
}
if(repl == IDCANCEL) return;
SQLClientDataSetl->Active=false;
BOnOff->Caption = "Подключить";
SQLClientDataSetl->DBConnection=SQLConnectionl;
SQLClientDataSetl->CommandType=ctTable;
SQLClientDataSetl->CommandText="ITEMS";
SQLClientDataSetl->Active=true;
if(repl == IDYES)
SQLClientDataSetl->ApplyUpdates(-l);
else
SQLClientDataSetl->CancelUpdates();
SQLClientDataSetl->Active=false;
}
Первым делом в обработчике проверяется наличие несохраненных изменений
(свойство ChangeCouni). Если несохраненные изменения есть, у пользователя запра-
шивается подтверждение на сохранение данных на сервере. Если пользователь нажал
кнопку Cancel, выполнение обработчика события прекращается. В противном случае
клиентский набор данных закрывается и подключается к объекту SQLConnection,
к таблице Items. Если пользователь выбрал сохранение данных, вызывается метод
ApplyUpdates, иначе управление передается методу CancelUpdates, который отменяет
все сделанные изменения.
Описанный пример применения портфельной модели данных достаточно прост.
Мы использовали таблицу в качестве источника данных для клиентского набора
TSQLClientDataSet и поэтому не встретили никаких сложностей. Однако портфельная мо-
дель чаще применяется для работы с более сложным набором данных, основанным на не-
скольких связанных таблицах. В этом случае простое использование метода ApplyUpdates
приведет к генерации исключения с сообщением о том, что отсутствует провайдер или
пакет данных. Это сообщение об ошибке не соответствует истине, так как компонент
TSQLClientDataSet имеет внутренний провайдер по определению, имеется также и пакет
данных. Исключение генерируется внутренним провайдером, потому что ему не известно,
как обновлять таблицы, расположенные на сервере.
Чтобы ваши намерения были «понятны» провайдеру, можно воспользоваться событи-
ями OnGetTableName и BeforeUpdateRecord. Обработчик события OnGetTableName объ-
явлен в классе TCustomCachedDataSet так:
typedef void fastcall ( closure *TGetTableName)(TObject* Sender,
Db::TDataSet* DataSet, AnsiString &TableName);
Обработчиком этого события можно воспользоваться для того, чтобы указать провай-
деру имя таблицы на стороне сервера, которая должна быть модифицирована. Имя табли-
цы должно быть задано в аргументе TableName. Кроме этого аргумента в тело обработ-
Глава 4. Клиент-серверные базы данных 269
чика события передается еще два аргумента. Аргумент Sender ссылается на внутренний
провайдер клиентского набора данных, а аргумент DataSet ссылается на его внутренний
набор данных.
Более полезно и удобно событие BeforeUpdateRecord. Оно происходит при обнов-
лении для каждой записи, информация о которой хранится в свойстве Delta (т.е. для
каждой модифицированной записи). Обработчик этого события также объявлен в классе
TCustomCachedDataSet;
typedef void fastcall ( closure *TBeforeUpdateRecordEvent)
(TObject* Sender, TDataSet SourceDS, TCustomClientDataSet *DeltaDS,
Db::TUpdateKind UpdateKind, bool &Applied);
Первые два аргумента, как и в обработчике события OnGetTableName, ссылаются на
внутренний провайдер и набор данных клиентского набора данных. Аргумент DeltaDS
представляет собой ссылку на набор данных, содержащий все модифицированные запи-
си. Аргумент UpdateKind содержит одно из трех значений, описывающих тип модифика-
ции записи: ukModify (запись была отредактирована), uklnsert (запись была добавлена),
ukDelete (запись была удалена). И последний аргумент, передающийся в тело цикла по
ссылке, — аргумент логического типа Applied. Если задать для Applied значение true, про-
вайдер не будет пытаться самостоятельно сохранять запись на сервере и не будет возвра-
щать ошибку обновления записи. По умолчанию аргумент Applied имеет значение false.
Это означает, что забота об обновлении записи возлагается на провайдер. Таким образом,
если вы сами обновляете запись на сервере, следует задать для аргумента Applied значение
true, а если нужно, чтобы это делал провайдер, задайте значение false (или не задавайте
никакого, что одно и то же).
Вот обычная схема использования обработчика события BeforeUpdateRecord:
void fastcallTForml::SQLC1ientDataSetlBeforeUpdateRecord(TObject
*5ender, TDataSet *SourceDS, TCustomClientDataSet *DeltaDS,
TUpdateKind UpdateKind, bool &Applied)
{
AnsiString SQLString;
SQLConnection *Connectionl=
(dynamic_cast<TCustomSQLDataSet *>(SourceDS)->5QLConnection);
switch (UpdateKind)
case ukModify:
SQLString="Update ИмяТаблицы! " +
"Set Поле1=" + FormatFunc(DeltaDS->Fields->
Fields[l]->NewValue) + ","
"rioneN=" + FormatFunc(DeltaDS->Fields->
Fields[N]->NewValue) +
"Where КлючевоеПоле1=" +
FormatFunc(DeltaDS->Fields->Fields[0]->OldValue)
Connectionl->Execute(SQLString, NULL, NULL);
(Далее может следовать любое количество
запросов на обновление)
break;
270 Borland C++ Builder 6. Разработка приложений баз данных
case ukDelete:
SQLString="Delete From ИмяТаблицы! " +
"Where КлючевоеПоле1=" +
FormatFunc(DeltaDS->Fields->Fields[0]->OldValue)
Connectionl->Execute(SQLString, NULL. NULL);
(Далее может следовать любое количество запросов на удаление)
break;
case uklnsert:
SQLString="Insert Into ИмяТаблицы!(Поле!. ... ПолеМ) " +
"Values (" + FormatFunc(DeltaDS->Fields->
Fields[l]->NewValue) + ","
FormatFunc(DeUaDS->Fields->Fields[N]->NewValue) +
«) - +
"Where КлючевоеПоле1=" +
FormatFunc(DeltaDS->F1elds->Fields[e]->OldValue)
Connectionl->Execute(SQLString, NULL, NULL);
(Далее может следовать любое количество запросов
на добавление)
break;
}
В приведенном обработчике события BeforeUpdateRecord вместо имени FormatFunc
должно использоваться имя реальной функции преобразования значения поля к типу
AnsiString (например, IntToStr).
Компонент TSQLClientDataSet— очень мощный и удобный инструмент, который
практически в любых случаях можно с успехом использовать в приложениях БД. Однако
есть ситуации, когда применение клиентского набора данных ведет к неоправданному
расходу ресурсов и существенному замедлению работы всего приложения (например,
в случае связи главный/подчиненный). Вам самим решать, когда нужно использовать
компонент TSQLClientDataSet, л когда — один из однонаправленных наборов данных из
набора dbExpress.
Компонент TSQLMonitor
Последний компонент из набора dbExpress, который будет рассмотрен в этой главе,
— TSQLMonitor. Как и одноименная утилита, поставляемая в составе C++ Builder, ком-
понент TSQLMonitor используется в отладочных целях. Основное отличие компонента
от утилиты в том, что им отслеживаются не все сообщения, а только ассоциированные
с конкретным объектом TSQLConnection. Экземпляр компонента TSQLMonitor под-
ключается к одному из объектов TSQLConnection и отслеживает все сообщения SQL,
передаваемые от сервера БД этому отдельно взятому объекту соединения и обратно. Все
перехваченные сообщения сохраняются в свойстве TraceList, которое является списком
строк. Содержимое списка строк StringList может быть автоматически или вручную со-
хранено в файле.
Мы покажем простейший способ работы с компонентом TSQLMonitor на приме-
ре. Для этого воспользуемся приложением BriefcaseDemo, которое рассматривалось
в предыдущем разделе, при изучении портфельной модели доступа к данным. Откройте
это приложение, при необходимости сохранив его в другом каталоге и под другим име-
Глава 4. Клиент-серверные базы данных 271
нем. Добавьте на форму экземпляр компонента TSQLMonitor и свяжите его свойство
SQLConnection с объектом SQLConnection. (He знаю почему, но аналогичное свойство
остальных компонентов с вкладки dbExpress называется DBConnection.) Далее, задайте
имя файла в свойстве FileName. Можно воспользоваться стандартным диалоговым окном
открытия файла, которое отображается на экране после щелчка на кнопке построителя
свойства FileName. В этом текстовом файле и будут сохраняться все сообщения SQL,
перехваченные объектом TSQLMonitor. Чтобы сохранение происходило автоматически,
задайте для свойства AutoSave значение true. После того как значение true будет задано
и для свойства Active, объект TSQLMonitor начнет функционировать.
Откомпилируйте приложение и запустите его на выполнение. Попробуйте произвести
какое-то действие, в которое будет вовлечен объект SQLConnection 1. Можно, например,
нажать кнопку Получить данные, в результате чего с сервера будет получена нужная ин-
формация и сохранена в файле на локальном диске. Закройте приложение. В указанном
вами каталоге появится файл с заданным именем, содержащий все сообщения SQL, ко-
торыми обменялись сервер БД и объект SQLConnection 1 в результате выполненного вами
действия (см. рис. 4.23).
оиск Справка
INTERBASE - isc_attach_database
INTERBASE - isc_dsql_allocate_statement
INTERBASE - isc_start_traneaction
select * from "ITEMS-
INTERBASE - isc_dsql_prepare
INTERBASE - isc_dsql_describe_bind
INTERBASE - isc_dsql_execute
INTERBASE - isc dsql allocate.statement
SELECT 0, ", ", A.RDB$RELATION_NAME, A.RDB$INDEX_NAME,
INTERBASE - isc_dsql_prepare
INTERBASE - isc_dsql_describe_bind
INTERBASE - isc_dsql_execute
INTERBASE - isc.dsql.fetch
INTERBASE - isc_dsql_fetch
INTERBASE - isc_dsql_fetch
INTERBASE - isc_dsql_fetch
Событие OnLogTrace происходит сразу после того, как очередное сообщение добавля-
ется в список строк TraceList. Аргумент Sender указывает на объект TSQLMonitor, для ко-
торого и было сгенерировано событие. Аргумент CBInfo является указателем на структуру
pSQLTRACEDesc, описывающую текущее сообщение. Структура эта объявлена так:
struct SQLTRACEDesc {
char pszTrace[1024];
TRACECat eTraceCat;
int ClientData;
WORD uTotalMsgLen;
}
Элемент этой структуры pszTrace содержит текст сообщения, а элемент uTotalMsg-
Len — его длину. ClientData используется в функциях обратного вызова (callback), рас-
смотрение которых выходит за рамки книги. Наиболее интересен для практических целей
элемент eTraceCat перечислимого типа TSQLTraceFlag, описывающий категорию сообще-
ния (команды). К этому типу относятся следующие значения:
Q traceQPREPARE — на сервер отсылается запрос для подготовки.
О traceQEXECUTE — команда на выполнение сервером запроса.
Q traceERROR — сервер возвращает сообщение об ошибке. Формат этого сообщения за-
висит от типа сервера БД.
Q traceSTMT— команда на выполнение одного из операторов ALLOCATE, PREPARE,
EXECUTE к FETCH.
Q traceCONNECT— операции подключения/отключения базы данных, в том числе опе-
рации распределения ресурсов.
G traceTRANSACT— операции, связанные с выполнением транзакций (BEGIN, COMMIT
и ROLLBACK).
Q traceBLOB — выполнение операций с Binary Large Object, таких как STORE BLOB,
GET BLOB и т.д.
О traceMISC — сообщения (команды), не вошедшие ни в одну из перечисленных выше
категорий.
Q traceVENDOR — вызов одной из функций API сервера (например, isc_attach_database
или других функций с приставкой isc_ для сервера Interbase).
Q traceDATAIN— параметры, передаваемые на сервер, например, для операторов Update
или Insert.
CD traceDATAOUT— получение данных от сервера.
Пользуясь сведениями, предоставляемыми аргументом CBInfo, можно записывать
в файл только сообщения определенного типа. Кроме того, событие OnLogTrace мож-
но применять для того, чтобы не засорять свойство TraceList ненужной информацией,
бессмысленно расходуя ресурсы. Ниже приведен код обработчика события OnLogTrace,
который записывает вновь добавленное в список TraceList сообщение в файл, а затем
очищает список:
void fastcall TForml::SQLMonitorlLogTrace(TObject *Sender,
pSQLTRACEDesc CBInfo)
Глава 4. Клиент-серверные базы данных 273
{
TSQLMonitor *Monitorl = dynamic_cast<TSQLMonitor *>(Sender);
Monitori->SaveToFile("D:\\Data\\trace.txt");
* Monitorl->TraceList->Clear();
Резюме
В этой главе были рассмотрены основные вопросы разработки клиентских при-
ложений, взаимодействующих с клиент-серверными базами данных. Упор делался на
использование локального сервера Interbase. Вначале были рассмотрены приемы ра-
боты с основными инструментами, поставляемыми с сервером Interbase, — утилитами
IBConsole и ISQL. Было показано, как с их помощью можно создавать и управлять база-
ми данных, таблицами, индексами, представлениями, доменами, триггерами и другими
объектами Interbase.
Большое внимание было уделено использованию баз данных Interbase. В частности,
был приведен обзор типов данных, поддерживаемых сервером Interbase, описаны приемы
манипуляции объектами Interbase (создание, удаление, модификация), создание системы
безопасности и управление ею.
Заключительная часть главы посвящена описанию основных приемов создания
клиентских приложений. Рассмотрены способы подключения к базам данных Interbase
при помощи механизмов доступа к данным dbExpress, IBExpress и ADO. Были подроб-
274 Borland C++ Builder 6. Разработка приложений баз данных
Р
ано или поздно при проектировании приложений БД возникает необходимость соз-
давать различные отчетные и аналитические формы. Такие формы позволяют по-
лучать твердые копии (т.е. распечатывать на бумаге) заранее сформированных набо-
ров данных (таблиц, запросов и т.д.). Для генерации отчетных форм (или просто отчетов)
предназначен набор компонентов QuickReport, расположенный на одноименной вкладке,
а для генерации аналитических форм — набор компонентов Decision Cube. В этой главе
мы рассмотрим основные приемы создания отчетных и аналитических форм с использо-
ванием компонентов из обоих наборов. Начнем с QuickReport.
Создание отчетов
при помощи компонентов QuickReport
Quick Report— это генератор отчетов, разработанный фирмой QuSoft AS
(www.qusoft.com) и распространяемый фирмой QBS Software (www.qbss.com). Вместе
с C++ Builder поставляется версия 3.0.9, однако по одному из указанных выше Web-адре-
сов можно выполнить обновление до версии 5.0 Professional.
На вкладке QReport имеется 23 компонента. Сразу разобраться в таком многообразии
непросто. Поэтому лучше начать с простейших приемов создания отчетов. Именно так
мы и поступим.
276 Borland C++ Builder 6. Разработка приложений баз данных
Рис. 5.1. Первое окно мастера генерации отчета предназначено для выбора типа отчета
Первое окно мастера, по замыслу его авторов, предназначено для выбора типа отчета.
По идее, выбрать тип отчета можно было бы при помощи комбинированного списка Select
Report. Однако, во всяком случае в моей версии C++ Builder, в этом списке содержится
только одна позиция, а именно List Report (табличный отчет). Так как других вариантов
нет, просто нажмем кнопку Start Wizard. На экране будет отображено второе окно мастера
(рис .5.2).
Второе окно мастера генерации отчетов предназначено для выбора источника данных.
Возможностей здесь тоже немного. Вы можете выбрать базу данных или псевдоним (ком-
бинированный список A lias or directory), после чего — имя таблицы из комбинированного
списка Table name. Тогда вам станет доступен список полей в элементе Available fields.
Пользуясь кнопками со стрелками («>», «»», «<» и ««»), можно указать имена полей,
которые будут присутствовать в отчете. Список этих полей будет отображен в элементе
Selected fields. Выбрав нужные поля, нажмите кнопку Finish. На этом работа мастера за-
вершается. Вашему вниманию будет представлена новая форма, которая и служит контей-
нером для созданного отчета (рис. 5.3).
Глава 5. Документирвоание и анализ информации 277
rSelect atable
^lias of directory JMyData •*•[ Browse
Jabiename ltems.DB
г Select Fields-:"
Bailable fields
ItemNo
Item
Category
Description
Index
Price
Fmish Cancel
Швш
StemNIo "Г ]tem
Page Foote:
' дшиШ
Рис. 5.3. Форма-контейнер отчета в режиме конструктора
Вы можете убедиться, что форма-контейнер отчета выглядит не так, как обычная фор-
ма. Первое, что бросается в глаза, — в этой форме есть горизонтальная и вертикальная
278 Borland C++ Builder 6. Разработка приложений баз данных
разметка, при помощи которой можно размещать все элементы управления отчета на ст|
нице так, как нужно для конечной цели. На самом деле, так выглядит поверхность объе»
TQuickRep, который выравнивается по всей поверхности формы. Именно этот компоне
и является основой для построения отчета. Еще один важный момент: для создания <
чета мастер использует механизм BDE. В частности, для подключения к набору данм
применяется компонент ТТаЫе из набора BDE.
В результате работы мастера кроме формы нового приложения Forml будет созда
и новая форма-контейнер для отчета Form2. Чтобы задействовать созданный мастер<
отчет, нужны еще несколько шагов. Добавьте заголовок формы Form2 в файл фор»
Forml. Для этого выделите форму Forml и в меню File выберите пункт Include Unit He
Из появившегося диалогового окна выберите модуль Unit2 и нажмите кнопку ОК. Дале
на форму Forml поместите кнопку (компонент TButton). В тело обработчика события н
жатия этой кнопки введите следующий оператор:
Form2->QuickRepl->Preview();
Откомпилируйте получившееся приложение и запустите его на выполнен»
В результате на экране появится форма Forml, содержащая кнопку. Нажав эту кнопку, в:
отобразите на экране окно Print Preview, содержащее готовый отчет (рис. 5.4).
Print Preview
И 4 Ц
и последней странице. Нажатие кнопки Print Setup вызывает на экран диалоговое окно
настройки свойств печати и принтера. Отправить отчет на печать можно, нажав кнопку
Print. Кроме того, отчет можно сохранить в файле с расширением qrp (в формате файлов
отчетов Quick Report). Сделать это можно, нажав кнопку Save Report. Загрузить отчет из
t/r/7-файла в окно Print Preview можно при помощи кнопки Load Report.
Вернемся еще раз к форме Form2, которая служит контейнером для отчета, сгенериро-
ванного мастером. Обратите внимание на то, что в качестве источника данных для отчета
мастер использовал компонент ТТаЫе из набора BDE. Механизм BDE используется масте-
ром не только для локальных таблиц, как в приведенном выше примере, но и для таблиц
баз данных в формате SQL. Это можно проверить, подключившись, например, к одной из
таблиц базы данных Interbase, поставляемой в качестве примера (IBLocal).
Итак, подытожим наши наблюдения. При помощи мастера можно сделать только
один, самый простой тип отчета (во всяком случае, в стандартной поставке Quick Report).
В качестве механизма доступа к данным мастер использует BDE, как для локальных та-
блиц, так и для таблиц баз данных в формате SQL. При подключении отчета к таблице ба-
зы данных SQL у вас возникнут проблемы с указанием имени пользователя и пароля, так
как окно для их ввода отображено не будет, и вы получите сообщение об ошибке. В ходе
работы мастер предлагает выбрать в качестве источника данных только таблицы БД, в то
время как в подавляющем большинстве случаев в этом качестве используются запросы.
Учитывая все сказанное, можно сделать вывод, что использовать мастер генерации от-
четов нецелесообразно. Поэтому в следующем разделе мы рассмотрим, как создать про-
стой отчет в режиме конструктора самим.
Листинг 5.1. Отрывок сценария, который можно использовать для определения табли
SALES и SALEPARTS
SET TERM ;л
SALEPARTNO PKEYFIELD,
SALE FKEYFIELD,
ITEM FKEYFIELD,
QTY SMALLINT DEFAULT 1,
PRIMARY KEY (SALEPARTNO)
);
ALTER TABLE SALEPARTS ADD CONSTRAINT SALEPARTSTOITEMS FOREIGN KEY (ITEM)
REFERENCES ITEMS (ITEMNO)
ON UPDATE CASCADE ON DELETE CASCADE;
Глава 5. Документирование и анализ информации 281
SET TERM ;A
Откройте набор данных, задав для свойства Active объекта SQLDataSetl значение
true. Теперь можно подключить к набору данных и отчет. Для этого свяжите свойство
DataSet объекта TQuickRep с набором данных SQLDataSetl (если этот объект расположен
в модуле данных, то вначале следует включить в модуль отчета заголовок модуля данных).
Обратите внимание на то, что отчет напрямую подключается к набору данных, минуя объ-
ект TDataSource.
Теперь можно приступить к непосредственному конструированию отчета. Весь отчет
состоит из набора полос различного типа, размещенных в пределах объекта TQuickRep.
Наиболее важна полоса детализации (DetailBand). Элементы управления, расположенные
на ней, последовательно выводятся в отчет для каждой строки набора данных. Для оформ-
ления простого табличного отчета, который мы пытаемся сконструировать в этом разделе,
нам понадобятся еще две полосы: заголовков столбцов (ColumnHeaderBand) и заголовка
отчета (TitleBand). Первую из них удобно применять для указания заголовков столбцов
отчета, так как содержащиеся в ней элементы управления повторяются в начале каждой
страницы. Элементы управления, размещенные во второй полосе, выводятся только
в начале отчета, поэтому она удобна для указания заголовка отчета. Все перечисленные
типы полос — это экземпляры компонента TQRBand.
Включить упомянутые полосы в отчет проще всего при помощи свойства Bands объ-
екта TQuickRep. В состав свойства Bands входит 6 подсвойств логического типа, наиме-
нования которых начинаются с префикса Has. Задавая какому-либо подсвойству значение
true, мы включаем соответствующую полосу в отчет. В нашем случае значение true нужно
задать подсвойствам HasDetail, HasColumnHeader и HasTitle.
Осталось немногое. В полосе детализации разместите пять экземпляров компонента
TQRDBText (вкладка QReport), выровняв их по горизонтали. Компонент TQRDBText яв-
ляется аналогом компонента TDBEdit с вкладки Data Controls. Для свойства AutoSize всех
объектов TQRDBText задайте значение true. Это позволит выровнять размеры элементов
управления в зависимости от размеров значений, которые будут в них отображаться,
а также в соответствии с требованиями к оформлению отчета. Кроме того, каждый из
объектов TQRDBText нужно связать с набором данных SQLDataSetl (свойство DataSet).
После этого, пользуясь свойством DataField нужно задать для каждого объекта TQRDBText
поле, значения которого и будут в нем отображаться. Из списка, прикрепленного к этому
свойству, последовательно (слева направо) выберите для каждого из объектов TQRDBText
такие имена полей: CheckN, SaleDate, LName, Item, Qty.
282 Borland C++ Builder 6. Разработка приложений баз данных
|ТН* Продажи
,__::
за июнь. J*5>ft
_ _ „ _ . _
t"heckNo!TfealeDate Employee Htem
bhlECKN ^ALEDATEtNAME ITEM
.__-*=-- - _;• « *
Компонент TQuickRep
Форма, на которой размещен экземпляр объекта TQuickRep, становится контейнером
для отчета. В этом случае она называется формой отчета. Ранее мы уже использовали
основные свойства компонента TQuickRep — DataSet и Bands. Свойство DataSet предна-
значено для организации связи отчета с одним из наборов данных любого из механизмов
доступа к данным (BDE, dbExpress и т.д.).
Использование свойства Bands — один из путей добавления в отчет полос, которые
являются экземплярами компонента TQRBand. Развернув свойство Bands, можно по-
лучить доступ к любому из подсвойств логического типа, имеющих в наименовании
префикс Has. Меняя значения этих подсвойств, можно добавить или удалить из отчета
следующие полосы (курсивом выделены имена полос, т.е. объектов типа TQRBand, а не
компонентов).
Глава 5. Документирвоание и анализ информации 283
•~l'. Процажи за июнь 2002 г.
|а|Щев
1
*
И < > И | т т \ У ~ gosej
1
'
\^°*&*~
Enter expression;
SQLDataSetl .QTY" SQLDataSetl PRICE
Рис. 5.8. Диалоговое окно, облегчающее вставку в выражение поля набора данных
значительно облегчить работу по вводу нужного выражения. Основную часть окна
мастера выражений занимает элемент управления MEMO, в котором и формируется
выражение (рис. 5.7).
Выражение можно вводить самому, а можно воспользоваться кнопками в нижней части
окна мастера выражений. Посредством кнопки Database field можно отобразить на экране
вспомогательное окно, при помощи которого в выражение вставляют нужное поле набо-
Глава 5. Документирвоание и анализ информации 287
ра данных. В левой части этого окна располагается список доступных наборов данных,
а в правой — список полей выбранного слева набора данных (рис. 5.8). К сожалению,
в этом списке есть только те наборы данных, которые расположены на этой форме отчета.
Если набор данных расположен, например, в отдельном модуле данных, то даже несмотря
на включение соответствующего заголовочного файла в форму отчета, сослаться на него,
как это делается в подобных случаях, нельзя.
Lxpression Wizard
-Select function-
Category
COLUMNNUMBER
REPORTTITLE
APPSTARTTIME
APPSTARTDATE
APPNAME
JReport title);
'Page Header
r
"SaleDate "CheckNo Item "Quantity Price Cosi
T
pet "SALEDATE^CHECKN ] JTEM QTY T>RICE "^QLDataSef
r
"Итого: SUM(SQLDataSet1.QTY'
L
' '
Page F,it>!«(
10 За*. 319
290 Borland C++ Builder 6. Разработка приложений баз данных
будет отображено стандартное окно настройки принтера (Printer Setup). В нем можно
выбрать другой принтер, а также изменить его свойства и свойства печати (количество
копий, то, нужно ли разбирать страницы по копиям и т.д.).
Настроить параметры принтера и страницы можно при помощи свойств объектного типа
PrinterSettings и Page. Оба свойства доступны как в режиме конструктора, так и в режиме
выполнения приложения. Свойство PrinterSettings имеет тип TQuickRepPrinterSettings.
Являясь производным от класса TQRPrinterSettings, класс TQuickRepPrinterSettings пу-
бликует только пять его свойств: Copies (количество копий), Duplex (задает печать с обеих
сторон листа бумаги, если позволяет принтер), Firs/Page и LastPage (первая и последняя
страница диапазона печати), OutputBin (источник бумаги).
При помощи свойства Page можно настроить параметры страниц отчета. Свойство со-
держит 11 подсвойств, соответствующих элементам управления стандартного диалогового
окна Параметры страницы (Page Setup). Вы можете настроить следующие параметры:
Q TopMargin, LeftMargin, BottomMargin, RightMargin — верхнее, левое, нижнее и правое
поля страницы;
U Column и ColumnSpace — количество колонок отчета и расстояние между ними;
Q PaperSize — размер бумаги; возможные значения: А4, A3, А5, В4, Custom и т.д.;
Q Length — длина (высота) страницы; чтобы значение этого свойства принималось во
внимание, необходимо установить свойство PaperSize в значение Custom;
Q Width — ширина страницы;
Q Orientation — ориентация страницы: альбомная (poLandscape) или книжная (poPortrait);
Q Ruler — если это свойство имеет значение true, то печатается также и сетка (разметка)
отчета.
Еще одно свойство объектного типа — Options, — уже упоминалось. Точнее, упо-
минались его подсвойства FirstPageHeader и LastPageFooter. Есть еще одно подсвой-
ство — Compression. Если задать для него значение true, то отчет в процессе генерации
будет упаковываться в памяти.
Через свойство Printer можно получить доступ к объекту TQRPrinter, при помощи ко-
торого можно управлять свойствами принтера, в частности, можно получить доступ к его
канве. Еще одно полезное свойство объекта TQuickRep — PrintlfEmpty. Если его значение
true, будут печататься все полосы (колонтитулы, заголовок отчета и т.д.), даже если соот-
ветствующий набор данных пуст.
Компонент TQuickRep имеет еще достаточно много интересных свойств и методов, од-
нако здесь мы их рассматривать не будем. Тех, которые уже были описаны в этом разделе,
достаточно для создания отчетов приемлемого уровня. С остальными разобраться неслож-
но и самостоятельно. Перейдем к краткому описанию событий компонента TQuickRep.
Событий у TQuickRep всего семь. Событие OnPreview генерируется после вызова
одного из методов Preview. Если вы создали для этого события обработчик, то вам при-
дется самому позаботиться, чтобы отображать страницы отчета на экране: в этом случае
окно Print Preview загружаться не будет. В частности, если обработчик события пуст, то
в результате вызова метода Preview ничего не произойдет. Обработчик события OnPreview
можно использовать для организации собственного представления страниц отчета в режи-
ме предварительного просмотра. Более подробную информацию об использовании собы-
тия OnPreview можно найти на сайте фирмы QuSoft по адресу www.qusoft.com.
Глава 5. Документирвоание и анализ информации 291
Полосы отчета
В предыдущем разделе уже рассказывалось, как при помощи свойства Bands ком-
понента TQuickRep можно добавить в отчет полосы верхнего и нижнего колонтитула,
а также полосы детализации, итогов, заголовков столбцов и отчета. То же можно проде-
лать, размещая на поверхности компонента TQuickRep экземпляры компонента TQRBand.
Основное свойство компонента TQRBand— это BandType типа TQRBandType. Можно
выбрать один из типов полос, речь о которых уже шла ранее, и которые можно было бы
добавить в отчет при помощи свойства Bands. Этот тип полос можно добавить в отчет,
выбрав из списка, прикрепленного к свойству BandType объекта TQRBand, одно из значе-
ний: rbPageHeader (верхний колонтитул), rbTitle (полоса заголовка отчета), rbColumnH-
eader (заголовок столбцов), rbDetail (полоса детализации), rbSummary (полоса итогов) и
rbPageFooter (нижний колонтитул).
Список, прикрепленный к свойству BandType объекта TQRBand, содержит еще не-
сколько значений:
Q rbSubDetail— зарезервирован для использования экземпляром компонента
TQRSubDetail. Самим этот тип для полосы задавать не рекомендуется.
Q rbGroupFooter — полоса, которая будет использоваться в качестве нижнего колонти-
тула полосы группировки (экземплярами компонентов TQRGroup и TQRSubDetail).
Печатается после очередной полосы детализации, когда меняется определяющее груп-
пу значение.
292 Borland C++ Builder 6. Разработка приложений баз данных
которой приводит к отображению на экране диалогового окна Expression Wizard (см. рис.
5.7). Работа с этим окном'уже описывалась ранее. В качестве выражения можно просто
задать какое-нибудь поле набора данных. В нашем случае следует задать поле SaleDate.
Смысл выражения, задаваемого в свойстве Expression, таков: полоса TQRGroup печатает-
ся каждый раз, как только меняется его значение. Это свойство полосы TQRGroup в соче-
тании с сортировкой по полю SaleDate, заданной в операторе SQL объекта Query 1, дает
возможность организовать группировку записей набора данных по выбранному критерию
(в данном случае — при изменении значения даты продаж).
После задания значения для свойства Expression полоса заголовка группы переместит-
ся, расположившись непосредственно перед полосой детализации (хотя иногда бывает,
что полоса заголовка группы размещается там сразу после добавления на отчет). Теперь
можно добавить на полосу TQRGroup элементы управления, которые будут печататься
в отчете перед очередной серией (группой) записей. Обязательно следует разместить эле-
мент управления, представляющий определяющее группу выражение. В нашем случае на
полосе заголовка группы нужно разместить экземпляр компонента TQRDBText, который
будет отображать значения поля SaleDate. Проще всего сделать это, скопировав в буфер
сответствующий элемент из полосы детализации и вставив его затем в полосу заголовка
группы. Чтобы выделить значения этого элемента, можно задать для его шрифта стили
fsBold wfsltalic. Здесь же можно расположить и другие элементы управления, какие вы
посчитаете нужными.
Чтобы после очередной группы записей выводились некие итоговые значения по
данной группе, следует воспользоваться полосой нижнего колонтитула группы (Group
Footer). Это та самая полоса TQRBand, которую мы добавили в отчет ранее, задав для ее
свойства BandType значение rbGroupFooter. Эта полоса все еще остается в самом низу
отчета. Полосу нижнего колонтитула группы нужно связать с соответствующей полосой
заголовка группы. Для этого выделите полосу TQRGroup и из списка, прикрепленного
к свойству FooterBand, выберите наименование полосы нижнего колонтитула группы.
В результате полоса Group Footer займет положение непосредственно после полосы дета-
лизации, перед которой находится соответствующая полоса заголовка группы.
Теперь можно разместить в полосе нижнего колонтитула группы необходимые эле-
менты. В нашем примере нужно отобразить итоговую сумму продаж за каждый день.
Проще всего достичь этого, скопировав в буфер соответствующий элемент управления
из полосы Summary и вставив его в полосу нижнего колонтитула группы. Далее, для того
чтобы элемент управления TQRDBText отображал итоговые значения именно для каждой
группы, а не накопленные значения, начиная с начала отчета, следует задать для свой-
ства Reset After Print элемента TQRDBText значение true. Тогда значение этого элемента
управления будет сбрасываться в ноль, т.е. итоговое значение для каждой группы будет
накапливаться начиная с нуля.
Отчет готов. Обратите внимание на то, что все необходимые действия описывались
гораздо дольше, чем выполнялись. Итог наших усилий представлен на рис. 5.13.
Аналогично можно добавить любое количество уровней группировки. Например, при-
веденный выше отчет с группировкой по полю SaleDate можно модернизовать, добавив
еще один уровень группировки по номеру чека. Для этого нужно добавить к отчету еще
одну полосу TQRGroup и еще одну полосу TQRBand, для которой свойство BandType уста-
новлено в значение rbGroupFooter. Для свойства Expression полосы TQRGroup задайте
значение CheckN. Скопировав соответствующий элемент управления из полосы детали-
294 Borland C++ Builder 6. Разработка приложений баз данных
A.
01.0U02
1 oi.oe.02 Сг*-1вв Epson Stylus Photo 810 3 122.0O$ зве
2 01.06.02 сн(-1ве Sock si 423 (Pentium 4) Intel D8906BC 3 132 .00$ 396
—
3 01.06.02 ci*-iee Canon CanoScan N95611 3 87.00» 261
4 01.06.rJ2 Chk-166 Epson Stylus Color CSO 4 185.00» 740
1763
02.0а02
5 02.00.02 Ct*.172 ISPOS") Samsung855DF 3 298.00» 894
6 О2.06.О2 CN<-174 Звуковая карта CMedia Thundering 4 12.00» 43
7 02.06.02 Cr*-iee Sooket478(Pentium4)ChaintechCT-9BIA 2 107.00» 214
8 02.06.02 Cr*-170 Pinnacle Mto Video Studio DC10 Plus 2 201.00» 432
9 02.06.02 Cr*-172 Epson Stylus Cob( СТО 1 148.00$ 148
10 02.06.02 Chk.168 DlrVM 128 H* PC133. HCP 4 34.00» 13в
11 02.06.02 CHi-171 Socket 370 FC-PGA/PPGAPC Partner 1 58.00» Э9 _u
Page i of 1 ? -;:';*; |S ' J |
Рис. 5.13. Простой пример табличного отчета
с использованием группировки по датам продажи
зации, добавьте его в новую полосу заголовка группы. Таким образом, записи набора дан-
ных будут группироваться не только по дате продажи, но и по номеру чека (т.е. по каждой
продаже), причем номер чека будет печататься перед каждой группой записей.
Свяжите вновь добавленную полосу нижнего колонтитула группы с соответствующ-
ей полосой заголовка группы. Для этого задайте ее имя в качестве значения свойства
FooterBand соответствующей полосы заголовка новой группы. Далее, в полосе нижнего
колонтитула новой группы следует разместить элемент управления, который будет ото-
бражать итоговые значения по этой группе. Достаточно скопировать вычисляемый эле-
мент управления из группы Summary и вставить его в полосу нижнего колонтитула новой
группы. Как и в приведенном выше примере, для свойства ResetAfterPrint этого элемента
управления задайте значение true. Таким образом, итоговое значение будет накапливаться
вплоть до изменения значения номера чека. Подобный отчет приведен на рис. 5.14.
Кроме рассмотренных в этом разделе полос TQRBandvi TQRGroup, на вкладке QReport
содержится еще три компонента, представляющие собой различного вида полосы.
Компонент TQRSubDetail предназначен для организации связи типа Master/Detail. Однако
для организации подобного типа связей гораздо проще использовать соответствующий
набор данных (например, TQuery или TSQLDataSef).
Компонент TQRChildBand представляет собой дочернюю полосу, которая при помощи
свойства ParentBand присоединяется к другой полосе — родительской. Дочерняя полоса
выводится в отчет в тех же случаях, что и родительская, сразу после нее. Проще всего до-
Глава 5. Документирвоание и анализ информации 295
01.06.02
Chk-166
1763
1763
02.06.02
Chk-167
бавить дочернюю полосу в отчет можно, задав значение true свойству HasChild полосы,
которая должна быть родительской. Чтобы дочерняя полоса всегда печаталась на той же
странице, что и родительская, задайте имя последней в свойстве LinkBand.
Полоса TQRStringsBand предназначена для отображения в отчете списка строк,
который задается при помощи свойства Items, имеющего тип TStrings*. При помощи
этого компонента, например, можно распечатать содержимое текстового файла. Полоса
TQRStringsBand печатается для каждого элемента списка строк. После размещения поло-
сы в отчете в свойство Expression компонента TQuickRep добавляется еще один элемент,
имеющий то же имя, что и полоса. С его помощью можно задать способ доступа к отде-
льным строкам списка.
Все компоненты полос имеют два события: BeforePrint и AfterPrint. Первое генери-
руется непосредственно перед печатью полосы. В тело обработчика этого события по
ссылке передается аргумент PrintBand логического типа. Если в обработчике события
BeforePrint задать для этого аргумента значение false, то полоса печататься не будет.
Событие AfterPrint генерируется сразу после печати полосы, даже если печать была отме-
нена в обработчике события BeforePrint. При помощи аргумента BandPrinted логического
типа можно определить, была ли полоса выведена в отчет. Этим можно воспользоваться
для корректировки размеров и положения элементов управления на странице.
Ниже приведен пример обработчика события BeforePrint, который используется для
фильтрации исходного набора данных:
296 Borland C++ Builder 6. Разработка приложений баз данных
Decision Cube
Компоненты, расположенные на вкладке Decision Cube палитры компонентов, пред-
назначены для многомерного анализа набора данных. Возможности этих компонентов
напоминают о сводных таблицах Microsoft Excel, а также о перекрестных запросах или
сводных таблицах из набора Microsoft Office Web Component. Кто хорошо знаком с мето-
дами использования этих инструментов, легко разберется и с возможностями механизма
Decision Cube.
На вкладке Decision Cube всего шесть компонентов: TDecisionCube, TDecisionQuery,
TDecisionSource, TDecisionPivot, TDecisionGrid и TDecisionGraph. TDecisionQuery — это
аналог компонентов, которые представляют собой программную оболочку вокруг запро-
са SQL. Строка запроса указывается в свойстве SQL, а имя базы данных — в свойстве
DatabaseName. Открыть связанный с объектом TDecisionQuery набор данных можно, за-
дав свойству Active значение true. Интерфейс компонента TDecisionQuery (набор свойств,
методов и событий) практически ничем не отличается от интерфейса компонента TQuery
из набора BDE.
298 Borland C++ Builder 6. Разработка приложений баз данных
Dimensions/Summari
List of Available Field
Sales.SaleNo
Sales.SaleDate Items. Item
Sales.CheckN
Sales.Employee
Sales.Comment
Saleparts.SalePartNo
Saleparts.Sale Summaries:
Seleparts.ltem SUM( Items.Price *Saleparts|
Saleparts.Qty
Items.ItemNo
Items.ltem
Items.Category
ЕЭ
£ЮН I^^^SSSi
:май 2002a;p Д июй.2002 июл,2002 .aer,2002
Ь Fujitsu f» 792.00$ 396.00$
3.8") LG St 113.00$ 113.00$ 1,017.00$
3.8")LGSt 928.00$ 580.00$
wnsung15 1,482.00$ 6,422.00$ 988.00$
unsung 15 2,560.00$ 2.048.00$ 1,024.00$ J
imsung 1 5 487.00$ 1,948.00$
imsung 55 1,120.00$ 980.00$ 700.00$ 420.00$
984.00$ 492.00$ —J
unsung 55 615.00$
imtron 5SE 714.00$ 714.00$ 238.00$
inySDM-N 4,805.00$ 961.00$ 961.00$ |fj
3") Sarnsti 2,079.00$ 756.00$
S"VC;»m<!i, 1 тля nnt Г Т5Н ПП« 1 КАП ПП«
'"""^:
-ах
.~i—,j-..-ii
TOecisionGraph
• 255.718 май, 2002
• 275.557 июн. 2002
П 334,839 июл, 2002
• 128,592 авг. 2002
Ш Form!
:
т т в Item [Jiijl SaleDate
I . " 0 ишззвши
(змнвшин
:
10.0Gb Fujitsu f>/
май, 2002
'
ива 2002 :
792.00$ 396.00$
:15"(13.8")LGSt 113.00$ 113.00$ 339.00J
j15"(13.8")LGSt 464.00$ 290.00$
; 15" Samsung 15 1,402.00$ 1,605.50$ 988.00$
! 15" Samsung 15 853.33$ 2.048.00$ 1.024.00$
15" Samsung 15 -187.00$ 974.00$
;•• i15" Samsung 55 373.33$ Э26.Б7$ 350.00$ 210.00$ .•„;„ 1
i15" Samsung 55 307.50$ 328.00$ 492.00$
j15"Samtron5BE 23B.QO$ 238.00$ 238.00$
;15"SonySDM-h 1,B01.67t 961.00$ 961.00$
a
;17"(16")Samsu 519.75$ 378.00$ ЧГ
:
' 1
Резюме
В этой главе рассматривались вопросы построения отчетных и аналитических форм
при помощи наборов компонентов Quick Report и Decision Cube. Вначале было показа-
но, как использовать мастер для создания простых табличных отчетов, а также описаны
Глава 5. Документирвоание и анализ информации 303
основные недостатки этого способа. Был приведен пример создания отчета только в режи-
ме конструктора. Особое внимание было уделено компоненту отчета TQuickRep, а также
компонентам полос. Кроме того, были изучены основные свойства наиболее важных
компонентов, предназначенных для отображения различной информации в полосах от-
чета (TQRLabel, TQRDBText, TQRExpr и TQRSysDatd). Показано, как при помощи полос
TQRBand и TQRGroup создать отчет с группировкой. Приведен краткий обзор всех компо-
нентов Quick Report, не рассмотренных ранее.
В конце главы показано, как при помощи компонентов Decision Cube конструировать
аналитические формы, удобные для многомерного анализа данных. Рассмотрены компо-
ненты TDecisionCube, TDecisionQuery, TDecisionSource, TDecisionGrid, TDecisionGraph
и TDecisionPivot и приведены примеры их использования.
TSQLQuery 261
TSQLStoredProc 261
Агрегатные функции 168 TStatusBar 75,130,284
TTable 34,42,159
В TUpdateSQL 182
Внешнее объединение 164
Внутреннее объединение 164
Вычисляемые поля 85 M
Макро
ARRAYOFCONST 62,68
Генератор 217 OPENARRAY 62,68
Метод
д Abort 69
AddAlias 100,119,120
Декартовая связь 163
AddFieldDef 101
Домен 209
AddlndexDef 101
И AddStandardAlias 100,119,120
AnsiPos 64
Исключение 229 Append 68
ApplyRange 51,264
К ApplyUpdates 94
Класс 34 Assign 80
TField 75,78 Bookmark Valid 71
TDataSet 34 Cancel 67
TDBGridColumns 143 CancelRange 51, 119
Компонент 34 CancelUpdates 94
TBatchMove 132,189 Clear 81
TDataSource 42,72,131,262 Commit Up dates 94
TDBChart 131 Date 88
TDBCtrlGrid 135,270 DecodeDate 88
TDBGrid 136,291 Delete 68
TDBLookupComboBox 91,131,263 DeleteDatabase 121, 144
TDBLookupListBox 130 DeleteTable 67,120
TDBNavigator 40,129 DropConnections 121
TDBRadioGroup 131 Edit 68
TEdit 42 EditKey 60,247,254
TListView 49,105,246, 282 EditRangeEnd 51,121,264
TSession 114, 115 EditRangeStart 51, 118
TSQLConnection 236,240 FieldByName 49, 118
Предметный указатель 305
FieldByNumber 49,111 CREATE EXCEPTION 222,229
FindFirst 63,267 CREATE GENERATOR 218
FindKey 61 CREATE INDEX 175,211,216
FindLast 63,146,155 CREATE PROCEDURE 223
FindNearest 61,120 CREATE ROLE 232
FindNext 63,110,261 CREATE TABLE 173,210
FindPrior 63 CREATE TRIGGER 219
GetFieldNames 111 DROP INDEX 176
GotoKey 60 EXCEPTION 222,229
GotoNearest 61 EXECUTE PROCEDURE 222
Insert 67 FOR SELECT ... DO 221
IsAlias 100 Full Outer 165
IsEmpty 88 Inner 164,230
IsNull 88 Left Outer 164
LoadFromFile 143 Outer 164
LoadParamsFromlniFile 236 REVOKE 232
Locate 62,142 Right Outer 165,215
Lookup 62 SET GENERATOR 219
MoveBy 41,110 SUSPEND 222,225
OpenDatabase 121,267 WHEN 222
Post 67 WHEN ...DO 229
Prepare 182 WHENGDSCODE 230
RemoveAllPasswords 118 WHEN SQLCODE 230
RenameTable 67 Операция
RestoreDefaults 143 Between 165
SaveConfigFile 120 Exists 165
SaveToFile 143 In 166
SetKey 60 Is [Not] Null 167
SetRangeEnd 51,181 Like 166
SetRangeStart 51
StartTransaction 110 П
Substring 88 Поля 82
Модель динамические 82
MyBase 263 постоянные 82
портфельная 263 подстановки 89
Модуль данных 103 Построитель запросов 177
н Предложение
From 162
Набор атрибутов 20,24 Having 168
Group By 168
О Order By 169
Select 161
Оператор
Представления 226
ALTER TABLE 175,227
Псевдоним 20,21,106
ALTER TRIGGER 223
CREATE DOMAIN 209
306 Borland C++ Builder 6. Разработка приложений баз данных
Readonly 69,190
RecNo 156
Редактор полей 48,82 RequestLive 179,263
Редактор столбцов 136 RowsAffected 182
Режим кэширования изменений 93 SessionName 115,235
Роль 231 State 72
UpdateObject 187,188
UpdatesPending 94
Свойства ViewStyle 49,106,246
ActiveStatements 247 Связь
AllowStreamedConnected 238 один-ко-многим 73
Aslnteger 53 Словарь 20, 24
AutoEdit 74 Событие
AutoGenerateValue 79 AfterPost 96,158
Bof 41 AfterScroll 44,250
Bookmark 70,130 BeforeApplyUpdates 267
ButtonStyle 138,263 BeforeUpdateRecord 269
CachedUpdates 93,183 OnCalcFields 85,149,152
CanModify 69 OnCellClick 158
Columns 136 OnChange 81
DataType 79,138 OnClose 59,113,273
DefaultExpression 80 OnColEnter 158
Delta 267,282 OnColumnMoved 158
DriverName 106,267 OnCreate 59
Eof 41 OnDataChange 74,114,249
FieldDefs 82,101 OnDrawColumnCell 153
FieldKind 79,150,270 OnEditButtonClick 139,140, 267
Fields 47,106 OnFilterRecord 60, 63,188, 268
Filter 57 OnGetText 81,116
Filtered 57,105 OnReconcileError 78,267
FilterOptions 57,58,140 OnSetText 82
IndexDefs 101 OnStateChange 74,114
IndexFieldNames 50, 60,115, 246 OnUpdateData 74,191
IndexName 50,60,130 OnUpdateError 78,95
Islndex 80 OnUpdateRecord 94
IsNull 80 OnValidate 81,117, 118, 271
KeepConnection 235
KeepConnections 116
ListSource 130 Тип
LoadParamsOnConnect 235 TLocateOptions 62
LookupCache 91,267 Транзакция 109
Options 144
Триггер 219
Предметный указатель 307
Функции
Cast 172
Extract 172,249,251
Lower 171
Substring 171
StrToIntDef 44
Trim 172
Upper 171
X
Хранимые процедуры 223
ЛАТИНСКИЙ АЛФАВИТ
Database Desktop 26
SQL Explorer 20
Стивен Прага
Язык программирования С.
Лекции и упражнения. Учебник
ISBN 5-93772-049-fl
SAMS
Книга Стивена Праты «Язык программирования С. меры протестированы, а исходные коды программ
Лекции и упражнения. Учебник» — подробный учеб- можно найти на web-сайте www.diasoft.kiev.ua.
ник по языку С, построенный на основе принципа
Рассмотрены все нововведения, привнесенные в
«от простого к сложному». В начальных главах опи-
язык С новейшим стандартом С99. Причем теорети-
сываются элементарные конструкции языка (опера-
ческие положения иллюстрируются конкретными
торы, выражения и т.д.), в последних главах рассмат-
практическими примерами.
риваются сложные структуры данных. Здесь вы най-
дете полное описание синтаксиса языка, основных Книга будет полезна начинающим и опытным про-
приемов и методов программирования, а также при- граммистам, использующим язык программирования
меры реальных коммерческих программ. Все при- С в своей повседневной деятельности.
Краткое оглавление
Глава 1. Готовимся изучать язык С
Глава 2. Введение в язык С
Глава 3. Представление данных в языке С
Глава 4. Строки символов и форматированный ввод/вывод
Глава 5. Операции, выражения и операторы
Глава 6. Управляющие операторы языка С: циклы
Глава 7. Управляющие операторы языка С: ветвление и безусловные переходы
Глава 8. Символьный ввод/вывод и перенаправление
Глава 9. Функции
Глава 10. Массивы и указатели
Глава 11. Символьные строки и строковые функции
Глава 12. Классы хранения, связывание и управление памятью
Глава 13. Файловый ввод/вывод
Глава 14. Структуры и другие формы данных
Глава 15. Дополнительные сведения о битах
Глава 16. Препроцессор и библиотека языка С
Глава 17. Расширенное представление данных
Приложение А. Ответы на контрольные вопросы
Приложение В. Справочник по языку С
Глоссарий. Словарь терминов по языку С
Предметный указатель
Стивен Прата
Язык программирования C++.
Лекции и упражнения. Учебник
ISBN 5-93772-073-3
SAMS
В новом издании популярной книги известного авто- физм, виртуальные функции, стандартная библио-
ра и специалиста по C++ рассматривается устояв- тека шаблонов STL, RTTI и др.
шаяся версия языка, соответствующая стандарту Практические задания к главам книги составлены на
ISO/ANSI. Изложение материала не привязано ни к основе реальных проектов. Издание рассчитано на
какой конкретной реализации C++. В основе изло- пользователей с любым уровнем подготовки. Для на-
жения материала лежит оригинальная методика, чинающих эта книга послужит превосходным учеб-
разработанная автором и неоднократно проверен- ником, а опытные программисты смогут воспользо-
ная на различных группах пользователей. Книга ох- ваться ею в качестве справочника, облегчающего их
ватывает обширный круг вопросов, необходимых для ежедневный труд.
создания полноценных программ на C++, — от зна-
Книгу можно рекомендовать как методическое посо-
комства с основами синтаксиса языка до многочис-
бие преподавателям высших и средних учебных за-
ленных новых функциональных возможностей C++,
ведений, а также в качестве учебника для студен-
таких как классы, объекты, наследование, полимор-
тов.
Краткое оглавление
Глава 1. Начальные сведения Глава 15. Дружественные конструкции, исключения и
Глава 2. Приступаемк изучению языка C++ прочее
Глава 3. Представление данных Глава 16. Класс string и стандартная библиотека шаб-
лонов
Глава 4. Составные типы данных
Глава 17. Ввод/вывод данных и работа с файлами
Глава 5. Циклы и выражения сравнения
Приложение А. Системы счисления
Глава 6. Операторы ветвления и логические опера-
ции Приложение В. Ключевые слова языка C++
Глава 7. Функции языка C++ Приложение С. Таблица кодов ASCII
Глава 8. Работа с функциями Приложение D. Приоритет операций
Глава 9. Модели памяти и пространства имен Приложение Е. Другие операции
Глава 10. Объекты и классы Приложение F. Шаблон класса STRING
Глава 11. Работа с классами Приложение G. Методы и функции библиотеки STL
Глава 12. Классы и динамическое распределение па- Приложение Н. Рекомендуемая литература
мяти Приложение I. Преобразование программного кода в
Глава 13. Наследование классов соответствии со стандартом ANSI/ISO C++
Глава 14. Повторное использование программного Приложение J . Ответы на вопросы для повторения
кода в C++
Клаус Микелсен
Язык программирования С#.
Лекции и упражнения.Учебник
ISBN 5-93772-052-0
SAMS
Книга содержит подробное описание языка програм- ны с исчерпывающей полнотой. Изложение матери-
мирования С#, а также соответствующие сведения, ала сопровождается многочисленными удачно подо-
касающиеся платформы .NET. Начиная с основ, бранными примерами, иллюстрирующими примене-
объектно-ориентированного программирования, ав- ние таких новых языковых концепций, как свойства,
тор последовательно излагает концепции, заложен- индексаторы, делегаты и события. Обилие графи-
ные в основу этого поистине "всеобъемлющего" язы- ческого материала способствует быстрому, глубоко-
ка. Его важнейшие составляющие - классы, объек- му и прочному усвоению языка С#.
ты, типы данных, циклы, массивы, пространства
имен, а также многие другие элементы языка описа-
Краткое оглавление
Глава 1. Компьютеры и программирование: основные концепции
Глава 2. Первая программа на С#
Глава 3. Обзор возможностей С#. Часть I
Глава 4. Обзор возможностей С#: Часть II
Глава 5. Первая объектно-ориентированная программа на С#
Глава 6. Типы. Часть I: простые типы
Глава 7. Типы. Часть II: операции, перечисления и строки
Глава 8. Поток управления. Часть I: операторы ветвления и общие концепции
Глава 9. Поток управления. Часть II: операторы итерации .
Глава 10. Массивы. Часть I: основные сведения
Глава 11. Массивы. Часть II: многомерные массивы, поиск и сортировка в массивах
Глава 12. Анатомия класса. Часть I: статические элементы класса и применение методов
Глава 13. Анатомия класса. Часть II: создание объектов и сборка мусора
Глава 14. Анатомия классов. Часть III: написание легко читаемого кода
Глава 15. Пространства имен, модули компиляции и сборки
Глава 16. Наследование. Часть I: основные понятия
Глава 17. Наследование. Часть II: абстрактные функции, полиморфизм, интерфейсы
Глава 18. Структуры
Глава 19. Обработка исключений
Глава 20. Делегаты и события
Глава 21. Препроцессорная обработка, XML-документация и атрибуты
Глава 22. Основы файлового ввода-вывода
Глава 23. Основы рекурсии
Приложение А. Ответы на вопросы и упражнения
Приложение В. Старшинство операций
Приложение С. Зарезервированные слова языка С#
Приложение D. Системы счисления
Приложение Е. Набор символов Unicode
Приложение F. Использование команд DOS в окне консоли
Роберт Седжвик
Фундаментальные алгоритмы на C++
Фундаментальные Анализ/Структуры данных/
алгоритмы Сортировка/Поиск
на C+ + ISBN 966-7393-89-5
tail 1-4
tarn
Ei ||
i|u 70x100/16, тв. переплет,
: tllllpnl 688с.
kn
unpm
70x100/16, тв. переплет
на <|ицш 496с,
Эта книга посвящена глубокому исследованию всех ос- Подробно рассматривается широчайший спектр фунда-
новополагающих концепций и алгоритмов, которые, не- ментальных алгоритмов на графах, в числе которых:
сомненно, относятся к категории «вечных». Тщательным поиск в орграфах, неорграфах и сетях; построение ми-
образом проштудировав их, вы получите знания, кото- нимальных остовных деревьев и кратчайших путей; вы-
рые никогда не устареют и которыми вы будете пользо- числение потоков в сетях с различными характеристи-
ваться всегда. ками. Большое внимание уделяется рабочим характе-
Краткость, точность, выверенность, актуальность, изо- ристикам алгоритмов, а также их математическому вы-
билие примеров и учебных заданий - вот лишь неболь- воду.
шой перечень очевидных достоинств книги. Иллюстра- Книгу можно использовать в качестве курса лекций (как
ция алгоритмов на одном из наиболее эффективных студентами, так и преподавателями), справочного посо-
языков программирования C++ лишний раз подчерки- бия или просто «романа», получая при этом ни с чем не
вает их популярность и «вечность». сравнимое удовольствие.
Краткое оглавление
Предисловие
Часть 5. Алгоритмы на графах
Глава 17. Свойства и типы графов
Глава 18. Поиск на графе
Глава 19. Орграфы и ориентированные ациклические графы
Глава 20. Минимальные остовные деревья
Глава 21. Кратчайшие пути
Глава 22. Потоки в сетях
Ссылки, использованные в пятой части
Предметный указатель
Роберт Седжвик
Фундаментальные
алгоритмы Фундаментальные алгоритмы на С. Часть 1-5
Анализ/Структуры данных/Сортировка/Поиск/
Алгоритмы на графах
ISBN 5-93772-083-0
70x100/16, тв. переплет, 1136 с.
Роберт Седжвик
Фундаментальные
алгоритмы Фундаментальные алгоритмы на С. Часть 1-4
Анализ/Структуры данных/Сортировка/Поиск
ISBN 5-93772-081-4
70x100/16, тв. переплет, 672 с.
Роберт Седжвик
Фундаментальные Фундаментальные алгоритмы на С. Часть 5
алгоритмы
Алгоритмы на графах
ISBN 5-93772-082-2
70x100/16, тв. переплет, 480 с.
КНИЖНЫЙ КЛУБ «КОМП@С»
(ОФИЦИАЛЬНЫЙ ПАРТНЕР ТОРГОВО-ИЗДАТЕЛЬСКОГО ДОМА «ДС»)
Приглашаем всех желающих вступить в книжный клуб «Комп@с». Вступив в клуб, Вы получите скидки
содействие в поиске и покупке необходимых Вам книг. Мы своевременно реагируем на запросы читателей i
своей издательской деятельностью способствуем их профессиональному росту. Вы раньше других будете имет:
возможность познакомиться с новинками, получая наш прайс-лист, иллюстрированный каталог по почте либ<
обратившись на наш сайт www.diasoft.kiev.ua.
Вы сможете: пользоваться системой скидок при покупке книг;
высказать свое мнение, пожелания и замечания относительно книг издательства;
найти новых друзей и партнеров по интересам;
заочно встретиться и пообщаться с авторами книг;
участвовать в розыгрыше призов бесплатной лотереи.
И главное, впервые членам клуба будет предоставляться бесплатное комплексное программное обес-
печение, позволяющее осуществлять связь с книжной базой данных, получать оперативные изменения
в базе, компоновать заказы, получать информацию о книгах издательств - объем, содержание (краткое или
глобальное), аннотация, текст одной из глав, внешний вид обложки и т.д. С более подробной информацией Вы
можете ознакомиться на сайте www.diasoft.kiev.ua
ПРИЕМ В КЛУБ
Для того чтобы стать членом книжного клуба, достаточно купить одну из книг нашего издательства в любой
торговой точке, заполнить анкету, помещенную в книге, и отправить ее по адресу издательства либо купить три
любых книги на одном из торговых мест ТИД «ДС».
Все члены клуба получают карточку с индивидуальным номером, дающую право на скидку.
Карточка выдается сразу же при покупке на торговом месте ТИД «ДС» или у партнеров ТИД «ДС» (адреса
смотри на следующей странице). В остальных случаях (заказ по почте, через электронный магазин, а также
покупка у независимых торговых организаций книги ТИД «ДС») Вам"необходимо заполнить анкету, помещенную
в книге, и отправить ее в издательство. Действие карточки распространяется на все торговые точки, перечис-
ленные в этом пункте, кроме независимых торговых организаций.
СИСТЕМА СКИДОК
Став членом книжного клуба, Вы получаете первоначальную скидку в размере 5% на весь ассортимент книг
(книги ТИД «ДС» + более 1000 наименований книг издательств Украины и стран СНГ).
Карточка действительна на протяжении года со дня выдачи; по истечении срока подлежит замене.
При покупке в течение квартала пяти и более книг карточка заменяется на новую с 10% скидкой.
Более подробную информацию о системе скидок можно получить, обратившись на наш сайт по адресу
www.diasoft.kiev.ua.
АНКЕТА
1. Название книги .
2. Где, по какой цене, когда (с точностью до месяца) Вы приобрели эту книгу?_
3. Ф.И.О.
Вышлите анкету по адресу: Украина, 03055, Киев-55, а/я 100, «Издательство «ДиаСофт».
ТОРГОВЫЕ МЕСТА ТОРГОВО-ИЗДАТЕЛЬСКОГО ДОМА «ДС»
ХАРЬКОВ ДНЕПРОПЕТРОВСК
КИЕВ
Г| Ш
ул. Пушкинская \ci/ У
ул. Пастера _| 1 1 \
ул. Привокзальная
метро "Петровка" AL^V
я1 ЗИН
с~
Островского, 1
пл.
пр-т Карла Маркса
САНКТ-ПЕТЕРБУРГ
пр-т Обуховской обороны, 105, ДК им. Крупской, м."Елизаровская"
•я, 190103. пр-т Лермонтовский, 44, а/я 66,000 "ДиаСофтЮП"
(
•
'
9>
:S ' m
•S:l
W
е
о.
о о
3) Отправьте его нам по факсу, почте или 3) Отправьте его нам по факсу, почте или
e-mail. e-mail.
4 в
4) В течение одного рабочего дня в Ваш ад- ) течение одного рабочего дня в Ваш адрес
рее будет направлен счет по факсу или е- будет направлен счет по факсу или e-mail. В
mail. В течение срока действия счета мы течение срока действия счета мы гаранти-
ем
гарантируем наличие книг и неизменность РУ наличие книг и неизменность цены.
ц£1Ш Внимание! Не оплачивайте покупку до по-
лучения счета.
5) После оплаты счета Ваш заказ, а также 5) оплатите счет в любом коммерческом бан-
оригинал счета, расходная и налоговаяна- ке деньгиможно отправить на наш расчет-
кладные будут высланы Вам посылкой. ныйсчетипочтовьм переводом, но это не-
Заказ
от« » 2003 г
Название организации:
Для организации
Адресдоставки:
Почтовый индекс
Телефон:/ / Факс:
E-mail: _