Вы находитесь на странице: 1из 318

Borland/!

Builderv
разработка приложений баз данных

Базовые принципы и рекомендации для проектирования реляционных баз данных


Варианты подключения к базам данных различных типов при помощи механизмов
BDE, InterBase Express, ADO Express (dbGo) и dbExpress
Максимальное использование возможностей компонентов-наборов данных
(таблиц, запросов)
Возможности управления сеансами связи и сессиями
Приемы использования элементов управления и визуализации данных
Описание языков запросов локального SQL и SQL стандарта ANSI-92
Стандартные и нестандартные приемы использования однонаправленных наборов
данных из набора dbExpress
Клиентские наборы данных и методы их использования с различными
механизмами доступа к БД
Портфельная модель данных и файловая модель MyBase
Разработка отчетных и аналитических форм при помощи компонентов из
наборов Quick Report и Decision Cube
Большое количество примеров, иллюстрирующих описанные концепции
ББК 32.973.2
УДК 681.3.06(075)
П-68
Послед Б.С.
П-68 Borland C++ Builder 6. Разработка приложений баз данных —
СПб.: ООО «ДиаСофтЮП», 2003 — 320 с.
ISBN 5-93772-094-6
Данная книга предназначена для тех, кто предполагает использовать один из самыз
популярных продуктов Borland C++ Bulder 6 для проектирования приложений баз данных
Рассматриваются основные вопросы создания клиентских приложений, взаимодействую
щих как с локальными, так и с клиент-серверными базами данных. На примере баз дан
ных Paradox и Interbase изучаются различия между локальными БД и БД в формате SQL
Приводится большое количество примеров.
Книга состоит из пяти глав. Первая глава вводит основные понятия, связанные с ба-
зами данных. Здесь приводится классификация БД, принципы построения реальных Б^
и рассматриваются этапы разработки. Следующие две главы посвящены использовании:
мезанизма BDE для проектирования приложений, взаимодействующих с локальными
базами данных. Подробно описаны основные компоненты из набора BDE, приведены ис-
черпывающие сведения о локальном SQL. В четвертой главе на примере использования
сервера Interbase рассматриваются особенности БД в формате SQL, приведены сведения
о языке запросов стандарта SQL-92. Далее описываются приемы проектирования прило-
жений БД с использованием механизма dbExpress, рассмотрены модели данных MyBase и
Briefcase. Последняя, пятая глава, посвящена описанию способов построения отчетных и
аналитических форм с использованием компонентов Quick Report и Decision Cube.
Книга рассчитана на пользователей средней квалификации.
ББК 32.973.2

Все права защищены. Любая часть этой книги не может быть воспроизведена в какой бы то ни
было форме и какими бы то ни было средствами без письменного разрешения владельцев авторс-
ких прав.
Все упомянутые в данном издании товарные знаки и зарегистрированные товарные знаки при-
надлежат своим законным владельцам.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых из-
дательством как надежные. Тем не менее, имея ввиду возможные человеческие или технические
ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведе-
ний и не несет ответственности за возможные ошибки, связанные с использованием книги.

ISBN 5-93772-094-6 © Б.С.Послед, 2003


© ООО «ДиаСофтЮП», 2003
Гигиеническое заключение №77.99.6.953.П.438.2.9. от 04.02.1999
"

От автора 7

Глава 1. Базы данных и вспомогательные


инструментальные средства C++ Builder 6 10
Базы данныхгклассификация и этапы разработки 12
Подходы к классификации 12
Основные этапы разработки 14
SQL Explorer 20
Создание и использование псевдонимов 21
Словари и наборы атрибутов 24
Database Desktop 26
Создание и модификация таблиц 26
Резюме 28

Глава 2. Использование механизма ВОЕ 29


Создаем простое приложение.. 30
Использование компонента ТТаЫе 34
Активация и деактивация набора данных 35
Свойства подключения к таблице 36
Таблица Items 37
Навигация по набору данных 40
Поля (Fields) 47
Фильтрация 50
Поиск 60
Сортировка набора данных 66
Изменение данных 67
Использование закладок 70
Состояние набора данных и компонент TDataSource 72
Объект TField и редактор полей 75
Свойства, методы и события класса TField 78
Постоянные и динамические поля. Редактор полей 82
Вычисляемые поля (Calculated Fields) 85
Borland C++ Builder 6. Разработка приложений баз данных

Поля подстановки (Lookup Fields) 8i


Организация связи между таблицами 9
Режим кэширования изменений 9J
Создание таблиц «на лету» 10(
Резюме 102

Глава 3. Использование механизма ВОЕ


(продолжение) 103
Модули данных 101
Управление подключением к базе данных
при помощи объекта TDatabase 10Е
Основные свойства компонента TDatabase 106
Транзакции и BDE 10S
Получение имен таблиц и полей базы данных 111
События компонента TDatabase 113
Управление сеансами связи при помощи компонента TSession — 114
Свойства и события компонента TSession 115
Использование паролей для доступа к таблицам Paradox и dBASE.... 117
Управление сеансами связи 119
Получение информации о соединениях 121
Элементы визуализации и управления данными 128
Простейшие средства управления данными 128
Списки, комбинированные списки и группы 130
Диаграмма (компонент TDBChart) 131
Компонент TDBCtrlGrid 135
Использование сетки (компонент TDBGrid) 136
Свойство Columns. Объекты TColumn 136
Параметры сетки 144
Приемы использования событий сетки 149
Пример использования события OnTitleClick 149
Оформление отдельных ячеек сетки при помощи события
OnDrawColumnCell 153
Запросы и компонент TQuery 159
Краткие сведения о языке запросов SQL 159
Некоторые соглашения 160
Встроенные комментарии .' 160
Форматы констант даты и времени 760
Формат логических констант 161
Оглавление 5

Язык манипуляции данными (DML) 161


Оператор выборки (Select) 161
Предложение Select 161
Предложение From 762
Предложение Where 765
Предложение Group By. 768
Предложение Having 768
Предложение Order By 769
Оператор удаления (Delete) 169
Оператор вставки (Insert) 169
Оператор обновления (Update) 170
Оператор объединения (Union) 170
Запросы с параметрами 171
Несколько полезных функций SQL 171
Язык определения данных (DDL) 173
Оператор создания таблицы (Create Table) 173
Оператор модификации таблицы (Alter Table) 175
Оператор удаления таблицы (Drop Table) 175
Оператор создания вторичного индекса (Create Index) 175
Оператор удаления индекса (Drop Index) 176
Свойство SQL компонента TQuery.
Построитель запросов (SQL Builder) 176
«Живые» запросы и свойство RequestLive 179
Использование параметров 180
Выполняемые запросы 182
Компонент TUpdateSQL 182
Компонент TBatchMove 189
Клиентские наборы данных 192
Резюме 193

Глава 4. Клиент-серверные базы данных 195


Работа с Interbase 196
Консоль Interbase (IBConsole) 197
Утилита ISQL 202
Базы данных Interbase 204
Типы данных, поддерживаемые Interbase 204
Несколько слов о языке SQL 205
Работа с базами данных 206
Borland C++ Builder 6. Разработка приложений баз данных

Домены (Domains) 209


Работа с таблицами 211
Использование индексов 216
Генераторы (Generators), триггеры (Triggers)
и хранимые процедуры (Stored Procedures) 277
Представления (Views) 226
Исключения (Exceptions) 229
Планирование системы безопасности 230
Создание клиентских приложений 232
Несколько слов о механизмах доступа к данным 233
Подключение к базам данных с помощью dbExpress 234
Использование IBExpress для подключения к БД 237
Подключение к БД при помощи механизма dbGo (ADO) 240
Однонаправленные наборы данных dbExpress 245
Компонент TSQLTable 246
Компоненты TSQLQuery, TSQLStoredProc и TSQLDataSet 260
Клиентский набор данных TSQLCIientDataSet 263
Компонент TSQLMonitor 270
Резюме 273

Глава 5. Документированием анализ информации 275


Создание отчетов при помощи компонентов QuickReport 275
Мастер создания отчетов 276
Создание простого отчета в режиме конструктора 279
Компонент TQuickRep 282
Полосы отчета 291
Краткий обзор компонентов Quick Report 296
Decision Cube 297
Резюме 302

Предметный указатель 304


В
последнее время все больше и больше программистов, как начинающих, так
и достаточно опытных, приобщается к работе с базами данных различного форма-
та. Например, программиста, всю жизнь проработавшего с базами данных Oracle,
могут привлечь к сопровождению или созданию приложений, взаимодействующих
с локальными таблицами (Paradox, dBASE и др.). Или в связи с приобретением новой
бухгалтерской программы придется срочно переквалифицироваться из разработчика си-
стем мониторинга и управления технологическими процессами в специалиста по клиент-
серверным базам данных.
Практически во всех таких или подобных ситуациях ощущается острая нехватка вре-
мени. Возможность самостоятельно в кратчайшие сроки найти решение новых задач вряд
ли представится. Бесконечные блуждания по справочной системе в поисках нужной ин-
формации могут довести до отчаяния. Неоценимую помощь в таком случае может оказать
исчерпывающее описание решения нужной задачи, включающее изложение основных
концепций и примеры, эти концепции иллюстрирующие. Зачастую достаточно просто
взглянуть на удачно спроектированный пример, чтобы понять, как все работает.
Предлагаемая вашему вниманию книга посвящена основным вопросам программи-
рования приложений баз данных с использованием шестой версии Borland C++ Builder.
Начиная с первой версии, когда был представлен универсальный механизм доступа
к данным BDE, в каждой очередной версии C++ Builder разработчикам приложений баз
данных предлагались все новые и новые возможности. Не стала исключением и версия 6.
Наряду с целым рядом усовершенствований и дополнений, Borland C++ Builder 6 предо-
ставляет и новый механизм доступа к данным. Механизм dbExpress дает возможность
легко и быстро получать доступ к базам данных в формате SQL (DB2, Oracle, Interbase
и др.). За счет использования специализированных драйверов БД (для каждого формата
БД применяется свой драйвер) механизм dbExpress позволяет создавать наиболее эффек-
тивно работающие приложения БД, в том числе и кросс-платформенные (CLX).
На данный момент C++ Builder поддерживает четыре разных механизма доступа к дан-
ным. Кроме упомянутых механизмов BDE и dbExpress, к их числу относятся и появившиеся
в пятой версии механизмы IB Express и ADO Express (в шестой версии последний называется
dbGo). IB Express — это специализированный механизм для доступа к базам данным в форма-
те Interbase, позволяющий воспользоваться всеми особенностями, характерными именно для
этого формата БД. Что касается механизма dbGo, который использует для доступа к данным
разработанный в Microsoft провайдер OLE DB, то, несмотря на объявленную .универсаль-
ность, пользоваться им удобно только для подключения к базам данных MS Access и MS SQL
Server (или к другим источникам данных, формат которых разработан Microsoft).
Благодаря тому, что BDE — самый старый (а значит, и наиболее проверенный на прак-
тике), самый универсальный и распространенный механизм доступа к данным, именно
на его основе в этой книге и объяснялись важнейшие концепции проектирования баз
данных (модели данных), а также принципы построения простейших приложений БД.
Параллельно описывались особенности локальных баз данных (Paradox, dBASE). При
описании клиент-серверных баз данных на примере БД Interbase преимущественно ис-
пользовался механизм dbExpress.
Borland C++ Builder 6. Разработка приложений баз данных

Книга разделена на пять глав.


Первая глава, как обычно бывает, служит вводной. Вначале обсуждаются различны
подходы к классификации баз .данных, рассматриваются основные понятия, использ)
емые при дальнейшем изложении, а также приводятся общие рассуждения о том, ка
должна выглядеть идеально спроектированная база данных. Подробно описываются ХЕ
рактерные этапы разработки модели данных (структуры базы данных). Вместе с Borlan
C++ Builder поставляются две полезные утилиты, при помощи которых можно создават
базы данных, модифицировать их структуру, заполнять таблицы данными и делать много
другое. Часть этих действий удобнее выполнять при помощи утилиты SQL Explorer, дру
гие — при помощи Database Desktop. Описание возможностей этих утилит и завершав
материал первой главы.
Во второй главе рассматриваются основы механизма BDE. На примере одного из про
стейших наследников класса TDataSet — компонента ТТаЫе — описываются основны<
действия с наборами данных. В частности, речь идет о таких задачах, как навигация ш
набору данных, поиск нужной информации, фильтрация и тому подобное. Обсуждение
всех этих простейших задач сопровождается примерами приложений, в которых неслож-
ные действия с наборами данных представлены несколько нестандартно. Кроме того
подробно рассматриваются компоненты TDataSource и TField, описываются постоянные
и динамические объекты полей, вычисляемые поля и поля подстановки. В заключение
рассматриваются создание связей типа Master/Detail между таблицами, режим кэширова-
ния изменений, приемы создания таблиц программным способом.
В третьей главе книги продолжается обсуждение механизма BDE и основных концеп-
ций создания приложений баз данных. Здесь рассматриваются модули данных, компонен-
ты TDatabase и TSession. Большое внимание уделено использованию элементов визуали-
зации и управления данными, которые можно найти на вкладке Data Controls. Особое вни-
мание уделено компоненту TDBGrid (сетка), очень часто используемому в приложениях
БД. Достаточно подробно описывается вариант языка SQL, который используется для до-
ступа к данным локальных БД (Paradox, dBASE, Foxpro и таблицы в текстовом формате).
На основе этой информации рассматриваются варианты применения наиболее важного
наследника класса TDataSet — компонента TQuery. Именно экземпляры этого компонента
чаще всего используются в приложениях БД. В заключение рассматриваются компоненты
TUpdateSQL и TBatchMove, а также основы использования клиентских наборов данных.
Четвертая глава посвящена созданию приложений, взаимодействующих с клиент-
серверными базами данных. В качестве примера используется сервер БД Interbase.
Вначале рассматриваются утилиты IBConsole и Interactive SQL. Описывается, как при
помощи утилиты IBConsole зарегистрировать локальный или удаленный сервер Interbase,
создать и зарегистрировать базу данных, а также выполнить другие действия по админи-
стрированию БД Interbase. Особое внимание уделено утилите Interactive SQL, при помощи
которой можно выполнить любой запрос SQL к открытой базе данных.
Далее в этой главе рассматриваются основы БД Interbase. В частности, расска-
зывается о типах данных Interbase, описывается встроенный язык SQL Interbase, из-
учается работа с основными объектами Interbase, обсуждается планирование системы
безопасности. Рассматриваются основные вопросы создания клиентских приложений,
взаимодействующих с клиент-серверными базами данных: использование механизмов
dbExpress, IBExpress и dbGo (ADO) для подключения к БД Interbase, использование одно-
направленных наборов данных dbExpress, способы обновления необновляемых наборов
данных и перемещения по однонаправленным наборам данных в любом направлении,
От автора У

применение клиентских наборов данных для организации портфельной модели данных


и файловой модели MyBase. В заключение приводятся примеры использования компонен-
та TSQLMonitor.
В последней, пятой главе книги речь идет о способах создания отчетных и аналити-
ческих форм для обычного и многомерного анализа данных. Рассматриваются компонен-
ты из наборов Quick Report и Decision Cube. Приводится много примеров, облегчающих
понимание основ конструирования отчетов различной сложности.
Надеюсь, вы сможете почерпнуть из этой книги нужную вам информацию. Удачи!
Базы данных и
вспомогательные инструментальные
средства C++Builder б
В ЭТОЙ ГЛАВЕ
Ф Классификация баз данных
Ф Основные понятия
ф Что такое «правильная» база данных
Ф Этапы разработки
Ф Утилиты SQL Explorer и Database Desktop
ф Резюме

Д
авно ушли в прошлое те времена, когда программисту, всю свою сознательную
жизнь проработавшему с языками программирования С или 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

ответствуя все возрастающим потребностям сообщества программистов. Вместе с основ-


ным продуктом менялись, совершенствуясь, и механизмы доступа к различным источни-
кам данных. Новейшая на момент написания этих строк версия C++ Builder — шестая.
Кроме BDE эта версия содержит и другие механизмы доступа к данным:
G dbExpress, предоставляющий очень быстрый и удобный доступ к базам данных в фор-
мате SQL.
Q ADOExpress — набор компонентов, позволяющий получать доступ к реляционным
и нереляционным источникам данных при помощи механизма ADO. Этот механизм,
разработанный фирмой Microsoft, для доступа к данным использует провайдер данных
OLE DB.
G IBExpress — набор компонентов, позволяющий получить доступ к базам данных
Interbase и воспользоваться всеми преимуществами СУБД Interbase, часть которых не-
доступна при использовании механизма BDE.
Выбрав подходящий механизм доступа к данным, можно воспользоваться стандарт-
ными компонентами для отображения и редактирования содержимого полей источника, а
также для перемещения по его записям. К числу таких компонентов относятся поля ввода,
списки и комбинированные списки, флажки, сетки и другие хорошо известные пользова-
телям Windows элементы управления. Для создания различных отчетных форм (в виде
таблиц, перекрестных таблиц, диаграмм и т.д.) предназначены наборы компонентов Quick
Report (QReport) и Decision Cube.
Кроме стандартных (привычных) средств работы с базами данных различного формата
C++ Builder 6 содержит средства для создания многоярусных приложений (так называе-
мая технология MIDAS), средства для интеграции информационных систем различных
предприятий (технология В2В, или Business-To-Business, реализованная при помощи на-
бора компонентов Web Services), а также поддерживает другие новые технологии.
Предлагаемая вашему вниманию книга поможет вам разобраться с различными ва-
риантами подключения к источникам информации (базам данных) и функциональными
возможностями большинства компонентов и классов, которые вы сможете использовать в
своих приложениях баз данных для отображения, просмотра и редактирования информа-
ции, а также для представления информации из различных таблиц выбранных источников
данных в удобной для анализа форме. Ознакомившись с этим материалом, вы сможете
самостоятельно создать базу данных, подготовить необходимые таблицы и заполнить их
информацией, подключиться к нужному реляционному или нереляционному источнику
данных, а также, воспользовавшись преимуществами той или иной схемы подключения,
создать информационное приложение, соответствующее вашим запросам или запросам
ваших заказчиков.
Среди прочего в этой главе представлены вводные сведения о базах данных, а так-
же описаны некоторые полезные инструментальные средства Borland C++ Builder 6.
Начальные сведения о базах данных позволят вам ознакомиться с основными терми-
нами, которые будут использоваться при дальнейшем изложении. К числу инструмен-
тальных средств, рассматриваемых в этой главе, относятся SQL Explorer и Database
Desktop. Ознакомившись с материалом главы, вы сможете со знанием дела создавать
простейшие базы данных, таблицы и другие объекты. Вы сможете также добавлять
или удалять записи таблиц, редактировать содержащуюся в этих записях информа-
цию, и многое другое.
I2 Borland 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. Разработка приложений баз данных

информационной системы. Серверы БД общаются с клиентскими приложениями пр


помощи стандартного языка SQL-запросов, поэтому их часто называют SQL-сервер;
ми. В настоящее время на рынке доступно большое количество SQL-серверов; к и
числу относятся, в частности, Interbase, Oracle, DB2, Sybase, Informix, MS SQL Serv<
и многие другие, менее известные и распространенные.
Q Многоярусные БД. Концепция многоярусных (multi-tiered) БД — дальнейшее развита
концепции клиент-серверных БД. Многоярусные БД — это новая и очень nepcnei
тивная архитектура информационных систем. Наиболее развита в настоящее врем
трехъярусная архитектура. При использовании такой модели на самом нижнем ярус
находится так называемый тонкий клиент, формирующий запросы к удаленному cej
веру и отображающий полученную от сервера информацию. Другими словами, тонки
клиент предназначен в основном для ввода и отображения информации. Требования п
производительности к тонким клиентам достаточно низкие, что ведет к существенно
му удешевлению каждого рабочего места.
На втором ярусе располагается так называемый сервер приложений. Он производи
основные вычисления, в том числе, наиболее громоздкие, формирует окончательны
запросы к серверу БД, а также обслуживает подключенных к нему клиентов. На это!
уровне должен находиться достаточно мощный компьютер, так как к нему может быт
подключено большое количество клиентов, одновременно запрашивающих сложную об
работку больших объемов информации. Серверов приложений в одной информационно]
системе может быть также большое количество (хотя и намного меньше, чем клиентов)
На третьем ярусе располагается сервер БД. Обычно используется один сервер БД
управляющий одной базой данных. Сервер БД обрабатывает поступающие к нему за
просы, формирует ответ и отсылает его запросившему серверу приложений. Серве{
приложений, получив ответ, отсылает необходимые данные клиенту, выступая также i
роли коммутатора. Такая архитектура очень надежна, устойчива и, при определенны?
условиях, не требовательна к аппаратному обеспечению и используемым операцион-
ным системам.
В последнее время, однако, чаще применяются упрощенные варианты приведенной
выше классификации. Эти упрощенные классификации также будут использоваться ъ
этой книге. В соответствии с первым упрощением, базы данных, в зависимости от метода
доступа к ним, делятся на локальные и клиент-серверные (удаленные). В большинстве
случаев такой подход оправдан. При втором упрощении все базы данных разделяются в
зависимости от количества составляющих их звеньев. При таком подходе локальные и
файл-серверные базы данных считаются одноярусными системами, а клиент-серверные
— двухъярусными. -

Основные этапы разработки


К сожалению, не существует какой-либо одной, теоретически обоснованной методики
разработки баз данных, следуя которой можно было бы без всяких проблем сконструиро-
вать необходимую информационную систему. С другой стороны, практически всем из-
вестно, что БД будет высокопроизводительной, устойчивой к ошибкам ввода, масштаби-
руемой и максимально удобной, если с самого начала не допустить ошибок при проекти-
ровании модели данных. К счастью, базы данных уже давно разрабатываются множеством
опытных программистов по всему миру, поэтому к настоящему времени существует набор
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 15

рекомендаций, выполняя которые даже начинающий или малоопытный разработчик БД


сможет не допускать грубых ошибок с самого начала.
Следует заметить, что совершенно избежать ошибок и неточностей при проектировании
модели данных не удается никому. Даже опытные программисты могут допускать неточ-
ности, не до конца продумав все нюансы. Поэтому вам придется смириться с тем, что не
раз и не два вы будете возвращаться в самое начало разработки, вновь и вновь перекраивая
структуру БД. Таким образом, конструирование модели данных — процесс итеративный.
Прежде чем перечислять то, что необходимо для разработки «правильной» базы дан-
ных, неплохо было бы выяснить, какую базу данных считать таковой. В большинстве
источников «правильной» считается база данных, которая соответствует следующим
четырем условиям.
Q Удовлетворяет всем запросам пользователя по содержанию. Конечно, это условие
кажется само собой разумеющимся и потому излишним, но это не совсем так. Прежде
чем приступать к разработке модели данных, вам следует на сто процентов удостове-
риться, что вы четко представляете себе, какие именно данные хотят видеть в создава-
емой информационной системе потенциальные пользователи. Кроме того, вы должны
догадываться о том, какую еще информацию возможно потребуется включить в базу
данных в процессе разработки информационной системы. Это связано с тем, что в
большинстве случаев заказчики базы данных не вполне представляют все с необходи-
мой точностью в момент постановки задачи.
О Удовлетворяет требованиям пользователя по производительности. На самом деле
не нужно спрашивать у пользователя, нравится ли ему, что его запрос выполняется
полчаса или час. Ответ и так понятен: это никому не понравится. Поэтому на всех
этапах разработки БД необходимо заботиться о наилучшей производительности.
Например, если таблица может содержать очень много строк, не стоит делать в ней
слишком много индексов, иначе после каждого ввода новой записи время индексации
может превысить 1-2 секунды, что будет сильно раздражать пользователя и замедлять
его работу. С другой стороны, если индексов будет мало, то слишком долгим будет
время отбора записей при отображении. Особое внимание следует уделить фильтрации
записей из больших таблиц при отображении. Например, если таблица содержит мил-
лион записей, то не стоит отображать сразу все. Достаточно отобразить небольшую
часть записей, например, соответствующих определенному периоду или диапазону
значений. Много внимания следует уделить и оптимизации запросов. Если запрос вы-
полняется медленно, попробуйте сформулировать его по-другому.
Q Не позволяет вводить данные, нарушающие целостность уже имеющейся инфор-
мации. Вы должны программно или при помощи настройки свойств объекта в режиме
конструктора запретить ввод данных, которые могут вызвать ошибочную ситуацию (на-
пример, деление на ноль), появление двусмысленности и тому подобные неприятности.
Это достигается за счет задания ограничений на диапазон вводимых значений, правиль-
ной организации связей между объектами (таблицами), использования транзакций и
других мер. Как правило, в остальном поддержание целостности информации берут на
себя системы управления базами данных (СУБД), управляющие конкретной базой.
Q Предоставляет естественную, интуитивно понятную организацию данных.
Хорошо и понятно организованная структура информации помогает потенциальным
пользователям легко и быстро разобраться в существе вопроса, что позволит им само-
стоятельно строить различные отчетные и аналитические формы, первоначально не
10 Borland C++ Builder 6. Разработка приложений баз данных

предусмотренные в проекте. С другой стороны, и сам разработчик при необходимости


легко может вспомнить все детали проекта, над которым он когда-то работал.
Конечно, приведенные выше условия — не догма, но учесть их все же стоит.
Вооружившись информацией о том, «что такое хорошо, а что такое плохо», можно
приступить к изучению этапов разработки БД. Обычно выделяют восемь таких этапов.
1. Определение типа информации, которая должна быть учтена в базе данных.
На самом деле это скорее не этап разработки, а этап подготовки к ней. Прежде чем
приступить к созданию модели данных, вы должны опросить всех предполагаемых
пользователей, чтобы получить полное представление об их требованиях. Всю эту
информацию необходимо тщательно задокументировать. На ее основе вы должны
четко представить себе целостную картину той части реального мира, которую вы со-
бираетесь моделировать. Чтобы убедиться в том, что вы все учли, вам нужно ответить
на несколько вопросов.
• Все ли объекты связаны между собой надлежащим образом? Нет ли противоречий
между связями? Не распадаются ли все объекты на несколько изолированных друг
от друга групп?
• Правильно ли вы представляете себе уровень детализации каждого из объектов?
При ответе на этот вопрос нельзя полагаться только на информацию, полученную
от предполагаемых пользователей, так как они могут не представлять всей специ-
фики разработки информационных систем.
• Какие из характеристик объектов уникальны и однозначно определяют конкрет-
ный объект? Например, можно ли, опираясь на информацию о дате накладной и
ее номере, однозначно идентифицировать накладную, или может одновременно
существовать несколько накладных, имеющих одинаковый номер и дату? Эта ин-
формация понадобится при создании системы индексов и ключей.
• Сколько и каких отчетных форм необходимо предусмотреть в БД? Имеются ли для
этого все необходимые объекты?
• Сколько и каких категорий пользователей будет работать с БД? В зависимости от
выполняемых функций у каждой категории пользователей должен быть свой уро-
вень доступа. Например, администраторы должны иметь полный доступ ко всем
объектам БД, операторы — доступ к вводу и просмотру информации из определен-
ных (или всех) таблиц, а для группы анализа и принятия решений достаточно прав
на просмотр информации, и т.д.
• Каким образом будет осуществляться доступ к базе данных — только с локаль-
ного компьютера, через локальную сеть, с использованием удаленного соедине-
ния? Сколько пользователей могут одновременно получать доступ к базе данных
— один, два, сто, тысяча?
• Какие из таблиц могут иметь большое количество записей, а какие нет? Какие
данные критичны ко времени обработки? (Например, таблицы могут иметь очень
большое количество строк и при этом подвергаться очень частым изменениям дан-
ных. В этом случае для организации нормальной работы и безопасности данных
необходимо предпринимать специальные меры: разбиение, архивирование и т.д.)
• Нужна ли архивация информации из основных (громоздких) таблиц, и если да, то
какова должна быть ее периодичность?
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder б 17

Если ответ на какой-либо из вопросов вам не ясен, следует повторно опросить предпо-
лагаемых пользователей. Таким образом, сбор информации также может быть итера-
тивным.
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

Рис. 1.1. Окно приложения SQL Explorer


Как видно на рис. 1.1, основная рабочая область SQL Explorer'a разбита на две части
— левую панель (Left Pane) и правую панель (Right Pane). Обе панели представляют собой
набор вкладок. Левая содержит две вкладки: Databases (базы данных) и Dictionary (сло-
варь). На правой панели может содержаться переменное число вкладок — в зависимости
от выбранного на левой панели объекта.

Создание и использование псевдонимов


Вкладка Databases содержит список зарегистрированных в системе псевдонимов
(Aliases). Псевдоним (Alias) — это специализированный именованный объект, содер-
жащий информацию, которую BDE (Borland Database Engine) использует для подклю-
чения к базам данных. Псевдоним можно создать при помощи утилит SQL Explorer,
BDE Administrator или Database Desktop, а можно его и не создавать. В последнем
случае, если ваше приложение использует BDE, временный псевдоним будет создан
автоматически, как только вы укажете в приложении всю необходимую для подключе-
ния информацию.
При первом запуске SQL Explorer вы можете обратить внимание на то, что в системе
уже есть несколько зарегистрированных псевдонимов. В частности, при инсталляции C++
Builder автоматически регистрируются псевдонимы примеров баз данных BCDEMOS,
DefaultDD, IBLocal (см. рис. 1.1). Первые два ссылаются на базу данных формата Paradox,
22 Borland C++ Builder 6. Разработка приложений баз данных

а последний — на базу данных Interbase (этот псевдоним будет инсталлирован, если в


установили на своем компьютере или клиент, или сервер Interbase). Кроме того, автомат!
чески регистрируются псевдонимы баз данных, соответствующие установленным драйв<
рам ODBC (на рис. 1.1 — последние пять строк).
Замечание
Чтобы воспользоваться псевдонимами, которые соответствуют драйверам ODBC
необходимо сначала их настроить. В частности, нужно создать DSN-файл (Data Sourc
Name). Для этого следует прибегнуть к приложению Источники данных ODBC (32
(ODBC Administrator), доступ к которому можно получить, воспользовавшись системныг
диалоговым окном Панель управления (Control Panel) или соответствующим пунктог
меню Object утилиты SQL Explorer.

Создать новый псевдоним довольно просто. При выборе пункта New меню Object Hi
экране будет отображено диалоговое окно, позволяющее указать в выпадающем списю
тип драйвера базы данных (см. рис. 1.2). Обычно доступны следующие драйверы (в за-
висимости от варианта инсталляции): STANDARD, MSACCESS, SYBASE, ORACLE,
MSSQL, INFORMIX, DB2, INTRBASE. Тип драйверов, возможно, кроме первого, поня-
тен из их названия. Драйвер STANDARD поддерживает локальные базы данных Paradox
(таблицы в файлах с расширением db), dBASE и FoxPro (таблицы в йй/^файлах), а также
таблицы в текстовом формате (файлы с расширением txt). Выбрав нужный драйвер, на-
жмите кнопку ОК. Чтобы зарегистрировать новый псевдоним, нужно выбрать пункт Apply
все того же меню Object.

Рис. 1.2. Диалоговое окно New Database Alias


Для дальнейшего использования псевдонима его необходимо соответствующим об-
разом настроить. Для начала следует задать для него подходящее информативное имя,
так как автоматически ему будет присвоено имя наподобие STANDARD! или INTRBASE1.
Для этого существует команда Rename контекстного меню псевдонима. На правой панели
окна SQL Explorer, на вкладке Definition, перечислены остальные свойства псевдонима.
Количество и тип этих свойств зависит от выбранного типа драйвера. Например, если вы
выбрали тип STANDARD, то в списке будет всего четыре свойства: Туре (тип драйвера),
DEFA ULTDRIVER (конкретный драйвер базы данных — PARADOX, DBASE, FOXPRO или
ASCIIDRV), ENABLE BCD (нужно ли преобразовывать числа с плавающей точкой в двоич-
но-десятичный код BCD) и PATH (полный путь к таблицам базы данных выбранного типа).
Тип драйвера базы данных изменить нельзя, а остальные свойства можно редактировать.
У псевдонимов, соответствующих драйверам SQL-серверов, свойств гораздо больше.
В основном это свойства, специфичные для конкретного типа базы данных. Не все из
отображенных свойств можно менять, некоторые из них предназначены только для про-
смотра. Чтобы не ошибиться в их настройке, следует тщательно изучить документацию,
поставляемую вместе SQL-сервером. Если такой возможности у вас нет, то можно просто
оставить для всех свойств их значения по умолчанию. Исключением является свойство,
задающее имя базы данных (или каталог — для локальных баз данных). Например, для
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 23
баз данных Informix это свойство называется DATABASE NAME, для Interbase — SERVER
NAME, а для псевдонимов типа STANDARD — PATH. В любом случае, прежде чем ис-
пользовать псевдоним, вы должны указать базу данных, для подключения к которой он
предназначен.
После создания и настройки псевдонима вы можете использовать его в своем при-
ложении, например, чтобы указать базу данных для объекта ТТаЫе (компонент, инкап-
сулирующий таблицу базы данных). Для этого достаточно выбрать имя псевдонима из
выпадающего списка. Это очень удобно, так как при помощи одного щелчка мышкой вы
получаете нужным образом настроенное подключение. С другой стороны, изменив имя
целевой базы данных или переместив ее в другой каталог, вы должны будете внести изме-
нения только в определение псевдонима, не меняя приложения, которые этот псевдоним
используют.

Це!р

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., —• -

Рис. 1.3. Список полей таблицы animals базы данных,


соответствующей псевдониму BCDEMOS
При помощи псевдонимов и SQL Explorer можно легко исследовать структуру любой
базы данных. Щелкнув на элементе развертывания слева от имени псевдонима, вы по-
лучите доступ к списку объектов, содержащихся в соответствующей БД. Количество объ-
ектов на этом уровне иерархии зависит от типа драйвера БД. Например, для псевдонимов
типа STANDARD вы получите доступ только к списку таблиц, а для псевдонимов типа
MSACCESS — к списку таблиц и списку процедур. Наибольшее количество объектов
соответствует псевдонимам SQL-серверов. В частности, для Interbase объектов перво-
го уровня иерархии восемь. Кроме списка таблиц вы можете получить здесь доступ к
следующим объектам: Domains (домен), Views (представления), Procedures (процедуры),
Functions (функции), Generators (генераторы), Exceptions (исключения) и Blob Filters
(фильтры объектов Blob).
Замечание
При попытке открыть некоторые из псевдонимов вы можете получить диалоговое
окно с требованием ввести имя пользователя и пароль для доступа к соответствующей
базе данных. Для БД Interbase по умолчанию используется имя пользователя SYSDBA
и пароль masterkey. Для некоторых баз данных можно оставить поля для ввода имени
пользователя и пароля пустыми. Кроме того, для баз данных в формате SQL должен,
быть предварительно запущен соответствующий сервер. Например, для доступа к базам
данных Interbase необходимо сначала запустить или IBConsole, или InterBase Guardian.
24 Borland C++ Builder 6. Разработка приложений баз данных

Замечание
Не забудьте, перед тем как использовать новый псевдоним, сохранить его. Для этог
предназначена команда Apply меню Object. Соответствующая клавиатурная комбинаци
- Ctrl + A.B противном случае вы получите сообщение об ошибке.

Таким образом, пробираясь вглубь иерархии, вы можете исследовать свойств


любого интересующего вас объекта БД: таблицы, поля, индекса, ограничений на да^
ные и на ссылочную целостность, спецификаций безопасности и т.д. На рис. 1.3 н
вкладке Summary отображены все поля таблицы animals базы данных (псевдонима
BCDEMOS. Для каждого поля отображены также все его свойства, включая тип поля
его размер и т.д.
При выборе тех или иных объектов на левой панели окна SQL Explorer на правой па
нели могут появляться различные вкладки. Например, если вы выбрали одну из таблиц
то станет доступной вкладка Data. С помощью этой вкладки вы сможете просмотреть шп
отредактировать информацию, хранящуюся в выбранной таблице. Обратите внимание HI
панель навигации, которая расположена в верхней правой части окна SQL Explorer. ITpi
помощи кнопок этой панели вы сможете удалять или добавлять записи, сохранять или от
менять сделанные изменения, перемещаться по записям таблицы.

! Аи 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

Рис. 1.4. Результаты выполнения запроса на выборку


Заслуживает особого внимания и вкладка Enter SQL. Она становится доступна, если
вы хотя бы раз открывали псевдоним. В верхней части этой вкладки расположено окошко
для ввода текста оператора SQL. Кнопка Execute Query с изображением желтой молнии
предназначена для запуска запроса на выполнение. На рис. 1.4 отображен результат вы-
полнения запроса на выборку. Две кнопки с синими стрелками вверх и вниз позволят вам
вернуться к введенным ранее запросам. Таким образом вы можете отредактировать текст
и выполнить запрос снова, не вводя текст SQL-оператора целиком.

Словари и наборы атрибутов


Словарь (Dictionary) — это специальная таблица, предназначенная для хранения на-
боров атрибутов базы данных. Хранится эта таблица в самой базе данных среди других
таблиц. Набор атрибутов (Attribute Set) — это именованный объект, описывающий
свойства отдельных столбцов (полей) таблицы. На рис. 1.5 отображен набор атрибутов
CustNo. В частности, заданы формат отображения, минимальное и максимальное допу-
стимые значения, выравнивание данных в ячейке.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 25

Ока.»*/ SampteOea DfOreroy

СйаЬэ,» Oirionay)
i -Ш Dictioneiy
r-: El Database!
: «-П BCDEMOS
; ш-flB DBDEMOS
• -v Я Фейлы dQASE
S И Attribute Sets
•«•И Company
:+ H Continent

Referencing Fielt
И BCDEMOS.O.:
-•BCOEMOS or
EOHOEMuviujd

Рис. 1.5. Набор атрибутов CustNo


Использование наборов атрибутов напоминает использование псевдонимов. Однажды
создав и настроив именованный набор свойств для столбца, вы сможете многократно
использовать его в своих приложениях, просто ассоциировав с ним поле набора данных
(таблицы или запроса). Если вдруг понадобится изменить одно из свойств, вам нужно
будет внести изменения только в набор атрибутов, а не просматривать все приложения,
рискуя допустить неточность. Таким образом, использование наборов атрибутов позволя-
ет унифицировать и упростить разработку информационных систем, вынося часть логики
за пределы приложений.
Чтобы создать новый словарь, следует воспользоваться пунктом New меню Object.
Если этот пункт недоступен, значит, один из словарей уже открыт, и его нужно закрыть
(пункт Close меню Object). На экране будет отображено диалоговое окно Create a new
Dictionary (см. рис. 1.6). В этом окне нужно указать имя нового словаря (под которым он
будет числиться в списке доступных словарей), из выпадающего списка выбрать для сло-
варя один из псевдонимов (они перечислены на вкладке Databases) и задать имя таблицы,
в которой будет храниться вся информация о словаре. Не обязательно, но желательно опи-
сать вкратце назначение нового словаря.

:". (Словарь для базы ценных IBLocal

Рис. 1.6. Диалоговое окно Create a new Dictionary


Первоначально новый словарь содержит два узла—Databases и Attribute Sets. Существует
несколько способов создания новых атрибутов. Можно щелкнуть правой клавишей мышки
на названии одного из имеющихся атрибутов или на названии узла Attribute Sets и выбрать из
контекстного меню пункт New. В список атрибутов будет добавлен новый атрибут под одним
из стандартных имен типа EXTFIELD1. Вам остается только задать для него подходящее имя
и настроить нужным образом требующиеся свойства.
Еще один вариант создания атрибутов — наследование от существующего столбца
(поля) таблицы. В этом случае необходимо сначала импортировать базу данных в сло-
варь. Для этого предназначен пункт Import to Dictionary контекстного меню псевдонимов
26 Borland C++ Builder 6. Разработка приложений баз данных

(вкладка Databases). В результате этой операции имя выбранного псевдонима появится в


списке Databases на вкладке Dictionary.
Более подробную информацию о работе со словарями и наборами атрибутов можно
почерпнуть из справочной системы SQL Explorer.

DatabaseDesktop
Возможности Database Desktop несколько больше, чем у SQL Explorer. С помощью
этой утилиты вы сможете создавать и модифицировать таблицы в форматах Paradox,
dBASE и SQL, добавлять, удалять, редактировать записи, сортировать информацию, вы-
полнять запросы, и многое другое. Database Desktop поставляется как отдельная утилита.
Запустить ее можно или при помощи пункта Database Desktop меню Tools, или найдя
файл dbd32.exe. Обычно этот файл расположен в каталоге /Common Files/Borland Shared/
Database Desktop/. В этом разделе кратко описаны основные возможности, предоставляе-
мые Database Desktop. За подробной информацией обратитесь к справочной системе.

Создание и модификация таблиц


Для того чтобы инициировать создание новой таблицы, нужно выбрать пункт New \
Table в меню File. Затем в маленьком диалоговом окошке Create Table следует указать тип
таблиц (точнее, тип драйвера БД). Вашему вниманию предлагается набор из драйверов
различных версий Paradox и dBASE, а также набор доступных в вашей системе ODBC-
и SQL-драйверов. К последним обычно относятся следующие: FOXPRO, MSACCESS,
SYBASE, ORACLE, INFORMIX, MSSQL, DB2 и INTRBASE. Выбрав нужный драйвер и
нажав кнопку ОК, вы получите доступ к диалоговому окну Create Table. На рис. 1.7 изо-
бражено окно для создания таблицы Interbase.

:teble>_StoreNo

s-tor,.

Рис. 1.7. Диалоговое окно для создания таблицы Interbase


В центральной части диалогового окна Create Table расположена таблица, при помощи
которой можно создать набор полей, задав для каждого из них имя (Field Name) и тип
(Туре). Для числовых типов с плавающей или фиксированной точкой может понадобиться
задать размер значений (Size) и количество знаков после запятой (Dec). Для различных
строковых типов нужно задать размер (Size). Тип поля можно выбрать из списка допу-
стимых вариантов, который отображается при щелчке правой кнопкой мышки в соответ-
ствующей ячейке. Обратите внимание на подсказку в нижней части окна. Она достаточно
информативна, чтобы вы не растерялись и сделали все правильно.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder б 27
Замечание
При вводе значений свойств поля необходимо руководствоваться правилами, принятыми
для баз данных выбранного формата. Особенно это касается баз данных в SQL-формате. Сама
утилита Database Desktop не всегда контролирует корректность введенного вами значения.
Например, если для поля StoreNo указать какой-либо размер (значение свойства Size), то
никаких сообщений об ошибке не будет. Однако ошибка произойдет позже, например,
когда вы попытаетесь сделать на основании этого поля индекс.

Но не спешите создавать новую таблицу «с нуля». У вас есть возможность воспользо-


ваться результатами работы, уже проделанной ранее вами или другими программистами.
Если уже существует таблица, структура которой похожа на ту, которую вы собираетесь
построить, вы можете заимствовать структуру (Borrow Struct). Если вы еще не ввели в
макет новой таблицы никакой информации, то в нижней части диалогового окна Create
Table будет доступна кнопка Borrow Struct. При нажатии этой кнопки на экране будет ото-
бражено диалоговое окно Select Borrow Table, напоминающее обычное диалоговое окно
Windows для открытия файла. В первую очередь нужно указать псевдоним (Alias), ссыла-
ющийся на базу данных, которой принадлежит нужная таблица. Затем необходимо указать
имя таблицы и нажать кнопку Открыть (Open). В результате в диалоговое окно Create
Table будет импортирована структура выбранной таблицы, при этом форматы исходной
и целевой таблицы могут не совпадать. Например, вы можете позаимствовать структуру
таблицы в формате dBASE для таблицы в формате Interbase. Далее можно часть информа-
ции удалить или модифицировать, часть информации добавить, добиваясь соответствия
структуры таблицы вашим целям.
Замечание
Если вы хотите позаимствовать структуру таблицы в формате *.db или *.dbf, то можно
обойтись и без псевдонимов. В таком случае из списка Alias окна Select Borrow Table
выберите значение None. После этого остается только указать каталог и имя файла, в
котором хранится нужная вам таблица.

Замечание
В нижней части диалогового окна Select Borrow Table расположено несколько
флажков. Устанавливая эти флажки, вы сможете добиться импорта следующих объектов:
Первичный ключ/индекс (Primary index), Проверка на допустимость (Validity check),
Таблицы просмотра (Lookup table), Вторичные индексы (Secondary index), Ссылочная
целостность (Referential integrity).

Рис. 1.8. Диалоговое окно для определения индексов


28 Borland C++ Builder 6. Разработка приложений баз данных

Дополнительные возможности диалогового окна Create Table зависят от выбранного д)


БД типа драйвера. Например, для БД в формате SQL можно задать и настроить индекс!
Соответствующее диалоговое окно представлено на рис. 1.8. Работать с ним достаточв
просто. Нужно выбрать одно или несколько полей, которые будут составлять индекс, а так»
настроить порядок сортировки и уникальность. Для таблиц Paradox вы сможете задать пар.
метры ссылочной целостности (Referential integrity), вторичные индексы (Secondary indexes),
также настроить другие объекты. К сожалению, вам не удастся сделать то же для баз данных
формате SQL. Но для этого можно воспользоваться SQL Explorer.
Замечание
Вообще, следует привыкнуть к мысли, что если вы не пользуетесь какими-либ|
инструментами от сторонних разработчиков, то вам постоянно придется переключала
от SQL Explorer к Database Desktop и обратно. Это связано с тем, что не все функцш
одного из этих инструментов реализованы во втором и наоборот. После выхода первой
версии C++ Builder предполагалось, что в последующих все необходимые функции буду
доступны в SQL Explorer, a Database Desktop больше не будет поставляться с пакетом
Однако, как видим, этого не произошло.

Когда вы закончите построение таблицы, ее следует сохранить. Таблицы в формате


*.db и * dbf можно просто сохранить в соответствующих файлах, а для сохранения таблиц
в остальных форматах нужно выбрать подходящий псевдоним (Alias). Тип таблицы дол-
жен совпадать с типом драйвера псевдонима.
К сожалению, описание всех возможностей утилиты Database Desktop выходит за рам-
ки этой книги. Замечу только, что в ней имеется неплохой редактор запросов по образцу
(QBE — Query By Example), который, по сути, является визуальным построителем запро-
сов, а также множество дополнительных возможностей манипулировать структурой та-
блиц и данными. Узнать об этом подробно можно в справочной системе Database Desktop.
Кроме того, в следующих главах, при описании конкретных примеров, мы еще не раз
будем возвращаться и к Database Desktop, и к SQL Explorer.

Резюме
Эта глава посвящена вводным замечаниям о БД. В частности, рассматриваются
несколько основных подходов к классификации, вводятся основные понятия, исполь-
зуемые при дальнейшем изложении вопросов проектирования БД. Не имея опыта,
практически невозможно создать устойчиво работающую, надежную и удобную БД.
Рекомендации, представленные в этой главе, помогут не делать ошибок с самого на-
чала проектирования.
В конце главы описываются две утилиты, поставляемые вместе с 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. Разработка приложений баз данных

Есть много преимуществ использования в приложениях механизма BDE. В первую


очередь следует заметить, что это наиболее отлаженный и проверенный механизм до-
ступа к данным, так как он применялся во всех версиях C++ Builder на протяжении мно-
гих лет. Следующий аспект, на который стоит обратить внимание: BDE вплоть до пятой
версии C++ Builder был единственным механизмом доступа к данным. Это значит, что
все компоненты визуализации и управления данными (Data Controls) библиотеки VCL из-
начально оптимизировались именно под BDE. Это дает некоторый выигрыш в скорости.
Еще одно, из преимуществ дает BDE ее универсальность. Так как при помощи BDE можно
подключаться практически к любой базе данных, то затраты при переводе приложения на
базу данных другого типа минимальны. Другими словами, приложения, использующие
BDE, легко масштабируемы. И последнее преимущество, которое хотелось бы упомянуть,
— экономического характера. Формат локальных баз данных является открытым, поэто-
му за него не надо никому платить. Вследствие этого цена приложений, использующих
локальные базы данных, почти на порядок меньше точно таких же приложений, основан-
ных на базах данных SQL-форматов. В качестве примера можно привести бухгалтерские
и складские программы от фирмы 1C. Если вы разрабатываете небольшое приложение,
то вполне можно обойтись локальными базами данных, драйверы для которых являются
базовыми для BDE.
Впрочем, есть у BDE и недостатки. Универсальность BDE, предоставляя ряд пре-
имуществ, вместе с тем порождает и проблемы. Давно известно, что универсальность
и оптимальность несовместимы. Механизмы доступа, специально настроенные на кон-
кретные форматы БД, позволяют более полно и гибко использовать заложенные в них воз-
можности. Следствием универсальности является и чрезмерная требовательность к ресу-
рсам компьютера. Производительность систем на основе BDE может быть значительно
ниже, чем у систем, построенных на одном из специализированных механизмов доступа.
Не добавляют популярности BDE и разговоры о том, что в дальнейшем Borland не будет
поддерживать этот механизм.
Несмотря на все эти недостатки и слухи о скорой кончине, изучение приложений БД
решено было начать именно с использования BDE. На это есть несколько причин. Во-
первых, не очень верится, что BDE в ближайшее время потеряет значение. Во-вторых,
универсальность и стандартность подходов, вызывающие отвращение у опытных профес-
сиональных разработчиков, очень импонируют начинающим и даже достаточно опытным
программистам. Поэтому армия пользователей BDE вряд ли в скором времени уменьшит-
ся. Третья причина— чисто методическая. Изучив принципы построения приложений
на основе BDE, впоследствии гораздо проще обсуждать более сложные и специфичные
концепции, связанные с другими механизмами доступа к данным.
В следующем разделе попробуем создать простое приложение и рассмотрим его
структуру.
-

Создаем простое приложение


Прежде чем создавать приложение, использующее информацию из какой-либо ба-
зы данных, эту базу данных нужно где-то взять. Например, создать. Откройте Database
Desktop и создайте новую таблицу в формате Paradox 7. Введите определение трех полей
(см. рис. 2.1):
1. Field Name — CategoryNo, Type — Autoincrement (+), Key — * .
Глава 2. Использование механизма ВОЕ 31
2. Field Name — Category, Type — Alpha (A), Size — 25.
3. Field Name — Description, Type — Alpha (A), Size — 50.

Рис. 2.1. Структура таблицы Category


Таблица, структура которой представлена на рис. 2.1, предназначена для хранения
информации о категориях товаров, продаваемых некой гипотетической фирмой Big
Computer. Первое поле этой таблицы исключительно служебное — оно является пер-
вичным ключом, т.е. служит для однозначной идентификации каждой записи таблицы.
В качестве типа этого поля удобно выбрать Autoincrement (в соответствующей ячейке
столбца Туре отображается только символ «+»). Это значит, что задачу заполнения
поля данными берет на себя Paradox. При добавлении в таблицу новой записи в поле
CategoryNo будет подставлено числовое значение, на единицу превышающее текущее
максимальное значение этого поля. Отредактировать значение поля типа Autoincrement
невозможно.
Замечание
При вставке новой записи в таблицу новое значение не будет отображено в поле
CategoryNo. Это произойдет только после того, как вы сохраните запись в таблице.
Для этого служит кнопка Post (кнопка с галочкой) навигационной панели утилиты SQL
Explorer.

Чтобы сделать поле 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 будет отображать русский текст вполне корректно.

В дальнейшем понадобится псевдоним, ссылающийся на каталог, в котором была со-


хранена таблица. Все остальные таблицы, которые будут храниться в этом же каталоге,
также будут доступны при помощи этого псевдонима. Напомню, базой данных в случае
локальных форматов (Paradox, dBASE, FoxPro) является набор таблиц, которые хранят-
ся в одном каталоге. Итак, откройте SQL Explorer и создайте новый псевдоним типа
STANDARD. В качестве значения свойства DEFAULT DRIVER укажите PARADOX (хотя
оно является значением по умолчанию), а для свойства РАТИ задайте путь и имя катало-
га, выбранного для хранения таблицы Category (например, D:\Datd). Задайте для нового
псевдонима какое-нибудь имя (например, MyData) и сохраните изменения (пункт Apply
меню Object).
Для дальнейшего использования таблицу Category нужно заполнить данными. Для
этого также удобнее использовать SQL Explorer. Откройте созданный чуть ранее псевдо-
ним MyData и выберите таблицу (пока единственную) Category. На правой панели окна
SQL Explorer выберите вкладку Data. Заполните таблицу данными, например так, как на
рис. 2.2. Для вставки, удаления и сохранения изменений вам понадобится панель навига-
ции (в верхней правой части окна). Для вставки новой записи предназначена кнопка Insert
record (символ «+»), для удаления — кнопка Delete record (символ «-»), а для сохранения
изменений — Post edit (символ галочки).

1;Принтеры ! Принтеры струйные, матричные, лазерные


2 Сканеры
З'Ппаты материнские Платы материнские дли Intel и AMD
Л Процессоры Процессоры Intel. Amd и VIA
5 .Модули памяти Моду пи памяти DIMM. DIMM DDR и RlMM
Б Видеокарты Видеокарты Radeon и GeForce
7 Мониторы ЭПТ- и ЖКИ-мониторы
8 Винчестеры Винчестеры Fujitsu, Samsung и Western Digrtel
9.Устройства ввода Клавиатуры, мышки, джойстики и т.д.
10 Мультимедиа Эеуиоеые карты, колонки, приводы CD-ROM
11 'Модемы Внешние и внутренние модемы
t? Сетевое оборудование Сетевые карты, жабы, коммутаторы

Рис. 2.2. Данные таблицы Category


Глава 2. Использование механизма ВОЕ 33

Теперь можно приступить к созданию элементарного приложения, связанного с базой


данных. Откройте в C++ Builder новый проект (приложение) и запустите утилиту
SQL Explorer. Открыв псевдоним MyData, перетащите на форму приложения таблицу
Category. На форме появятся три компонента: TDBGrid (сетка) и два невизуальных
компонента— ТТаЫе (таблица) viTDataSource (источник данных). Невизуальные ком-
поненты обычно размещаются один над другим, так что на форме виден только один.
Перетащив его в другую область формы, вы получите доступ и ко второму. Выберите объ-
ект типа ТТаЫе и в инспекторе объектов задайте для свойства Active значение true. Все,
приложение готово. В режиме конструктора приложение будет выглядеть примерно так,
как на рис. 2.3. Обратите внимание на то, что информация из таблицы будет отображаться
в объекте типа TDBGrid даже без компиляции самого приложения.
Попробуем проанализировать, что же на самом деле произошло при простом пере-
таскивании таблицы. Каким образом была построена цепочка, в результате активиза-
ции которой компонент TDBGrid отобразил информацию из базы данных? Выберите
на форме компонент TDBGrid и просмотрите в инспекторе объектов его свойства.
Обратите внимание на свойство DataSource. Его значением является другой компо-
нент (об этом сигнализирует красный цвет наименования свойства), а именно объект
DataSourcel типа TDataSource, также добавленный на форму. Обратившись теперь
к свойствам объекта DataSourcel, заметим, что его свойство DataSet имеет значение
Table]. Свойство DatabaseName объекта Tablel типа ТТаЫе указывает на созданный
нами псевдоним MyData, а свойство TableName имеет значение Category.db. При по-
мощи этих двух свойств осуществляется подключение к нужной таблице. Активизация
цепочки таблица БД - компонент TDBGrid происходит при переключении свойства
Active компонента ТТаЫе в значение true.

1 Принтеры Принтеры струйнв ••чтриччыо i


2 Сканеры Сканеры планшетные
3 Ппаты материнские ПЛАТЫ материнские ала Intel и AMD

Рис. 2.3. Простое приложение БД в режиме конструктора

Замечание
В C++ Builder б инспектор объектов предоставляет возможность просмотреть целиком
всю цепочку между элементом управления данными (например, TDBGrid) и таблицей БД.
Щелкая мышкой на элементе развертывания слева от свойства DataSource, мы получаем
доступ к списку свойств объекта DataSourcel, среди которых есть и свойство DataSet,
имеющее значение Table?. Аналогично, разворачивая список свойств объекта Tablel, мы
можем получить доступ к его свойствам DatabaseName и TableName. Таким образом,
инспектор объектов предоставляет возможность в удобной иерархической форме
увидеть связь между конкретным элементом управления данными и таблицей БД.

Благодаря этому простому анализу можно получить представление о том, каким об-
разом элементы визуализации и управления данными (Data Controls), такие как TDBGrid
или TDBEdit, связываются с информацией из БД. В первую очередь следует выделить

2 Зак. 319
34 Borland C++ Builder 6. Разработка приложений баз данных

компонент ТТаЫе, который является одним из наследников класса TDataSet (набор да


ных). Класс ТТаЫе инкапсулирует таблицу БД, подключаясь к ней при помощи свойс
Database и TableName. Кроме ТТаЫе среди наследников TDataSet наиболее важны TQue,
(запрос) и TStoredProc (хранимая процедура).
Компонент ТТаЫе инкапсулирует таблицу БД, включая ее структуру и данные, Крол
сведений о конкретной таблице, компонент ТТаЫе содержит методы, позволяющие Heni
средственно решать разнообразные задачи, в частности, обрабатывать пароли, манипул:
ровать индексами, записями и т. д. Результаты действия большинства из этих функци
используются компонентом TDataSource, который является посредником между наборе
данных (ТТаЫе, TQuery или TStoredProc) и компонентами визуализации и управлет
данными (TDBGrid, TDBEdit и др.). На первый взгляд непонятно, зачем нужен такой п<
средник, однако со временем становится очевидно, что на самом деле его существовав
вполне естественно.
Далее в этой и следующей главе будут подробно обсуждаться наследники класс
TDataSet — ТТаЫе и TQuery, а также компоненты TDataSource, TDatabase, класс TFiek
Будет рассмотрено и множество других вопросов, что поможет создавать приличные при
ложения, основанные на использовании механизма BDE.

Использование компонента ТТаЫе


В шестой версии C++ Builder компонент ТТаЫе расположен на новой вкладке, котора!
называется BDE. Таким образом в Borland видимо решили подчеркнуть обособленное^
и неперспективность механизма BDE, перенеся на эту вкладку с вкладки Data Access вс<
связанные с BDE компоненты.
Компонент ТТаЫе — наследник класса TDataSet, использующий большую част!
объявленных в нем свойств, методов и событий. Сам класс TDataSet абстрактен, т. е
экземпляр этого типа создать нельзя. TDataSet предназначен для того, чтобы предо-
ставить интерфейс, состоящий из методов, событий и свойств, для манипуляции дан-
ными, представленными в виде строк и столбцов (т. е. в табличной форме). Большая
часть этих методов, событий и свойств чисто виртуальны, т. е. они объявлены, но не
реализованы. Любой наследник объекта TDataSet должен переобъявить и реализовать
все виртуальные методы.
Компонент ТТаЫе переопределяет виртуальные методы объекта TDataSet, вводя
при этом много новых свойств и методов, позволяющих напрямую работать с любой
таблицей БД. Можно считать ТТаЫе программной оболочкой таблицы БД. Контекстное
меню объекта ТТаЫе, подключенного к какой-либо таблице, позволяет выполнить сле-
дующее: удалить таблицу (Delete Table), переименовать ее (Rename Table), обновить
определение таблицы (Update Table Definition), открыть SQL Explorer на соответству-
ющей таблице (Explore).
Планомерное и полное изучение интерфейса ТТаЫе (стиль, предлагаемый справочной
системой) хотя и интересно, но не очень эффективно. Поэтому в этом разделе предпри-
нята попытка рассмотреть основные свойства, методы и события ТТаЫе, исходя из по-
требности в них при решении тех или иных задач. Следует заметить, что большая часть
рассматриваемых задач решается одинаково для всех основных наследников TDataSet
(ТТаЫе, TQuery, TStoredProc), поэтому, если не оговорено обратное, речь будет идти
о наборах данных. Под набором данных будет пониматься не объект TDataSet, а один из
его наследников (ТТаЫе, TQuery, TStoredProc).
Глава 2. Использование механизма BDE 35

Активация и деактивация набора данных


Изучение функциональных возможностей любого объекта вполне естественно на-
чинать с простейших задач. Именно такова активация и деактивация набора данных
(или открытие и закрытие). Если в режиме конструктора задать для свойства Active
компонента-наследника TDataSet значение true, то набор данных сможет обеспечить
все зависящие от него элементы управления данными. Задание значения false деакти-
вирует (закроет) набор данных. Все элементы управления, связанные с набором дан-
ных, очистятся. Кроме того, при изменении многих свойств набора данных требуется,
чтобы он был деактивирован (закрыт). При изменении в режиме конструктора таких
свойств, как DatabaseName или TableName (для ТТаЫе), свойство Active автоматиче-
ски принимает значение false.
Однако в подавляющем большинстве случаев свойством Active в режиме конструктора
пользуются только при отладке, или если набор данных заведомо очень мал. Например,
в таблице Category не может быть больше полусотни записей, поэтому открытие формы, эле-
менты управления которой подключены к этой таблице, не вызовет проблем. Если в режиме
конструктора задать свойству Active значение true для таблицы, имеющей сотни тысяч запи-
сей, может возникнуть несколько проблем. Например, форма для отображения данных будет
открываться неоправданно долго или будет поддерживаться связь с базой данных тогда, когда
это не нужно. В любом случае, это может привести к излишнему расходованию ресурсов.
Общий подход к использованию свойства Active таков: для него оставляют значение
false, а затем в приложении по мере необходимости открывают или закрывают набор
данных программным путем. Существует два способа управления активностью набора
данных — с использованием свойства Active или пары методов Open и Close. Объявление
свойства Active в классе TDataSet выглядит так:
property bool Active =
(read=GetActive, write=SetActive, default=0};
В программе свойство Active можно использовать для чтения состояния набора дан-
ных. В следующем фрагменте кода определяется состояние набора данных и выводится
соответствующее сообщение.
if(Tablel->Active) ShowMessage("Ha6op данных открыт!");
else ShowMessage("Набор данных закрыт!");
Так же просто используется свойство Active и для активации/деактивации набора дан-
ных. Чтобы показать это, можно воспользоваться простым приложением, созданным нами
ранее. Разместите в верхней правой части формы кнопку (компонент TButton с вкладки
Standard) и задайте для нее имя (Name) DeactButton и подпись (Caption) Deactivate. В код
обработчика ее события OnClick введите следующий текст:
Tablel->Active=!Table1->Active;
if(Tablel->Active) DeactButton->Caption="Deactivate";
else DeactButton->Caption="Activate";
Переключение состояния набора данных происходит в первой строке: значение свой-
ства Active просто меняется на противоположное. Остальные две строки кода нужны толь-
ко для того, чтобы менять соответствующим образом надпись на кнопке. Разместите на
форме еще одну кнопку. Задайте для нее надпись Status, а в тело ее обработчика события
OnClick включите код проверки состояния, приведенный чуть выше. Таким образом, на-
36 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 позволяет сделать код не-
сколько производительней.

1 ;Принт9ры Принтер» струйны


2-Сканеры 'Сканеры лланшетн:
3'Платы матер Ни ИШНЗгски
' -1!Проиессоры""р НЕ*"!"!^.
Б;Мряупи псмч-" .I^»fio^:aeHM*'0'TXjjt#?{'' " j, DIMM. DIMM DDR и RIN-f^
Ь Видеокарты ' ":/
^Мониторы'

Рис. 2.4. Иллюстрация возможностей свойства Active

Свойства подключения к таблице


Непосредственно за подключение компонента ТТаЫе к таблице отвечают свойства
DatabaseName и TableName. Есть еще свойство Exclusive, которое позволяет открывать та-
блицы Paradox и dBASE в монопольном режиме. Пришло время познакомиться поближе
с вариантами использования этих свойств.
В качестве значения свойства DatabaseName в конструкторе обычно указывается имя
одного из зарегистрированных в системе псевдонимов. Вы можете выбрать любой псев-
доним из выпадающего списка, ассоциированного со свойством. Однако использование
псевдонимов — не единственный способ задания целевой базы данных для локальных
таблиц (для таблиц dBASE, FoxPro, Paradox). Для локальных таблиц в качестве значения
свойства DatabaseName можно указать каталог, в котором хранятся таблицы. Например,
Глава 2. Использование механизма ВОЕ 37

если бы мы не зарегистрировали псевдоним MyData, то доступ к таблице Category


можно было бы получить, указав в качестве значения свойства DatabaseName строку
«D:\Data» (или тот путь, который вы выбрали для хранения таблицы Category).
Независимо от того; каким образом была указана база данных, выпадающий список, ас-
социированный со свойством TableName, будет содержать список доступных в базе данных
таблиц. Если в свойстве DatabaseName указан каталог, то в списке могут быть одновременно
таблицы различных типов: dBASE, FoxPro, Paradox. Как ни парадоксально это звучит, но
в этом случае непонятен будет тип базы данных. Так как для локальных таблиц базой данных
является совокупность таблиц, находящихся в одном каталоге, то вполне логично с этой точки
зрения, что в одну базу данных будут входить таблицы трех совершенно разных типов (не го-
воря уже о различиях в версиях). Конечно же, такую ситуацию допускать нежелательно.
Гораздо большие возможности предоставляются при использовании свойств
DatabaseName и TableName программным путем. Объявления этих свойств выглядят так:
property AnsiString DatabaseName =
{read=FDatabaseName, w r i t e = S e t D a t a b a s e N a m e } ;
property AnsiString TableName =
{read=FTableName, write=SetTableName};
Ниже приведено два отрывка кода, иллюстрирующих применение свойств
DatabaseName и TableName. В первом отрывке используется псевдоним, а во втором —
каталог с таблицами.
Tablel->Active = false;
Tablel->DatabaseName = "MyData";
Tablel->TableName = "Category";
Tablel->Active = true;
Tablel->Active = false;
Tablel->DatabaseName = "D:\Data";
Tablel->TableName = "Category";
Tablel->Active = true;
В обоих случаях в первой строке закрывается набор данных, так как при открытом
наборе данных следующие действия производить нельзя. Если вы уверены, что набор
данных уже закрыт, то первая строка не нужна.

Таблица 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.

Рис. 2.5. Таблица Items в режиме конструктора.


На следующем этапе нужно связать таблицы Items и Category. Для этого выбери-
те в комбинированном списке Table properties (в правом верхнем углу окна) позицию
Referential Integrity и нажмите кнопку Define. На экране будет отображено диалоговое
окно, которое поможет вам задать необходимую связь. В левой части этого окна находится
Глава 2. Использование механизма ВОЕ 39
список полей таблицы Items (см. рис. 2.6), а в правой — список таблиц базы данных (т.е.
таблиц, расположенных в выбранном каталоге). Между этими списками расположены два
поля: слева — Child fields, а справа — Parent s key. Выберите в левом списке поле Category
и нажмите кнопку с жирной стрелкой вправо. В результате его название и тип появятся
в поле Child fields. В правом списке выберите таблицу Category и нажмите на кнопку
с жирной стрелкой влево. Database Desktop подставит в поле Parent s key имя первичного
ключа таблицы Category. Далее, выберите переключатель Cascade в группе Update rule,
чтобы при изменении первичного ключа таблицы Category каскадно обновлялись значе-
ния связанного с ним внешнего ключа таблицы Items. Наконец, установите флажок Strict
referential integrity. Это заставит Paradox следить за выполнением всех предусмотренных
правил целостности данных.
Замечание
Если первичный ключ является составным, т.е. содержит несколько полей, то
и внешний ключ должен содержать столько же полей соответствующих типов (в порядке
их перечисления).

Рис. 2.6. При помощи диалогового окна Referential Integrity


утилиты Database Desktop можно задать связь между таблицами
После всех операций по установке связи нажмите кнопку ОК. Вам будет предложено
указать имя для созданного ограничения на ссылочную целостность. Можно указать, на-
пример, имя Category — по имени внешнего ключа, участвующего в связи. Чтобы под-
черкнуть, что речь идет именно о ссылочной целостности (Referential Integrity), в конец
имени можно добавить буквы R и I — CategoryRI.
Замечание
После того как вы установите связь между таблицами Items и Category, закройте
таблицу Items и откройте таблицу Category. В комбинированном списке Table properties
выберите пункт Dependent tables (зависимые таблицы). Вы можете убедиться в том, что
таблица Items.db появилась в списке таблиц, зависящих от таблицы Category.

Так как предполагается, что таблица Items может быть достаточно большой, для уско-
рения поиска, выборки и сортировки нужно определить индексы. Для этого выберите
в комбинированном списке Table properties пункт Secondary Indexes. Обратите внимание
на то, что в списке вторичных индексов уже присутствует индекс Category. Этот индекс
был создан автоматически на основе внешнего ключа при создании связи.
40 Borland C++ Builder 6. Разработка приложений баз данных

Предполагая, что основные операции поиска и сортировки будут проводиться с участие\


поля Item, именно по этому полю и создадим еще один вторичный индекс. Нажмите кнопк>
Define и в появившемся диалоговом окне Define Secondary Index в списке полей (Fields) выбе-
рите поле Item. Нажмите кнопку с жирной стрелкой вправо, чтобы перевести выбранное поле
в список индексированных (Indexedfields) и нажмите кнопку ОК. Вам будет предложено веот
для нового индекса имя. Укажите что-нибудь вроде ItemNDX(cM. рис. 2.5).
Замечание
Чтобы проверить, как изменилась производительность при появлении нового индекса,
можно добавить еще один индекс, например составной, включающий поля Category
и Item. Однако для таблиц, содержащих не более двух-трех тысяч записей, изменение
производительности при добавлении такого индекса вряд ли будет заметным.

Сохраните таблицу в том же каталоге Data под именем Items и закройте Database
Desktop. Чтобы заполнить таблицу Items данными, откройте для нее SQL Explorer и перей-
дите на вкладку Data. При этом не забудьте, что все поля, кроме Description, — обязатель-
ны. Поэтому попытка оставить любое из этих полей пустым (кроме поля ItemNo, которое
является автоинкрементным) при переходе на другую запись вызовет ошибку. Вызовет
ошибку и ввод в поле Category значения, отсутствующего в поле CategoryNo таблицы
Category.
Таблица Items, содержащая все необходимые данные, располагается на поставляемом
с книгой диске в каталоге Data.

Навигация по набору данных


Сразу после открытия непустого набора данных активной становится его первая за-
пись. Для навигации по таблице кроме стандартных инструментов (мышки, полос про-
крутки, клавиш управления курсором) есть и специфические средства. К ним относится
компонент TDBNavigator и набор методов навигации.
Чтобы проиллюстрировать использование компонента TDBNavigator, создайте но-
вое приложение, добавьте в форму Form] компоненты TTable, TDataSource и TDBGrid
и свяжите их так, как описано в предыдущем примере. В качестве источника данных для
компонента ТТаЫе укажите таблицу Items и задайте для свойства Active значение true.
В верхнюю правую часть окна формы добавьте компонент TDBNavigator. Этот компо-
нент, как и большинство других компонентов, ориентированных на данные из таблиц
(вкладка Data Controls), имеет свойство DataSource. При помощи этого свойства свяжите
TDBNavigator с компонентом TDataSource. Скомпилируйте приложение и запустите его
на выполнение.
Строго говоря, панель навигации TDBNavigator, как и соответствующая панель
утилиты SQL Explorer, предназначена не только для навигации по записям. Собственно
для навигации используются только первые четыре кнопки (крайние слева). Функции
этих кнопок, если двигаться слева направо, таковы (см. рис. 2.7): переход к первой
записи (First), к предыдущей записи (Prior), к следующей (Next), к последней (Last).
Остальные кнопки панели навигации используются для манипуляций записями, так
что изображенное на рис. 2.7 приложение вполне можно использовать для заполнения
таблицы Items данными.
Если какие-либо из кнопок на панели навигации не нужны, их можно удалить. Для
этого существует свойство панели навигации VisibleButtons. Это агрегатное свойство
Глава 2. Использование механизма BDE 41
представляет собой набор свойств логического типа. Каждое из этих свойств соответству-
ет конкретной кнопке на панели навигации. Установив значение такого свойства в false,
вы удалите соответствующую кнопку с панели. При помощи набора этих свойств можно
управлять видом панели, как в режиме конструктора, так и программно.

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

Рис. 2.7. Компонент TDBNavigator позволяет не только перемещаться


по набору данных, но и манипулировать записями
Для навигации по набору данных в процессе выполнения программы в компоненте
ТТаЫе предусмотрены пять методов и два свойства:
void _fastcall Fi rst(void) // Методы
void _fastcall Prior(void)
void _fastca!1 Next(void);
void fastcall Last(void) ;
int fastcall MoveBy(int Distance);
property bool Bof = (read = FBof, nodefault}; //Свойства
property bool Eof = (read=FEof, nodefault};
Первые четыре метода по функциям аналогичны соответствующим кнопкам панели
навигации. Метод MoveBy перемещает позицию курсора в наборе данных на Distance за-
писей вперед. Значение Distance может быть как положительным, так и отрицательным.
В последнем случае курсор будет перемещаться по набору данных в сторону первой за-
писи (назад). В частности, вызов метода MoveBy(1) эквивалентен вызову метода Next{),
а вызов MoveBy(-l)— вызову Prior(). Метод MoveBy() возвращает количество записей,
на которые был перемещен указатель активной записи. В большинстве случаев возвра-
щаемое значение совпадает со значением Distance. Но если вы хотите перейти за пределы
набора данных, указатель будет установлен на первую или последнюю запись набора,
в зависимости от направления перехода, а возвращено будет количество записей, на кото-
рые был осуществлен переход.
Свойства Bof (Begin of file) и Eof (End of file) позволяют определить, достигнута ли пер-
вая и последняя запись набора данных соответственно. Свойство Bof принимает значение
true в одном из четырех случаев:
U при открытии набора данных;
Q при вызове метода First (или при нажатии соответствующей кнопки панели навигации,
что одно и то же);
42 Borland C++ Builder 6. Разработка приложений баз данных

О при вызове метода Prior, если активной была первая запись;


Q при вызове метода SetRange для пустого диапазона или набора данных.
Во всех остальных случаях свойство Яо/имеет значение false. Почти то же можно ска
зать и о свойстве Eof, которое принимает значение true также в четырех случаях:
G при открытии пустого набора данных;
G при вызове метода Last;
О при вызове метода Next, если активной была последняя запись;
1_1 при вызове метода SetRange для пустого диапазона или набора данных.
Свойства Во/ и Eof доступны только во время выполнения программы. С их по-
мощью, например, можно организовать цикл просмотра всех записей набора данных.
Когда происходит определенное событие (например, событие AfterScroll компонента
TTable) можно проверить значение свойств Во/и Eof, чтобы, например, разрешить или
запретить доступ к тем или иным кнопкам или другим элементам управления. Еще
один, менее очевидный вариант — если загружена не вся таблица, то при попытке
перейти за первую (Bof = = true + метод Prior) или последнюю (Eof ' = = true + метод
Next) запись загруженного набора данных можно организовать подкачку предыдущей
или последующей части таблицы.
Чтобы проиллюстрировать некоторые способы использования методов навигации
и свойств Bof и Eof компонента TTable, сконструируем небольшое приложение. Итак,
создайте новое приложение VCL и расположите на пустой форме три компонента TPanel.
Выровняйте компоненты TPanel по высоте и расположите их друг за другом по горизон-
тали в верхней части рабочей области окна формы, очистив их свойства Caption. Чтобы
рельефно отделить каждую из трех групп элементов управления, которые планируется
разместить на панелях, можно установить для каждой панели свойство Bevellnner в значе-
ние bvRaised, а свойство BevelOuter в значение bvLowered.
На панель, расположенную слева, поместите четыре кнопки (компоненты TButton).
Задайте для их свойств Name значения (слева направо): BFirst, BPrior, BNext и BLast,
а для свойств Caption — значения First, Prior, Next и Last соответственно. Для свойства
Tag кнопок последовательно задайте значения 1 (BFirst), 2 (BPrior), 3 (BNext) и 4 (BLast)
Измените размеры кнопок так, чтобы на них помещались их надписи, и разместите их
компактно на панели. Подровняйте размер панели.
На среднюю панель поместите два компонента TEdit, а между ними кнопку TButton. Для
кнопки задайте имя BMoveBy (свойство Name) и подпись (Caption) MoveBy. Для компонентов
TEdit следует очистить свойство Text. Задайте для них следующие имена (слева направо):
EPlan и EReally. Подровняйте размеры и положение всех элементов. И, наконец, на третьей
панели расположите два компонента TEdit и слева от каждого объекта разместите метку
(TLabel). Для меток задайте подписи (Caption): Bof и Eof (слева направо). Для компонентов
TEdit очистите свойство Text и задайте имена: EBofu EEof. В заключение, как всегда, следует
подровнять размеры и положение всех элементов управления.
Ниже панелей разместите сетку (компонент TDBGrid(DBGridl)), а также компоненты
TDataSource (DataSourcel) и TTable (Tablet). Свяжите их между собой так, как это опи-
сывалось выше. Компонент Table 1 подключите к таблице Items, а для его свойства Active
задайте значение false. Получившаяся форма должна выглядеть примерно так, как изо-
бражено на рис. 2.8.
Глава 2. Использование механизма ВОЕ 43

* : i Fffrfj Pnpfj Ne^j: 1!шI ' ; i ~% McveByj j

Рис. 2.8. Приложение, иллюстрирующее применение


методов навигации и свойств Bof и Eof
На следующем шаге нужно создать код, который заставит работать все компоненты,
как единое целое. Выделите на форме кнопку BFirst, перейдите в инспекторе объектов
на вкладку Events и дважды щелкните мышкой напротив события OnClick. C++ Builder
сформирует заготовку обработчика события BFirstClick. Задайте этот же обработчик со-
бытий OnClick и для остальных кнопок группы (выбрав имя обработчика из выпадающего
списка). В тело обработчика события введите следующий код:
void fastcall T F o r m l : : B F i r s t C l i c k ( T O b j e c t *Sender)

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;

В первой строке обработчика объявляется временная переменная Temp типа TButton.


Во второй строке этой переменной присваивается результат динамического приведения
к типу TButton аргумента обработчика Sender. Так проверяется, был ли вызван обработчик
44 Borland C++ Builder 6. Разработка приложений баз данных

события для кнопки или для другого компонента (например, по ошибке). Все эти дей
ствия нужны в связи с тем, что один и тот же обработчик события используется сразу дл:
четырех кнопок. Если динамическое приведение было успешным, проверяется свойств!
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

void fastcall TForml::TablelAfterScroll(TDataSet *DataSet)

if(DataSet->Bof) EBof->Text="true";
else EBof->Text="false";
if(DataSet->Eof) EEof->Text="true";
else EEof->Text="false";

В тело обработчика события AfterScroll передается один параметр — указатель на ак-


тивный набор данных DataSet. Проверяя значения свойств .So/и .Ео/этого набора данных,
мы можем задать соответствующие значения для элементов редактирования ЕВо/н EEof.
В заключение остается задать действия, которые нужно произвести при создании формы,
а именно: открыть набор данных и запретить доступ к кнопкам BFirst и BPrior (так как
активной будет первая запись). Ниже приведен код обработчика события OnCreate формы
Forml:
void fastcall TForml::FormCreate(TObject *5ender)

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>

class TForml : public TForm

published: // IDE-managed Cdmponents


TTable *Tablel;
TDataSource *DataSourcel;
TDBGrid *DBGridl;
TButton *BFirst;
TButton *BPrior;
46 Borland C++ Builder 6. Разработка приложений баз данных

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);

extern PACKAGE TForml *Forml;


//-- ........ -
#endif

Листинг 2.2. Главный модуль приложения NavMethods

#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"

#pragma package(smart_init)
#pragma resource "*.dfm"
TForml *Forml;
_fastcall TForml : :TForml(TComponent* Owner)
: TForm(Owner)

void _ fastcall TForml : :FormCreate(TObject *Sender)


{
Tablel->0pen() ;
BFi rst->Enabled=false;
BPrior->Enabled=false;

void _ fastcall TForml : :BFirstClick(TObject *Sender)


Глава 2. Использование механизма ВОЕ 47
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 ;
BP r i or ->Enabled=! Table l->Bof;
BNex t->Enabled=! Table! ->Eof;
Bias t->Enabled=! Table!- >Eof ;

void _ fastcall TForml: :BMoveByClick(TObject *Sender)


t
EReally->Text=IntToStr
(Tablel->MoveBy(StrToIntDef (EPlan->Text ,0)

void _ fastcall TForml : :EPlanKeyPress(TObject *Sender, char &Key)


{
if (Key == VK_RETURN) BMoveByClick(NULL) ;

void _ fastcall TForml: :TablelAf terScroll (TDataSet *DataSet)


if(DataSet->Bof) EBof->Text="true";
else EBof->Text="false"
if (DataSet->Eof) EEof->Text="true";
else EEof->Text="false"

Поля (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.

Наиболее важные методы контейнера TFields — методы, предназначенные для досту-


па к объектам полей (TField). Вот их определение:
TField* operator [] (int Index) { return Fields[Index] ; }
TField* fastcall FieldByNumber(int FieldNo);
TField* fastcall FieldByName(const AnsiString FieldName);
Оператор [] служит для доступа к полям по их индексу с использованием синтакси-
са массива. Индексы присваиваются полям по мере их объявления в наборе данных (не
в таблице БД!). По умолчанию в наборе данных используются все поля из источника
данных, причем располагаются они в той же последовательности. Например, в таблице
Items индекс 0 имеет поле ItemNo, индекс 1 — поле Item, а полю Price соответствует ин-
декс 5. Свойство Count имеет значение 6. Все те же поля будут использоваться и в наборе
данных с теми же значениями индекса: полю ItemNo будет соответствовать индекс 0, полю
Item — индекс \ и т.д.
Состав и порядок следования полей в наборе данных по сравнению с таблицей
можно изменить. Для этого предназначен редактор полей (Fields Editor), который ото-
бражается на экране после двойного щелчка мышкой на объекте типа ТТаЫе. О работе
с редактором полей будет рассказано чуть позже, пока же ограничусь тем, что с его
помощью можно настроить набор полей для компонента ТТаЫе. Например, вы хотите,
чтобы поле Description было последним по счету в наборе данных, а не четвертым, как
в таблице. Чтобы добиться этого, необходимо дважды щелкнуть мышкой на компоненте
ТТаЫе, затем правой кнопкой мышки щелкнуть в рабочей области окна редактора полей
и выбрать из контекстного меню пункт Add fields. В следующем диалоговом окне вы-
берите все поля, кроме поля Description (чтобы выделять их вразброс, используйте кла-
вишу Ctrl) и нажмите кнопку ОК. Проделайте все то же снова, но на этот раз выберите
поле Description. В результате в набор данных будут добавлены определения всех полей
Глава 2. Использование механизма ВОЕ 49

таблицы, но в нужном порядке.


Вышеописанные действия нужны для того, чтобы пояснить разницу между операто-
ром /7 и методом FieldByNumber. Оба метода возвращают указатель на поле, принимая
в качестве аргумента его индекс. Однако метод FieldByNumber указывает на поле табли-
цы, являющейся источником данных для набора данных, в то время как оператор [] ука-
зывает на поле набора данных.
Для иллюстрации, как всегда, разработаем небольшое приложение. Создайте но-
вое приложение, и на пустую форму поместите метку (компонент TLabel), компонент
TListView, а также компонент ТТаЫе. Оставьте наименования всех объектов по умолча-
нию. Объект ТТаЫе подключите к таблице Items базы данных MyData. Далее, при помо-
щи редактора полей создайте определения всех полей таблицы, как это было описано вы-
ше (поле Description добавьте последним). Затем следует настроить компонент TListView.
Дважды щелкнув мышкой на самом компоненте, отобразите на экране редактор столбцов
и введите определение трех заголовков: Index, Fieldsflndex] и FieldByNumber [Index]. Чтобы
указанные столбцы отображались в компоненте, установите для свойства ViewStyle значение
vsReport. В тело обработчика события OnCreate формы введите следующий код:
void _ fastcall TForml: :FormCreate(TObject *Sender)
{
Tablel->0pen();
Labell->Caption=
"Таблица Items содержит " +
IntToStr(Tablel->Fields->Count)
+ " полей : " ;

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) ;

Откомпилируйте приложение и запустите его на выполнение. Обратите внимание на


разницу в результатах работы оператора [] и FieldByNumber (см. рис. 2.9).
Замечание
Свойство Count класса TF/e/ds возвращает количество полей в наборе данных, а не
в нижележащей таблице. Если, например, в набор данных добавить пять полей из
таблицы Items, то свойство Tab/e7->F/e/ds->Count возвратит значение 5, в то время как
в самой таблице останется шесть полей. То же значение вернет и свойство F/eldCount
компонента ТТаЫе (точнее, объекта TDataSet).

В большинстве случаев, когда необходимо получить доступ к определенному полю,


удобней пользоваться методом FieldByName. Этот метод возвращает указатель на объект
TField, в качестве аргумента принимая имя поля. Это позволяет не задумываться о том,
какой индекс в наборе данных имеет поле — вы просто указываете его имя и получаете
50 Borland C++ Builder 6. Разработка приложений баз данных

доступ к его свойствам. Ниже приведено несколько примеров использования метод


FieldByName,
Tablel->FieldByName("Item") ' //Указатель на поле Item
Tablel->FieldByName("Description") //Указатель на поле Description

Таблица Hems cc

l( rr 4 0
Р ::
Item temNo ?. '' '
Iten
2 ' Category : Category
* Jridex .. _ .1 Description
4 . Price i Index
5 : Qeacriptipn : Price

, _

Рис. 2.9. Метод FieldByNumber обращается к полям в том порядке,


в котором они следуют в таблице, а не в наборе данных

Фильтрация
Чтобы не отображать для пользователя всю таблицу, что может быть сопряжено
с большими неудобствами и значительными затратами ресурсов, следует прибегнуть
к фильтрации. 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

SetRangeStart и SetRangeEnd. Изменить граничные значения индекса можно при по-


мощи методов EditRangeStart и EditRangeEnd.
Q Чтобы применить фильтр, следует вызвать метод ApplyRange. Метод CancelRange слу-
жит для отмены фильтра.
Ниже приведен код, при помощи которого для таблицы Items устанавливается фильтр
по полю Category. Это поле — внешний ключ, поэтому Paradox автоматически создает
для него индекс. В итоговый набор попадут только те записи из таблицы Items, значение
поля Category для которых заключено в пределах от 3 до 5.
Table1 ->DatabaseName="MyData";
Tablel->TableName="Items.db";
Tablel->IndexFieldNames="Category";
Tablel->0pen();
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=3;
Tablel->SetRangeEnd();
Table!->FieldByName("Category")->AsInteger=5;
Tablel->ApplyRange();
Вместо пары методов SetRangeStart и SetRangeEnd и метода ApplyRange можно ис-
пользовать метод SetRange, совмещающий их функции. Метод SetRange- получает набор
начальных и конечных значений индекса (если индекс составной) и на основе этой инфор-
мации применяет фильтр. Вот как можно переписать приведенный выше код:
Tablel->DatabaseName="MyData";
Tablel->TableName="Items.db";
Tablel->IndexFieldNames="Category";
Tablel->0pen() ;
Tablel->SetRange(OPENARRAY(TVarRec,(3)), OPENARRAY(TVarRec,(5)));
Замечание
Макро OPENARRAY предназначен для того, чтобы собрать несколько значений в так
называемый открытый массив, в конце которого указывается индекс последнего элемента.
Таким образом, выражение OPENARRAY(TVarRec,(3)) задает первых два аргумента
метода SetRange, а выражение OPENARRAY(TVarRec,(5)) - два последних. Конструкция
ОР£Л/АЯКАУспециально разработана для эмуляции массивов Open/Array из Object Pascal,
предназначенных для передачи в функцию переменного числа параметров. Несмотря
на то, что в C++ есть свои средства для передачи переменного числа параметров, VCL,
написанная именно на Object Pascal, требует использования открытых массивов. Более
подробно о макро OPENARRAY и похожем на него макро ARRAYOFCONST можно узнать
в справке Borland C++ Builder.

Для иллюстрации совместного использования методов навигации и фильтрования раз-


работаем небольшое приложение. Как всегда, создайте новое приложение, и на пустую
форму поместите компоненты TDBGrid, ТТаЫе и TDataSource, связав их между собой
и с таблицей Items так, как это описывалось ранее. Оставьте для этих элементов имена,
принятые по умолчанию. В верхней части формы расположите две панели (TPanel). На
одну из панелей поместите четыре кнопки (TButton). Последовательно задайте для этих
кнопок имена: BPriorCat, BNextCat, BPriorRec, BNextRec, и подписи: Prior Category, Next
Category, Prior Record, Next Record. На вторую панель поместите поле ввода (TEdii), метку
52 Borland C++ Builder 6. Разработка приложений баз данных

(TLabel) и две кнопки. Для кнопок задайте имена BApply, BCancel и подписи Apply Rang:
и Cancel Range соответственно. Поле ввода назовите ECat и очистите его свойство Text
для метки задайте подпись Category. Разместите все элементы управления примерно так
как на рис. 2.10.

tefrtNe|ft*m --. : '-I .,'... jCetago-ylDescrioion *]


t 1 MustekScariExpress1200UBPk
2 Mustek ScenExpress 1200 USB F 2 CCD. планшетный. А.4. 36 бит. f \.Т;Г"
• 3 Mustek ScnnExpress A3 EP 2 OS. планшетный. A3, 36 бит, 'Л — i.
A HP ScanJet 2200C 2 • CCD. планшетный. А4 True Col> : !:i
5 HP ScanJet Э4СОС 2 СШпленшетныЯА<ТгиеСоК;;:|
6 HP ScanJet 44COC 2 CCD, планшетный. А4, True Col' . ;,
7 HPScartJeH47QC 2 CCO, планшетный, А< True Col.; ^il
8 HP ScanJet 540QC 2 : CCO, планшетный. At True Coir .: ;.

iLJ

Рис. 2.10. Главная форма приложения ApplyRangeDemo


В дальнейшем нам понадобится переменная для хранения текущего значения катего-
рии. Откройте модуль формы, и ниже объявления переменной Forml, перед первой функ-
цией модуля, укажите следующее объявление:
long int C a t ;
Объявленная таким образом переменная Cat будет доступна из любой функции
модуля формы. Далее создайте обработчик события OnCreate формы и введите в него
следующий текст:
void _ fastcall TForml: :FormCreate(TObject *Sender)
{
Tablel->IndexFieldNames="Category" ;
Tablel->0pen() ;
Cat=l;
SetFilterCat(l) ;
BPriorCat->Enabled=false;
BPriorRec->Enabled=false;

В первых строках обработчика события FormCreate задается имя индексированного


поля, по которому предполагается установить фильтр (Category), и открывается набор
данных, связанный с таблицей Items. Далее переменной Cat присваивается начальное
значение, и по этому значению применяется фильтр (функция SetFilterCai). Так как сразу
после открытия активной становится первая запись набора, вполне логично запретить
доступ к кнопкам BPriorCat (переход к предыдущей категории) и BPriorRec (переход
к предыдущей записи). Для этого предназначены последние две строчки обработчика.
Функция SetFilterCat предназначена для установки фильтра по указанному значению
категории. Переключитесь на файл заголовка модуля формы (Unitl.h, если вы не пере-
именовывали модуль формы), и укажите следующее объявление в разделе private (можно
и в разделе public, но это будет дурным тоном, так как вы предоставляете стороннему
пользователю объекта доступ к его внутренней реализации):
void _ fastcall SetFilterCat (long int Cat);
Глава 2. Использование механизма ВОЕ 53

В самом модуле Unit 1.с введите следующее определение:


void fastcall TForml::SetFilterCat(long int Cat)
{
if(Cat == 0) return;
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->SetRangeEnd();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->ApplyRange();
ECat->Text=IntToStr(Cat);
BApply->Enabled=false;
BCancel->Enabled=true;
}
В первой строке функции SetFilterCat проверяется, установлен ли фильтр. Если
переменная Cat равна 0, то фильтр не установлен. В этом случае ничего делать не
нужно. В последних трех строках функции SetFilterCat в поле ввода ECat помеща-
ется значение текущей категории и соответствующим образом настраивается доступ
к кнопкам Apply Range и Cancel Range. В остальных строках устанавливается фильтр,
благодаря которому в сетке TDBGrid выводятся записи, соответствующие только
указанной категории. Свойство Aslnteger класса TField преобразует значение указан-
ного поля к типу Integer. Кроме свойства Aslnteger класс TField имеет еще несколько
свойств, связанных с преобразованием значения поля к необходимому типу. Обо всем
этом будет рассказано чуть позже.
Замечание
В классе ТТаЫе объявлено свойство KeyExclusive типа boo/; в некоторых ситуациях
оно может быть очень полезно. С его помощью можно исключить из устанавливаемого
диапазона значение индекса, используемого в качестве начала или конца диапазона при
фильтрации. Например, в результате выполнения следующего отрывка кода в итоговый
набор данных попадут только записи, соответствующие категориям 4 и 5.
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=3;
Tablel->KeyExclusive=false;
Tablel->SetRangeEnd();
Tablel->FieldByName("Category")->AsInteger=5;
Tablel->ApplyRange();

Для каждой из четырех кнопок, расположенных на левой панели, создайте обработ-


чики событий OnClick. Для кнопки BPriorCat в обработчике события OnClick укажите
вызов метода Tablel->First(), для кнопки BNextCat — метода Tablel->Last(), а для кнопок
BPriorRec и BNextRec — соответственно вызовы методов Tablel->Prior() и Tablel->Next().
Хитрость здесь в том, что при достижении начала или конца набора данных (т. е. при до-
стижении свойствами Во/или Eof значения true) изменяется текущее значение категории,
и по этому значению устанавливается фильтр. Остальные части обработчиков событий
OnClick кнопок навигации (BPriorCat, BNextCat, BPriorRec и BNextRec) связаны с отсл-
еживанием начала и конца отфильтрованного или неотфильтрованного набора данных
54 Borland C++ Builder 6. Разработка приложений баз данных

и с заданием соответствующих значений для свойства Enabled каждой из этих кнопок


Если фильтр снят с набора данных, то все кнопки ведут себя прогнозированным образом
перемещая к первой, последней, предыдущей или последующей записи набора.
Обработчики событий OnClick оставшихся двух кнопок, отменяют или вновь уста
навливают фильтр. Значение категории, в соответствии с которым будет устанавливать^
фильтр, можно задать в поле ввода ECat. Откомпилируйте приложение и запустите его нг
выполнение. Попробуйте перемещаться по набору данных при помощи кнопок навига-
ции. Обратите внимание на то, как будет переключаться набор данных с одной категориь
на другую.
Конечно, предлагаемое вашему вниманию приложение — не образец устойчивости
и надежности, но оно вполне приемлемо иллюстрирует основные принципы навигации
и фильтрации. Полностью код приложения приведен в листингах 2.3 и 2.4.
Листинг 2.3. Файл заголовка приложения ApplyRangeDemo
// -
#ifndef UnitlH
#define UnitlH
//
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <DB.hpp>
#include <DBGrids.hpp>
#include <DBTables.hpp>
^include <Gn'ds.hpp>
#include <ExtCtrls.hpp>
//
class TForml : public TForm
{
published: // IDE-managed Components
TDBGrid *DBGridl;
TTable Tablel;
TDataSource *DataSourcel;
TPanel *Panell;
TButton *BPriorCat;
TButton *BPriorRec;
TButton *BNextCat;
TButton *BNextRec;
TPanel *Panel2;
TEdit *ECat;
TLabel *Labell;
TButton *BApply;
TButton *BCan.cel;
void fastcall FormCreate(TObject «Sender);
void fastcall BPriorCatClick(TObject *Sender);
void fastcall BNextCatClick(TObject *Sender);
void fastcall BNextRecClick(TObject *Sender);
void fastcall BPriorRecClick(TObject *Sender) ;
void fastcall BCancelClick(TObject *Sender);
void fastcall BApplyClick(TObject *Sender);
Глава 2. Использование механизма ВОЕ 55

private: // User declarations


void _ fastcall SetFiUerCat(long int Cat);
public: // User declarations
_fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


//
#endif
Листинг 2.4. Главный модуль приложения ApplyRangeDemo

#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)

void _ fastcall TForml: :FormCreate(TObject *Sender)


{
Tablel->IndexFieldNames="Category" ;
Tablel->0pen() ;
Cat=l;
SetFilterCat(l);
BPriorCat->Enabled=f alse;
BPriorRec->Enabled=false;

void _ fastcall TForml: :BPriorCatClick(TObject *Sender)


{
Tablel->First() ;
if (Cat != 0)
{
if(Cat > 1) Cat--;
SetFilterCat(Cat);
ECat->Text=IntToStr(Cat) ;
Tablel->Last() ;
}
if(Cat < 2) BPriorCat->Enabled=false;
if (BNextCat->Enabled == false) BNextCat->Enabled=true;
if (BNextRec->Enabled == false) BNextRec->Enabled=true;
56 Borland C++ Builder 6. Разработка приложений баз данных

void _ fastcall TForml : : BNextCatClick(TObject *Sender)


{
Tablel->Last() ;
if(Cat != 0)
{
if (Cat 12) Cat++;
SetFilterCat(Cat) ;
ECat->Text=IntToStr(Cat) ;
}
if(Cat == 12) BNextCat->Enabled=false;
if (BPriorCat->Enabled == false) BPriorCat->Enabled=true;
if (BPriorRec->Enabled == false) BPriorRec->Enabled=true;

void _ fastcall TForml : :BNextRecClick(TObject 'Sender)


{
Tablel->Next();
if (Tablel->Eof)
{
if ((Cat == 12) | | (Cat == 0))
{
BNextRec->Enabled=false;
BNextCat->Enabled=false;
}
if(Cat != 0)
{
if(Cat 12) Cat++;
SetFilterCat(Cat) ;
ECat->Tex.t = IntToStr(Cat) ;

if (BPn'orRec->Enabled == false) BPriorRec->Enabled=true;


if((Cat == 2) && (BPriorCat->Enabled == false))
BPriorCat->Enabled=true;
if((Cat == 12) && (BNextCat->Enabled == true))
BNextCat->Enabled=false;

void _ fastcall TForml : :BPriorRecClick(TObject *Sender)


{
Tablel->Prior() ;
if(Tablel->Bof)
{
if(Cat < 2) BPriorRec->Enabled=false;
else
{
if (Cat > 1) Cat--;
SetFilterCat(Cat) ;
ECat->Text=IntToStr(Cat) ;
Tablel->Last() ;
Глава 2. Использование механизма ВОЕ 57

if(BNextRec->Enabled == false) BNextRec->Enabled=true;


if((Cat == 11) && (BNextCat->Enabled == false))
BNextCat->Enabled=true;
if((Cat == 1) && (BPriorCat->Enabled == true^)
BPriorCat->Enabled=false;

void fastcall TForml::5etFilterCat(long int Cat)


if(Cat == 0) return;
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->SetRangeEnd();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->ApplyRange();
ECat->Text=IntToStr(Cat);
BApply->Enabled=false;
BCancel->Enabled=true;

void fastcall TForml::BCancelClick(TObject *5ender)


Cat=0;
Tablel->CancelRange();
BApply->Enabled=true;
BCancel->Enabled=false;
ECat->Text="All";
BPriorCat->Caption="Fi rst Record";
BNextCat->Caption="Last Record";

void fastcall TForml::BApplyClick(TObject «Sender)


Cat=StrToIntDef(ECat->Text,0);
if ((Cat < 1) || (Cat > 12)) Cat=l;
SetFilterCat(Cat);
BPriorCat->Caption="Prior Category";
BNextCat->Caption="Next Category";

Еще один способ фильтрации набора данных — использование свойств Filter, Filtered
и FilterOptions. Все они объявлены в классе TBDEDataSet, который является промежуточ-
ным между классом TDataSet и ТТаЫе. Способ фильтрации с применением свойств Filter
и Filtered — наиболее общий, гибкий и, что немаловажно, очень простой. Этот способ не
требует применения индексов. Вы можете задавать фильтр с использованием любого поля
или произвольного набора полей набора данных.
Вначале необходимо задать в свойстве Filter строку условия, которому должны удо-
влетворять записи набора данных, чтобы попасть в итоговую выборку. Простейший вари-
58 Borland C++ Builder 6. Разработка приложений баз данных

ант такого условия имеет вид:


ИмяПоля=значение.
Например, условие Category=3, заданное в свойстве Filter, приведет к тому, что
в итоговую выборку попадут только записи, соответствующие категории 3. Значение мо-
жет быть или константой, или переменной, в частности именем поля, или допустимым
выражением. Константа должна указываться в соответствии с принятыми правилами, на-
пример, строку нужно заключать в одинарные кавычки. Если имя поля состоит более чем
из одного слова, оно должно заключаться в квадратные скобки: [Имя поля].
Кроме знака равенства можно использовать знак любой операции отношения: неравно
(<>), больше (>), меньше (<), и т.д.
Чтобы указанный фильтр вступил в действие, необходимо для свойства Filtered задать
значение true. Если набор данных открыт, то вы получите отфильтрованную выборку
прямо в режиме конструктора. Чтобы отключить фильтр, задайте для свойства Filtered
значение false.
На окончательный вид отфильтрованного набора данных влияет также свойство
FilterOptions, которое является множеством. Множество FilterOptions может содержать про-
извольный набор из двух элементов типа FilterOption: foCaselnsensitive KfoNoPartialCompa-
ге, или быть пустым. Оба элемента используются при сравнении значений текстовых полей
с указанным образцом. Если свойство FilterOptions содержит элемент foCaselnsensitive, то
при сравнении не будет учитываться регистр символов строки. Присутствие во множестве
FilterOptions элементаfoNoPartialCompare отменяет возможность сравнивать образец с часть-
ю значения поля. Если элемент foNoPartialCompare не задан, можно указывать символ звез-
дочки в качестве замены любого количества символов. В этом случае, например, условие вида
Item- 'S*' приведет к отбору всех записей, значения поля Item которых начинаются с символа
S. В режиме конструктора любую из опций FilterOption можно активизировать, задав для нее
значение true в инспекторе объектов.
Убедиться в простоте и гибкости метода фильтрации с помощью свойств Filter и Filtered мож-
но на примере. Создайте новое приложение, добавьте на пустую форму, как и в предыдущем при-
мере, сетку (TDBGricf), компоненты ТТаЫе и TDataSource, и объедините их в цепочку, подключив
к таблице Items. В верхнюю часть формы добавьте элементы управления, как изображено на рис.
2.11, и подровняйте их соответствующим образом. Для элемента редактирования (компонента
TEdit) задайте имя ECriteria, а для флажков (TCheckBox) задайте имена CBCaselnsensitive и CBNo-
PartialCompare соответственно. Для кнопок (TButton) задайте имена в соответствии с их подпися-
ми: В Apply и BCancel.

^rtteria Stfjpg: • & Сове ыет^е " r NO Р*


йепт'ер** CwcsiRterj

ttemNo |йвт jcafcij.!^ |''s *•]


i 3 Epson EPL-5900L A^ IJJ4,
6 Epson AcuUasef C1000 A4;>."'l
7 EpsonAcuLaserCZDOO •A4.-.',;:;^
3 Epson Stylus ColorCSDSX A4 . ,§
4 Epson Stylus ColorCMQUX A-f ,.|
:

5 Epson Stylus Color C60 A1 v.:^


6 Epson Stylus Color C70 :
A4 "- Й
7 Epson Stylus ColorCfiO A4..;.|
| В Epson Stylus Photo B1Q 'A4 •"'-&•

JLJJ

Рис. 2.11. Результат применения фильтра по полю Item


^
с использованием свойств Filter и Filtered
Глава 2. Использование механизма ВОЕ 59

В обработчики событий формы OnCreate и OnClose введите следующий текст:


void fastcall TForml::FormCreate(TObject *5ender)
{
Tablel->0pen();
}
void fastcall TForml::FormClose(TObject *Sender,
TCloseAction &Action)
{
Tablel->Close();
}
Смысл кода, выполняемого этими методами, предельно прост: при создании формы
набор данных Tablel открывается, а при закрытии — закрывается. Остается ввести код
в обработчики событий OnClick кнопок BApply и BCancei.
void fastcall TForml::BApplyClick(TObject *Sender)
{
Tablel->Filter=ECriteria->Text;
if(CBCaseInsensitive->Checked)
Table1->FiIterOptions foCaselnsensitive;
else Tablel->FilterOptions » foCaselnsensitive;
if(CBNoPartialCompare->Checked)
Table1->FiIterOptions foNoPartialCompare;
else Tablel->FilterOptions » foNoPartialCompare;
Tablel->Filtered=true;
}
void fastcall TForml::BCancelClick(TObject *Sender)
{
Tablel->Filtered=false;
}
При нажатии кнопки BCancei свойству Filtered компонента ТТаЫе задается значе-
ние false', тем самым отключается примененный ранее фильтр. Обработчик события
нажатия кнопки BApply выполняет противоположную задачу — устанавливает фильтр
в соответствии с условием, заданным в элементе редактирования ECriteria, Текст,
введенный в поле ECriteria, присваивается свойству Filter, а затем, в зависимости от
состояния флажков CBCasefnsensitive и CBNoPartialCompare, настраивается свойство
FilterOption. Чтобы непосредственно применить фильтр, свойству Filtered задается
значение true.
Вот и все! Вы получили приложение, в рамках которого можете применить к набору
данных самый изощренный фильтр. Компилируйте приложение и поэкспериментируйте
с ним. На рис. 2.11 изображена таблица Items, отфильтрованная по полю Item. Обратите
внимание на то, Что флажок Case Insensitive установлен, а флажок No Partial Compare
снят. Это позволит не учитывать регистр символов при сравнении с образцом и приме-
нять символ звездочки в качестве замены любого количества символов. С учетом изо-
браженного на рис. 2.11 в финальный набор данных войдут все записи, значения в поле
Item которых начинаются с символов sam или Sam (или других комбинаций этих символов
в разных регистрах) и включают любое количество других символов (конечно, с учетом
длины поля).
60 Borland C++ Builder 6. Разработка приложений баз данных

Еще более изощренный фильтр можно применить при помощи обработчика собьт
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. Разработка приложений баз данных

но использовать макро OPENARRAY или ARRAYOFCONST. При использовании метод


FindKey или FindNearest не нужно предварительно вызывать метод SetKey и задавать д
полей, составляющих индекс, образцы поиска. Вот как, например, можно переписать о
разец кода, приведенный выше:
Tablel->IndexName="ItemNDX";
Tablel->FindKey(OPENARRAY(TVarRec,(ESample->Text)));
Tablel->IndexName="";
Более удобная и универсальная технология поиска в наборе данных связана с прим
нением метода Locate. Он объявлен в классе TBDEDataSet (это непосредственный насле,
ник класса TDataSet) следующим образом:
virtual bool fastcall Locate(const AnsiString KeyFields,
const System::Variant &KeyValues, Db::TLocateOptions Options);
Этот метод — наиболее общий способ поиска информации в наборах данных, явл?
ющихся наследниками класса TBDEDataSet (TTable, TQuery, TStoredProc). Поиск можн
производить по любому полю или набору полей, не обязательно составляющим индекс
Первый аргумент метода Locate — это строка AnsiString, содержащая наименования по
лей для поиска. Поля перечисляются через точку с запятой. Второй аргумент представляв
собой массив значений типа Variant. Этот массив должен содержать список образцов по
иска, соответствующий списку полей в первом аргументе. Для динамического построена
массива типа Variant можно использовать процедуру VarArrayOf. Третий аргумент — мно
жество типа TLocateOptiom. В его состав могут входить элементы loCaselnsensitive (прр
поиске не будет учитываться регистр значений поля) и loPartialKey (разрешается сравне-
ние образца поиска с частью значения поля). При поиске по нетекстовым полям аргумею
Options игнорируется.
Ниже приводится небольшой отрывок программы, иллюстрирующий способ примене-
ния метода Locate.
void fastcall TForml::BLocateClick(TObject *Sender)
{
TLocateOptions SearchOptions;
SearchOptions.ClearQ;
if(CBCaselnsensitive->Checked) SearchOptions loCaselnsensitive;
if(CBPartialKey->Checked) SearchOptions loPartialKey:
Tablel->Locate(EField->Text, ESample->Text, SearchOptions);
}
В этом отрывке кода флажки CBCaselnsensitive и CBPartialKey используются для на-
стройки параметров поиска. Имя поля для поиска задается пользователем в элементе ре-
дактирования EField, а образец поиска — в элементе редактирования ESample.
Lookup — еще один полезный метод, пригодный для поиска нужной информации, но
не связанный с перемещением указателя активной записи. Этот метод объявлен в классе
TBDEDataSet так:
virtual System::Variant fastcall Lookup(const AnsiString KeyFields,
const System::Variant SKeyValues, const AnsiString ResultFields);
Метод Lookup разыскивает запись, удовлетворяющую указанным критериям, и возвр-
ащает значение указанного поля. В качестве критерия поиска используются первые два
Глава 2. Использование механизма ВОЕ 63

аргумента. Их смысл и назначение аналогичны таковым первых двух аргументов метода


Locate (см. предыдущие несколько абзацев). Третий аргумент представляет собой стро-
ку AnsiString. Эта строка содержит наименование поля, значение которого необходимо
вернуть. Например, если нужно отобразить индекс какого-либо товара из таблицы Items,
заданного наименованием, можно использовать следующую конструкцию:
EField->Text=Tablel->Lookup("Item","HP LaserJet 1200","Index");
И в заключение о самой гибкой, удобной и простой технологии поиска — о посл-
едовательном поиске при помощи методов FindFirst, FindPrior, FindNext и FindLast.
Они напоминают методы навигации по набору данных, однако, в отличие от последних,
переход осуществляется к первой, предыдущей, следующей или последней записи, удо-
влетворяющей указанному критерию. Методы навигации в этом смысле являются мето-
дами безусловного перехода, в то время как методы поиска семейства Find... — методами
условного перехода. Методы FindFirst, FindPrior, FindNext и FindLast объявлены в классе
TDataSet следующим образом:
bool fastcall FindFirst(void);
bool fastcall FindPrior(void);
bool fastcall FindNext(void);
bool fastcall FindLast(void);
Каждый из этих методов возвращает значение true в случае успешного поиска
и false в противоположном случае. Критерий поиска задается в обработчике события
OnFilterRecord так же, как и при фильтрации. Однако в отличие от фильтрации свойство
Filtered набора данных должно иметь значение false.
Пользоваться этими методами достаточно просто и удобно. Кроме того, позаботив-
шись об обработчике события OnFilterRecord, можно создать удобную и привычную для
пользователя процедуру поиска нужного значения. В качестве примера использования
методов группы Find... совместно с событием OnFilterRecord, создадим небольшое при-
ложение. Как и в предыдущих примерах, на пустую главную форму приложения поме-
стите компоненты TDBGrid, TTable и TDataSource, оставив для них имена по умолчанию.
Соедините эти компоненты друг с другом и подключите всю эту цепочку к таблице Items
псевдонима MyData. В верхней части формы разместите два элемента редактирования
(имена: EField и ESample), две метки (подписи: Field и Sample) и четыре кнопки (имена:
BFindFirst, BFindPrior, BFindNext, FindLast; подписи, соответственно: Find First, Find
Prior, Find Next, Find Last; значения свойства Tag, соответственно: 1,2,3, 4). Разместите
все элементы управления примерно так, как изображено на рис. 2.12.
Создайте для кнопки BFindFirst обработчик события OnClick и свяжите с этим обра-
ботчиком события OnClick всех кнопок. В тело обработчика события введите следующий
код:
TButton* Temp=dynamic_cast<TButton *>(Sender);
if(Temp)
{
swi tch(Temp->Tag)
{
case 1: Tablel->FindF1rst(); break;
case 2: Tablel->FindPrior(); break;
case 3: Tablel->FindNext(); break;
64 Borland C++ Builder 6. Разработка приложений баз данных

case 4: Tablel->FindLast(); break;


};
DBGridl->SetFocus();
]
Обработчик события имеет стандартный вид обработчиков, ассоциированных с один
ковыми событиями сразу нескольких однородных элементов управления. Вначале арг
мент Sender приводится к типу TButton *, затем, при успешном преобразовании, провер
ется свойство Tag, и в зависимости от его значения выполняются те или иные действ!
(вызывается соответствующий метод последовательного поиска). В заключение фою
ввода переводится на компонент TDBGrid.
Критерий поиска задается в обработчике события OnFilterRecord:
Accept=(DataSet->Fields->
FieldByName(EField->Text)->As5tring.AnsiPos(ESample->Text) 0) ;
В этом обработчике события для каждой записи извлекается значение поля, имя коте
рого задано в поле ввода EField. Это значение при помощи свойства AsString преобразуете
к строковому типу AnsiString. Затем при помощи метода AnsiPos класса AnsiString определи
ется позиция, начиная с которой текст, заданный в поле ввода ESample, входит в полученну!
строку. Если образец входит в эту строку, метод AnsiPos возвращает значение, большее (
Тогда аргумент Accept получает значение true, и методы семейства Find... останавливаютс
на этой записи, также возвращая значение true. Методы FindPrior и FindNext начинают nonet
отталкиваясь от текущей позиции в наборе данных, а не от первой или последней.

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 . :-?-<

«LJ , ... ;i :,.; .,.;;:.;j;,^


Рис. 2.12. Простое приложение, иллюстрирующее последовательный поиск в наборе
данных при помощи методов FindFirst, FindPrior, FindNext и FindLast
Сохраните приложение где-нибудь, например, в каталоге FindFirstDemo, а затем от-
компилируйте его и запустите на выполнение. На рис. 2.12 изображены результаты поиска
по полю Item образца 'FC-PGA'. Нажимая кнопки BFindPrior и BFindNext, размещенные
на форме, вы можете последовательно перемещаться по всем записям набора данных, удо-
влетворяющим критериям поиска. При помощи кнопок BFindFirst и BFindLast вы сможе-
те перейти, соответственно, к первой и последней такой записи. Полный текст профаммы
FindFirstDemo представлен в листингах 2.5 и 2.6.
Замечание
Конечно, описываемая процедура последовательного поиска нуждается
в существенной доработке, если вы хотите использовать ее в реальном приложении.
Глава 2. Использование механизма ВОЕ 65

Например, неплохо было бы осуществлять переход на поле, по которому был задан


поиск, в этом поле выделить подстроку, совпадающую с заданным образцом, и т. д.
Однако, как водится, поупражняться в этом предоставим читателю.

Листинг 2.5. Файл заголовка приложения FindFirstDemo


// ------ ............................... - ............................
#ifndef UnitlH
#defme UnitlH
// ------ ......................... - ..... -------- ............. --------
#include <Classes . hpp>
#include <Controls . hpp>
#include <5tdCtrls.hpp>
#include <Forms.hpp>
#include <DB.hpp>
#include <DBGrids.hpp>
#include <DBTables.hpp>
#include <Grids.hpp>
// ..... - ..... - ............ - ............ ---- ............... ---- ......
class TForml : public TForm
{
_ published: // IDE-managed Components
TDBGrid *DBGridl;
liable "Tablel;
TDataSource *DataSourcel;
TEdit *EField;
TLabel *Labell;
TEdit *ESample;
TLabel *Label2;
TButton *BFindFirst;
TButton *BFindLast;
TButton *BFindPrior;
TButton *BFindNext;
void _ fastcall BFindFi rstClick(TObject *Sender);
void _ fastcall TablelFilterRecord(TDataSet *DataSet,
bool &Accept) ;
void _ fastcall FormCreate(TObject *Sender) ;
void _ fastcall FormClose(TObject *5ender, TCloseAction &Action);
private: // User declarations
public: // User declarations
_fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


/ / ----- .................. -
#endif

Листинг 2.6. Главный модуль приложения FindFirstDemo

#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)

void _ fastcall TForml: :BFindFi rstClick(TObject *Sender)


{
TButton* Temp=dynamic_cast<TButton *>(5ender);
if (Temp)
{
swi tch(Temp->Tag)
{
case 1: Tablel->FindFi rst() ; break;
case 2: Tablel->FindPrior() ; break;
case 3: Tablel->FindNext() ; break;
case 4: Tablel->FindLast() ; break;
};
DBGridl->SetFocus() ;

void _ fastcall TForml : :TablelFilterRecord(TDataSet *DataSet,


bool &Accept)
{
Accept=
(DataSet->Fields->FieldByName(EField->Text)->
AsString.AnsiPos(ESample->Text) > 0);

void _ fastcall TForml: :FormCreate(TObject *Sender)


{
Tablel->0pen() ;

void _fastcall TForml: : FormClose(TObject *Sender, TCloseAction


&Action)
{
Tablel->Close();

Сортировка набора данных


Возможности сортировки набора данных типа ТТаЫе невелики. Такой набор сорти-
руется в соответствии с индексом, активным в данный момент. Активный индекс указы-
вается при помощи свойства IndexName или IndexFieldNames. В зависимости от свойств
Глава 2. Использование механизма BDE 67

индекса, заданных при проектировании, набор данных сортируется по полям, входящим


в состав индекса, по возрастанию или убыванию значений, начиная с первого поля, вхо-
дящего в индекс. Если индекс для набора данных не задан, набор сортируется с использо-
ванием первичного ключа, конечно, если он задан. Если же не задан и он (и это позволяет
используемая СУБД), записи сортируются по мере их ввода в таблицу.
Это все, чего можно добиться от компонента ТТаЫе. Большие возможности сортиров-
ки — у компонента TQuery.

Изменение данных
Еще один очень важный момент, связанный с компонентом ТТаЫе,- изменение данных та-
блицы, к которой этот компонент подключен. В режиме конструктора, щелкнув правой клави-
шей мышки на значке компонента ТТаЫе, вы можете выбрать из контекстного меню пункты,
при помощи которых можно удалить или переименовать связанную таблицу. Тех же резуль-
татов в процессе выполнения программы можно добиться при помощи методов 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

Обработчики вышеупомянутых событий с приставкой Before могут быть использованы


для каких-либо действий, предваряющих событие. Например, можно запросить у пользо-
вателя подтверждение выполняемых действий и затем, в зависимости от его ответа, отме-
нить их или продолжить. Обработчики событий с префиксом After могут использоваться
для каких-либо действий, следующих непосредственно за событием. Ниже приведен об-
работчик события BeforePost. Он будет вызван перед тем, как запись будет сохранена. Вначале
на экран будет выдано диалоговое окно, запрашивающее подтверждение у пользователя. Если
пользователь нажал кнопку ОК, никаких действий не будет и запись будет сохранена. Если
пользователь решил вернуться назад, в режим редактирования записи, не сохраняя ее, он
может нажать Cancel. Тогда вызывается метод Abort, прерывающий выполнение текущей опе-
рации (сохранения записи), и набор данных остается в неизменном состоянии. Если пользова-
тель нажмет кнопку No, перед методом Abort, прерывающим текущую операцию сохранения
записи, вызывается метод Cancel. В результате все изменения, внесенные в текущий набор
данных и еще не сохраненные, будут отменены.
void _ fastcall TForml: :TablelBeforePost(TDataSet *DataSet)
{
int resp;
resp=Application->MessageBox("CoxpaHHTb текущую запись?",
"Сохранение записи",
MB_YESNOCANCEL+MB_ICONQUESTION);
swi tch (resp)
case IDCANCEL:
{ AbortQ; break; }
case IDNO:
{ DataSet->Cancel() ; AbortQ; break; }

Используя методы модификации набора данных, следует иметь в виду один немало-
важный фактор. Набор данных может быть немодифицируемым, т. е. предназначаться
только для чтения. В этом случае применение одного из методов модификации приве-
дет к исключительной ситуации и выдаче сообщения: 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

Чтобы запомнить текущую позицию в наборе данных, следует прибегнуть к методу


GetBookmark. При этом нужно использовать предварительно объявленную переменную
типа TBookmark. Для перехода к закладке применяется метод GotoBookmark. Каждому
вызову метода GetBookmark должен соответствовать вызов метода FreeBookmark, так как
именно этот метод освобождает ресурсы, занятые при вызове метода GetBookmark.
Возвращаясь к примеру использования свойства Bookmark, который был описан чуть
выше, можно на той же форме расположить еще две кнопки — с именами BGetBookmark
и BGotoBookmark. В модуле формы, чуть ниже объявления объекта формы Forml, нужно
разместить следующее объявление:
TBookmark MyTBookmark;
В обработчики событий OnClick кнопок BGetBookmark и BGotoBookmark поместите
следующий код:
void fastcalT TForml::BGetBookmarkClick(TObject *Sender)
{
MyTBookmark=Tablel->GetBookmark();
}
void fastcall TForml: :BGotoBoc»kmarkClick(TObject *Sender)
{
Tablel->GotoBookmark(MyTBookmark);
Tablel->FreeBookmark(MyTBookmark);
MyTBookmark=NULL;
}
В обработчике события нажатия кнопки BGotoBookmark при помощи вызова метода
FreeBookmark освобождаются все ресурсы, выделенные под переменную MyTBookmark
при вызове метода GetBookmark. После этого переменной MyTBookmark присваивается
значение NULL. Это делается для того, чтобы предотвратить возможную утечку ресурсов,
если будет нажата кнопка BGetBookmark и вслед за этим будет закрыта форма. В тело об-
работчика события OnClose формы нужно поместить следующий оператор:
if(MyTBookmark) Tablel->FreeBookmark(MyTBookmark);
Таким образом, при закрытии формы будет проверяться текущее значение переменной
MyTBookmark. Если ее значение не равно NULL, значит, для нее необходимо вызывать
метод FreeBookmark.
Прежде чем использовать закладку в реальном приложении, нужно убедиться в том,
что этой закладке было присвоено допустимое значение. Для этого служит метод
BookmarkValid, объявленный в классе TBDEDataSet следующим образом:
virtual bool fastcall BookmarkValid(void * Bookmark);
Этот виртуальный метод переопределен в наследниках класса TBDEDataSet, в частно-
сти, в компонентах ТТаЫе и TQuery. Если переменная типа TBookmark, передаваемая
методу в качестве аргумента, имеет допустимое значение, то метод BookmarkValid воз-
вращает значение true.
Замечание
Прежде чем использовать закладки, следует убедиться в том, что набор -данных
допускает их применение. Например, однонаправленный (unidirectional) набор данных
72 Borland C++ Builder 6. Разработка приложений баз данных

не позволяет использовать закладки. Для проверки того, является ли набор данных


однонаправленным, можно использовать свойство логического типа IsUniDirectional.

Состояние набора данных и компонент TDataSource


Часто возникают ситуации, когда необходимо точно знать, что происходит в данный
момент с набором данных. Например, если вызвать метод Post в то время, когда набор
данных находится в режиме просмотра, будет возбуждена исключительная ситуация
и выдано сообщение: Dataset not in edit or insert mode («Набор данных не находится
в режиме редактирования или вставки»). Чтобы избежать подобной ошибочной ситуа-
ции, прежде чем вызывать метод Post, необходимо проверить, находится ли набор данных
в состоянии редактирования или вставки записи.
Для проверки состояния набора данных следует воспользоваться его свойством State.
Оно объявлено в классе TDataSet так:
property TDataSetState State = {read=F5tate, nodefault};
Свойство State, что вполне естественно, может использоваться только для чтения. Оно
может принимать одно из значений перечислимого типа TDataSetState:
О dslnactive — или набор данных закрыт, или данные по каким-то причинам недоступ-
ны.
О dsBrowse — режим открытого набора данных по умолчанию. Набор данных в этом
режиме можно только просматривать.
О dsEdit — текущую запись набора данных в этом режиме можно редактировать. Прежде
чем перевести набор данных в такой режим, проверяют, не пуст ли этот набор. Если
нет, то текущая запись копируется в буфер, где ее можно редактировать. После того
как запись отредактирована, следует вызвать метод Post (чтобы сохранить внесенные
в запись изменения) или метод Cancel (для отмены всех изменений). В любом случае
набор данных снова переводится в режим просмотра dsBrowse.
G dslnsert — набор данных переводится в этот режим, если в него добавлена новая за-
пись, например при помощи вызова методов Insert или Append. Для новой записи
отводится место под буфер редактирования, в котором можно задать значения для
входящих в ее состав полей. Как и в случае с состоянием dsEdit, в этом состоянии не-
обходимо вызвать (или явно, или неявно) метод Post или метод Cancel для перевода
набора данных в состояние просмотра dsBrowse.
О dsSetKey — набор данных переведен в состояние задания параметров поиска или зна-
чений пределов для фильтрации. В это состояние набор данных переходит, например,
после вызова методов группы SetRange. В состоянии dsSetKey могут находиться набо-
ры данных типа ТТаЫе и TClientDataSet.
О dsCalcFields — в этом состоянии набор данных находится при обработке события
OnCalcFields (т. е. в процессе обработки (расчета значений) вычисляемых полей на-
бора данных). В этом состоянии нельзя редактировать обычные (невычисляемые) поля
и вставлять новую запись.
Q dsFilter — - свойство State имеет это значение в процессе обработки события
OnFilter-Record. В этом состоянии отображается ограниченый набор записей. Нельзя
редактировать информацию набора данных и вставлять новые записи.
Глава 2. Использование механизма BDE 73

U dsNewValue, dsOldValue и dsCurVahie. Временное состояние набора данных, использу-


емое BDE для своих служебных целей. В одно из этих состояний набор данных пере-
ходит при доступе к свойствам компонента TField NewValue, OldValue или CurValue
соответственно.
О dsBlockRead— в этом состоянии набор данных бывает при переходе к другой запи-
си — до тех пор, пока ее содержимое не будет помещено во внутренний буфер. Пока
набор данных находится в этом состоянии, элементы управления данными не обновля-
ются и не происходят связанные с ними события.
Q dslnternalCalc— это временное состояние набора данных используется BDE для
своих служебных целей при вычислении полей, имеющих тип (свойство FieldKind)
fklnternalCalc. Поля Tvm&fklnternalCalc — это поля вычисляемого типа, однако, в отли-
чие от полей irniufkCalculated, они хранятся в самом наборе данных. Более подробно
о различных типах полей (компонент TField) будет рассказано далее в этой главе.
Q dsOpening. В этом состоянии набор данных находится при его открытии в асинхронно-
м режиме — до тех пор, пока открытие не завершится.
Свойство State есть не только у наборов данных. Компонент TDataSource также имеет
свойство State типа TDataSetState, описывающее состояние набора данных, к которому
подключен данный объект TDataSource. Во многих случаях удобнее пользоваться интер-
фейсом, который предоставляет именно объект TDataSource. Чтобы понять, почему это
так, следует вначале кратко ознакомиться с возможностями компонента TDataSource.
Компонент TDataSource предоставляет интерфейс между набором данных и элеме-
нтами управления, предназначенными для отображения или манипуляции инфор-
мацией из этого набора. В документации, поставляемой с C++ Builder, компонент
TDataSource представлен как канал (conduit) между набором данных и любым эле-
ментом управления данными. Любой элемент управления данными, расположенный
на форме, обязательно должен быть подключен к одному из объектов TDataSource,
иначе он будет оставаться пустым. С другой стороны, любой набор данных должен
быть подключен к одному из объектов TDataSource, если предполагается отображать
его данные, манипулировать его информацией при помощи элементов управления
данными (Data-aware Controls) или как-то еще использовать представляемые им
данные. Кроме основной функции канала передачи информации между набором дан-
ных и элементами управления данными, компонент TDataSource используется также
для организации между наборами данных связи типа один-ко-многим (или главный/
подчиненный (Master/Detail)).
Компонент TDataSource имеет четыре свойства: уже упоминавшееся State, а также
AutoEdit, DataSet и Enabled. Свойство State типа TDataSetState описывалось выше.
Единственное отличие в использовании свойства State компонента TDataSource в том, что
если его свойство Enabled имеет значение false или не присвоено значение для его свой-
ства DataSet, то свойство State будет иметь значение dslnactive независимо от состояния
самого набора данных.
От значения свойства Enabled зависит, будет ли открыт канал данных между набором
данных и подключенными к нему элементами управления. При значении этого свойства
false связь между набором данных и элементами управления разрывается. Свойство
DataSet содержит ссылку на объект типа TDataSet, к которому должен быть подключен
данный объект TDataSource. Таким образом, канал передачи данных будет нормально
работать, если его свойство Enabled имеет значение true, а свойство DataSet содержит
74 Borland C++ Builder 6. Разработка приложений баз данных

ссылку на имеющийся в приложении объект типа TDataSet (таблица, запрос, хранима:


процедура, пользовательский набор данных).
Если вы обратили внимание, при попытке внести изменения в ячейки объект;
TDBGrid набор данных, к которому подключен объект TDBGrid, автоматически пере
ходит в режим редактирования (dsEdit). Если в состав набора свойств сетки (свой
ство-коллекция Options компонента TDBGrid) не входит элемент dgAlwaysShowEditor
то для перехода к редактированию нужно нажать клавишу Enter или F2, иначе сетк;
постоянно будет находиться в режиме редактирования. Автоматический переход на
бора данных в режим редактирования возможен благодаря тому, что свойство AutoEdit
объекта TDataSource по умолчанию имеет значение true. Если задать для AutoEdit зна-
чение false, то для перевода набора данных в режим редактирования нужно будет явно
вызывать его метод Edit. Таким образом можно предотвратить непредумышленное
изменение данных.
К числу методов компонента TDataSource, кроме конструктора и деструктора,
относятся всего два: IsLinkedTo и Edit. Метод Edit переводит связанный с объектом
TDataSource набор данных в режим редактирования, если это возможно. Вначале про-
веряется свойство AutoEdit. Если его значение равно true, проверяется состояние набо-
ра данных. Если набор данных в состоянии просмотра (dsBrowse), вызывается метод
Edit набора данных. Таким образом, метод Edit компонента TDataSource берет на себя
проверку допустимости перевода набора данных в режим редактирования, поэтому
иногда пользоваться им удобнее.
IsLinkedTo — низкоуровневый метод, проверяющий подключение объекта TDataSource
к указанному в качестве аргумента объекту типа TDataSet. Этот метод может заинтересо-
вать разработчиков компонентов.
Наибольший интерес для программистов представляют события компонента
TDataSource. Их всего три: OnDataChange, OnStateChange и OnUpdateData. Обработчики
последних двух событий имеют стандартный тип TNotifyEvent, а тип обработчика собы-
тия OnDataChange объявлен следующим образом:
typedef void fastcall ( closure *TDataChangeEvent)
(TObject* Sender, TField* Field);
Событие OnDataChange происходит при каждом переходе на новую запись в наборе
данных, в том числе при его открытии, а также при модификации значения поля при пере-
ходе к другому полю (например, в сетке). Если запись модифицирована не была, то при
переходе к другой записи аргумент Field будет иметь значение NULL. To же значение ар-
гумент Field будет иметь, если были изменены значения сразу нескольких полей. Однако
это возможно только при модификации программным путем. Использовать это событие
можно, например, для изменения значений элементов управления, не связанных с набо-
ром данных, для обновления других открытых в данный момент окон приложения и для
прочих подобных действий.
Событие OnUpdateData происходит непосредственно перед тем, как данные будут
сохранены в базе данных, до того как произойдет событие BeforePost. Использовать об-
работчик этого события можно для дополнительной обработки данных перед их сохране-
нием или для проверки допустимости внесенных изменений.
И, наконец, событие OnStateChange происходит всякий раз при изменении состояния
набора данных, например, при его переходе из режима просмотра в режим редактирова-
ния или вставки. Это событие пригодится для проверки текущего состояния набора дан-
Глава 2. Использование механизма BDE 75

ных, чтобы произвести соответствующие действия, например, разрешить или запретить


доступ к тем или иным элементам управления формы.
Чтобы проиллюстрировать применение события OnStateChange, можно использовать
приложение ModifyRec, которое мы создали ранее при описании методов модификации
записей набора данных. Откройте это приложение в IDE C++ Builder и добавьте на фор-
му компонент TStatusBar. Он автоматически будет выровнен по нижней части формы
(свойство Align будет иметь значение alBottom). Задайте для свойства SimplePanel объекта
TStatusBar значение true — это позволит пользоваться свойством SimpleText для отобра-
жения текста строки состояния.
В заключение создайте обработчик события OnStateChange и введите в него следую-
щий текст:
void fastcall TForml::DataSourcelStateChange(TObject *Sender)
AnsiString StatusText;
switch(DataSoureel->State)
case dslnactive: { StatusText="Inactive"; break; }
case dsBrowse: { StatusText="Browse"; break; }
case dsEdit: { StatusText="Edit"; break; }
case dslnsert: { StatusText="Insert"; break; }
case dsSetKey: { StatusText="SetKey"; break; }
case dsCalcFields: { StatusText="CalcFields"; break; }
case dsFilter: { StatusText="Filter"; break; }
case dsNewValue: { StatusText="NewValue"; break; }
case dsOldValue: { StatusText="01dValue"; break; }
case dsCurValue: { StatusText="CurValue"; break; }
case dsBlockRead: { StatusText="BlockRead"; break; }
case dsInternalCalc: { StatusText="InternalCalc"; break; }
case dsOpening: { StatusText="Opening"; break; }
default: 5tatusText="Unknown" ;
StatusBarl->SimpleText="DataSet Status: " + StatusText;

Думаю, нет смысла объяснять приведенный выше фрагмент программы.


Откомпилируйте приложение и запустите его на выполнение. В строке состояния теперь
будет отображаться текущий статус набора данных. Попробуйте вставить новую запись
или перейти к редактированию — вы сможете убедиться, что строка состояния правильно
отображает текущее положение вещей.

Объект TField и редактор полей


Класс TField является программной оболочкой поля набора данных (таблицы, запроса
и т.д.). Этот класс — общий предок для специализированных классов полей, представля-
ющих различные типы данных. Объекты класса TField в приложении никогда не исполь-
зуются, вместо этого используются наследники, соответствующие всем типам данных,
применяемым в C++ Builder. Приложение может иметь дело с классами полей одного из
типов, описание которых приведено в таблице 2.1.
Borland C++ Builder 6. Разработка приложений баз данных

Таблица 2.1. Наследники класса TField

Класс Описание
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. Разработка приложений баз данных

TVariantField Непосредственный наследник класса TField. Представляет поля


типа Variant.
TWideStringField Непосредственный наследник класса TStringField. Представляет
поля типа WideString.
TWordField Непосредственный наследник класса TInteger Field, который
унаследован от класса TNumericField. Представляет значения
типа Word.

Класс TField задает общую функциональность всех классов полей. Этот класс вводит
свойства, методы и события, позволяющие редактировать значения полей в наборе дан-
ных, проверять допустимость вводимых в поле данных, преобразовывать значения полей
из одного типа в другой, задавать стиль отображения данных в соответствующих элемен-
тах управления, вычислять значение поля на основании значений других полей в том же
или в другом наборе данных. Большая часть свойств и методов TField переопределяется
в его классах-наследниках ради возможности манипулировать представляемыми этими
классами данными. Кроме того, каждый из классов-наследников определяет свои свойства
и методы. Например, наряду со свойствами и методами, определенными в классе TField,
в его наследнике, классе TStringField, объявлены также свойства FixedChar и Transliterate,
специфичные для значений текстового типа.
Здесь не будут рассматриваться специфические особенности каждого из классов-на-
следников TField: с этим можно разобраться и самостоятельно. Предпочтительней из-
учить свойства, методы и события, составляющие общую функциональность всех классов
полей.

Свойства, методы и события класса TField


Класс TField имеет множество свойств «на все случаи жизни». В этом разделе будет рас-
смотрена часть этих свойств, в основном те, с которыми приходится сталкиваться чаще всего.
С остальными свойствами можно ознакомиться в справочной системе C++ Builder.
Большая часть свойств класса TField связана с преобразованием значений поля в опр-
еделенный тип данных. Наименования этих свойств состоят из приставки As и следу-
ющего за ней наименования типа, в который будет производиться преобразование. Эти
свойства — самые простые, полезные и часто используемые. Ниже приведено несколько
примеров их применения.
Ansi String Price=Tablel->FieldByName("Price")->AsString;
int IntPrice=Tablel->FieldByName("Price")->AsInteger;
Currency CurPrice=Tablel->FieldByName("Price")->AsCurrency;
Еще одна группа свойств связана со значением поля. К ней можно отнести следующие
свойства.
CurValue, OldValue и NewValue (первые два предназначены только для чтения) — эти
свойства содержат, соответственно, текущее значение поля, старое значение и новое. Все
эти свойства используются только в случае, если доступ к данным осуществляется при
помощи компонента TClientDataSet или разрешены кэшированные изменения (cached
updates). Эти свойства применяются, если при внесении изменений в базу данных (при
вызове метода Post) произошла ошибка. Тогда в обработчиках событий OnUpdateError
или OnReconcileError можно воспользоваться свойствами CurValue, OldValue и NewValue,
Глава 2. Использование механизма ВОЕ 79

чтобы устранить источник ошибки.


Value, DisplayText и Text. Свойство Value используется для прямого чтения/записи зна-
чения поля (без преобразования) при выполнении программы. Тип этого свойства всегда
совпадает с типом самого поля. Например, если тип поля — TCurrencyField, то и свойст-
во Value имеет тип Currency. Свойства DisplayText и Text принадлежат к типу AnsiString
и содержат текстовое представление значения поля в том виде, в каком это значение ото-
бражается в элементах управления данными. Свойство Text содержит текстовое представ-
ление значения поля в процессе его редактирования в элементе управления. Свойства Text
и DisplayText используются в обработчике события OnGetText поля, в тело которого они
передаются в качестве аргументов.
Свойства DataType и FieldKind описывают тип данных поля и тип самого поля.
Свойство DataType служит для задания типа данных при создании объекта поля про-
граммным путем. Свойство DataType может принимать одно из значений перечислимого
типа TFieldType. Значения этого типа соответствуют перечисленным в таблице 2.1 клас-
сам-наследникам класса TField. Например, классу TIntegerField соответствует константа
ftlnteger, и т.д. Свойство FieldKind задает тип поля и может принимать одно из значений
перечислимого типа TFieldKind:
• fkData — поле содержит обычные данные, хранящиеся в наборе данных.
• jkCalculated— поле является вычисляемым. Значение поля вычисляется в обрабо-
тчике события OnCalcFields.
• fkLookup — поле является полем подстановки. О вычисляемых полях и полях под-
становки будет рассказано чуть далее.
• JklnternalCalc — вычисляемое поле, значение которого хранится в наборе данных,
как и у обычного поля типа/kData. К этому типу относятся поля, вычисляемые на
стороне серверов баз данных формата SQL, или поля, вычисляемые BDE для ото-
бражения результатов запроса, для которого свойство RequestLive имеет значение
true.
• jkAggregate — поле агрегатного типа.
Еще несколько свойств имеют отношение к типу поля. Это Calculated, Lookup,
LookupCache, LookupDataSet, KeyFields, LookupKeyFields, LookupList и LookupResultField.
Значение true свойства Calculated поля означает, что это поле вычисляемо, т. е. его значе-
ние вычисляется в обработчике события OnCalcFields набора данных. Остальные свой-
ства имеют отношению к полям подстановки. Все они будут рассмотрены дальше.
Особый интерес представляет свойство AutoGenerateValue. Дело в том, что не все
драйверы баз данных могут определить поля, значения для которых должны подставлять-
ся сервером автоматически. К таким относятся поля автоинкрементного типа и те, для
которых задано значение по умолчанию. В таком случае при попытке сохранить модифи-
цированную или новую запись в базе данных (при вызове метода Post) будет возбуждено
исключение. Для того чтобы избежать этого и предназначено свойство AutoGenerateValue.
С его помощью можно сообщить серверу БД, что данное поле или автоинкрементно, или
имеет значение по умолчанию. AutoGenerate Value может принимать одно из значений типа
TAutoRefreshFlag: arNone (сервер не должен подставлять значение в это поле), arAutoInc
(данное поле автоинкрементно), arDefault (поле имеет значение по умолчанию). Несмотря
на то, что не всегда требуется соответствующая установка свойства AutoGenerateValue,
рекомендуется всегда задавать необходимое значение для полей автоинкрементного типа
и полей, имеющих значение по умолчанию.
80 Borland C++ Builder 6. Разработка приложений баз данных

Значение по умолчанию для объекта поля можно задать при помощи свойств
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 б. Разработка приложений баз данных

Третий аргумент обработчика события OnGetText— переменная логического тип


DisplayText. Как сказано в документации, этот аргумент указывает, будет ли строка Text ис
пользоваться только при отображении или при редактировании тоже. Однако я не замети
каких-то различий в поведении поля в зависимости от значения этого аргумента.
Событие OnSetText происходит после того, как свойству Text присваивается какое
либо значение. В тело обработчика этого события также передается аргумент Text, н
с модификатором const, так что изменить строку Text уже нельзя. Однако можно из
менить свойство Text компонента поля. Если вы внесли в это свойство в обработчик^
события OnGetText какие-либо изменения, то в обработчике OnSetText эти изменена
следует отменить.

Постоянные и динамические поля. Редактор полей


Объекты поля могут быть или динамическими (Dynamic fields), или ПОСТОЯННЫМР
(Persistent fields). Динамические объекты полей создаются автоматически при открытии
набора данных, если он не имеет постоянных объектов полей. В этом случае для каждого
поля набора данных создается объект поля. Тип объекта поля совпадает с типом соответ-
ствующего поля набора данных. Динамические поля располагаются в наборе в том же по-
рядке, что и поля таблицы. Таким образом, набор динамических полей позволяет работать
с ними так, как будто это поля таблицы.
Работать с динамическими полями очень просто. Для их создания не нужно ничего
делать — вы открываете набор данных и получаете в свое распоряжение набор объектов,
которые можно использовать для доступа к полям таблицы. Набор динамически создава-
емых полей всегда правильно отражает структуру набора данных. Если в таблицу были
добавлены новые поля, или какие-то поля были из таблицы удалены или переименованы,
это никак не скажется на вашем приложении. Это происходит потому, что при каждом от-
крытии набора данных считывается информация о его структуре и затем эта информация
используется для создания набора полей.
Для хранения определений полей набора данных предназначено свойство FieldDefs
компонента ТТаЫе. Оно имеет тип TFieldDefs и хранит список определений полей набора
данных, к которому подключен объект ТТаЫе. Заполняется свойство FieldDefs при каждом
открытии набора данных, а затем, если нет постоянных полей, на его основе создается
набор динамических полей. Свойство FieldDefs кроме определения всех полей табли-
цы может содержать и определения других полей, например вычисляемых (Calculated
Fields). Оно также может использоваться для программного создания таблицы (наряду
со свойством IndexDefs), для копирования структуры таблицы в другую таблицу и прочих
подобных задач.
Постоянные поля (Persistentfields) создаются при разработке приложения при помощи
специального инструмента, называемого редактором полей (Fields Editor). Создавая на-
бор постоянных полей, вы можете включить в него только те поля набора данных, которые
вам нужны, причем в том порядке, который вам нужен. Создание постоянных полей по-
зволяет быть уверенными в том, что при каждом запуске приложения вы получаете один
и тот же набор полей, упорядоченных именно так, как вы и ожидаете. Более существенное
преимущество постоянных полей в том, что с ними вы получаете возможность настро-
ить объекты полей так, как это не сделаешь при использовании динамических полей.
Некоторая часть важных свойств компонента TField (а также его наследников) доступна
в режиме конструктора только для постоянных полей.
Глава 2. Использование механизма ВОЕ 83
Нет единого рецепта, какой из типов полей (динамические или постоянные) нужно вы-
брать при создании собственного приложения. В большинстве случаев, конечно, удобнее
пользоваться постоянными полями. Один раз создав и настроив набор нужных полей, вы
можете без проблем пользоваться им всегда. Но есть случаи, когда без динамических по-
лей не обойтись. Один из недостатков постоянных полей проявляется при удалении полей
из таблицы, для которой созданы объекты постоянных полей. В этом случае при запуске
приложения возбуждается исключение, сообщающее о том, что соответствующего поля
в таблице нет. Единственный способ добиться, чтобы приложение нормально работало
и дальше,- открыть его в режиме конструктора и удалить объекты постоянных полей, ко-
торые вызывают ошибку.
Есть еще ряд случаев, когда без набора динамических полей не обойтись, в частно-
сти, в приложении, в котором заранее не известна структура таблицы. В качестве примера
можно привести приложение, отображающее содержимое таблицы, имя которой выбира-
ется из списка и структура которой заранее не известна.

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

Рис. 2.13. При помощи редактора полей можно перемещаться по записям


в режиме конструктора
Итак, если вы решили воспользоваться набором постоянных полей, вам следует об-
ратиться к редактору полей. Его можно вызвать, или дважды щелкнув в форме на изо-
бражении компонента-наследника класса TDataSet (например, ТТаЫе или TQuery), или
выбрав из его контекстного меню пункт Fields Editor. В любом случае, на экране будет
отображено соответствующее диалоговое окно (см. рис. 2.13).
Практически вся функциональность редактора полей сосредоточена в его контекстном
меню. Чтобы добавить в список постоянных полей нужные поля из набора данных, следу-
ет выбрать из контекстного меню пункт Add fields или пункт Add all fields. В первом случае
на экране будет отображено диалоговое окно, содержащее список доступных полей набо-
ра данных. Во втором — все поля будут добавлены автоматически.
Еще один пункт контекстного меню — New field — служит для добавления поля
в список постоянных полей. При выборе этого пункта на экране отобразится одно-
именное диалоговое окно. Оно позволяет добавить определение поля одного из сле-
дующих типов: поле данных (Data), вычисляемое (Calculated) и поле подстановки
(Lookup). Если вы используете клиентский набор данных (например, ClientDataSet),
то можно добавить также определения полей агрегатного типа (Aggregate) и вычисля-
емых полей типа IntemalCalc. О создании вычисляемых полей и полей подстановки
будет рассказано далее. Что касается полей данных (Data), то они должны основы-
ваться на существующих в наборе данных полях. Как сказано в документации, поля
84 Borland C++ Builder 6. Разработка приложений баз данных

этого типа могут использоваться для замены полей набора данных, например, для и
менения типа данных поля.
Основную часть рабочей области окна редактора полей занимает список постоянш
полей. При выборе поля в этом списке инспектор объектов будет отображать все его сво
ства. Вы можете изменить значения всех свойств поля, включая свойства 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), в качестве подписи содержащая имя поля. Можно пометить группу
полей, которые предполагается разместить на форме, и перетащить их все на форму за
один раз. Такой нехитрый прием позволяет моментально воспроизвести необходимую
структуру формы.

Вычисляемые поля (Calculated Fields)


-
При помощи редактора полей можно также создавать и вычисляемые поля (Calculated
Fields), т.е. поля, получающие значение в результате операций, которые могут произво-
диться с участием значений других полей текущей записи. Например, при заполнении на-
кладной на отпуск товаров достаточно указать количество товара и его цену, а стоимость,
которая требуется в документе, может быть рассчитана на основании первых двух данных.
Вследствие этого в таблице нет смысла хранить сведения о стоимости товара, достаточно
данных о количестве и цене, а стоимость будет представлена вычисляемым полем.
Создание вычисляемого поля— двухшаговый процесс. На первом шаге нужно при
помощи редактора полей определить вычисляемое поле. На втором шаге в обработчике
события OnCalcFields набора данных нужно задать способ, которым поле будет получать
значение. Следует иметь в виду, что помимо определения вычисляемого поля нужно также
определить и все поля данных, значения которых должны будут отображаться в элементах
управления, а также поля набора данных, информация из которых будет использоваться
при определении значений вычисляемых полей. Для полей, значения которых будут ис-
пользоваться при расчетах, но не должны отображаться в форме, нужно для свойства
Visible задать значение false.
В обработчике события OnCalcFields для каждого вычисляемого поля должна при-
сутствовать конструкция внца.ИмяПоля->Уа1ие-значение. Кроме конструкций этого вида
в обработчике события OnCalcFields могут присутствовать также и другие достаточно
сложные вычисления. Однако следует помнить, что это событие может происходить ча-
сто, поэтому незачем чрезмерно раздувать его обработчик. Событие OnCalcFields проис-
ходит в таких случаях:
Q когда открывается набор данных, в котором определены вычисляемые поля;
Q когда набор данных переходит в состояние редактирования (состояние dsEdif);
G когда запись возвращается из базы данных (например, при обновлении набора данных).
Кроме того, если свойство AutoCalcFields имеет значение true, событие OnCalcFields
будет происходить также и при переходе фокуса с одного элемента управления на другой
в пределах одной записи или при переходе с одной колонки сетки (TDBGrid) на другую.
Если набор данных содержит много вычисляемых полей, и пользователю разрешено
редактировать записи таблицы, то свойству AutoCalcFields нужно задать значение false,
иначе приложение будет сильно «притормаживать».
Прежде чем привести пример использования вычисляемых полей, добавим в нашу ба-
86 Borland C++ Builder б. Разработка приложений баз данных

зу данных с псевдонимом 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

В итоговой выборке должны присутствовать (отображаться) только три поля, причем


все три — вычисляемые: Name (фамилия и инициалы), Age (возраст), Standing (стаж).
Откройте редактор полей (например, дважды щелкнув мышкой на значке объекта ТТаЫе).
Далее, выбрав пункт New field контекстного меню, в одноименном диалоговом окне вве-
дите определение всех трех полей. Имя каждого из них необходимо ввести в поле ввода
Name, имя компонента поля можно не менять (имя компонента по умолчанию имеет вид
ТаЫеШмя_поля). Из комбинированного списка Туре нужно выбрать тип String для поля
Name и тип Smalllnt для полей Age и Standing. В поле ввода Size укажите размер 50 для
поля Name, для остальных полей размер подставляется автоматически и изменить его
нельзя. В заключение следует для каждого из полей в области Field type выбрать значение
переключателя Calculated. На рис. 2.14 изображен момент определения поля Standing.

Рис. 2.14. Определение вычисляемого поля Standing


Для вычисления значений полей Name, Age и Standing нам понадобятся также поля
LName, FName, MName, HireDate и Birthday. Кроме того, нужно добавить поле FireDate,
так как оно используется в выражении для фильтра (в противном случае при открытии на-
бора данных вы получите сообщение об ошибке). Откройте редактор полей и выберите из
контекстного меню пункт Add fie Ids. В списке доступных полей выберите все вышеназван-
ные и нажмите кнопку ОК. Теперь нужно задать для свойства Visible всех добавленных из
набора данных полей значение false, так как эти поля не нужны нам в итоговой выборке.
Сделать это можно, выделив все нужные поля в окне редактора полей и задав значение
false свойству Visible для всей группы целиком.
Далее, создайте обработчик события OnCalcFields и введите в него следующий код:
void _fastcall TForml: :TablelCalcFields(TDataSet *DataSet)

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. Разработка приложений баз данных

Первые два оператора обработчика предназначены для формирования инициалов. О


формируются в переменных FI и SI типа AnsiString. Первый инициал создается, если i
ле FName не пусто. Чтобы проверить это, используется метод IsNull. Если поле FNar
не пусто, переменной FI присваивается строка, содержащая пробел, первую букву име
и символ точки. Для отделения первой буквы строки используется метод Substring клас
AnsiString. Этот метод возвращает подстроку длиной, указываемой вторым аргументе
начиная с символа, номер которого указан первым аргументом. Манипуляции, продел
ваемые с переменной SI, аналогичны, за исключением того, что второй инициал форми]:
ется только в том случае, когда есть первый. Чтобы проверить это, используется еще од
полезный метод класса AnsiString — метод IsEmpty.

Бобров С Н
ЦрейбвнФ.А

Рис. 2.15. Набор данных, который содержит три вычисляемых поля


В заключение этого участка кода свойству Value вычисляемого поля Name присваив
ется строка, содержащая фамилию и инициалы сотрудника. Это ключевой оператор: i
будь его, вычисляемое поле Name осталось бы пустым.
В оставшейся части обработчика события OnCalcFields вычисляются значения для пол(
Age и Standing. Возраст сотрудника (поле Age) определяется как разность между текущн
годом и годом, соответствующим дате рождения. Стаж (поле Standing) равен разности межд
текущим годом и годом-приема на работу. Для получения текущей даты используется мете
Date, возвращающий значение типа TDateTime. Класс TDateTime имеет метод DecodeDat
с помощью которого можно извлечь из даты значения года, месяца и дня.
В заключение нужно создать обработчик события OnCreate формы и ввести в нег
следующий код:
void fastcall TForml::FormCreate(TObject *Sender)
{
Tablel->0pen();
Tablel->Filtered=true;
}
В этом обработчике открывается набор данных и включается фильтр. Откомпилируйт
приложение и запустите его на выполнение. На рис. 2.15 изображен примерный вид по
лучившегося приложения.
Замечание
Решать задачи наподобие описанной в этом пункте гораздо удобнее с помощьк
другого наследника класса TDataSet — компонента TQuery. Более подробно о компонент!
TQuery будет сказано далее-.
Глава 2. Использование механизма BDE

Поля подстановки (Lookup Fields)


Поля подстановки (Lookup Fields) предназначены для отображения в наборе данных
значений полей из другого набора данных. Подстановка информации из другого набора
данных производится на основании связи между ключевыми полями обоих наборов. Чаще
всего поля подстановки используются для замены внешнего ключа таблицы, содержащего
числовое значение, более описательным значением, соответствующим первичному ключу
другой таблицы. Например, в таблице Items есть поле Category, являющееся внешним
ключом по отношению к таблице Category. Это поле содержит числовые значения, ко-
торые не очень-то информативны. Если категорий в таблице более сотни, то запомнить
номер каждой категории не в состоянии даже очень опытный оператор. Используя поле
подстановки, можно вместо числовых значений категорий получить в наборе данных их
текстовые описания.
В качестве примера создадим поле подстановки для поля Category таблицы Items. Для
начала нужно создать новое приложение и, как обычно, разместить на его главной форме
компоненты TDBGrid, TTable и TDataSource, оставив их имена по умолчанию. Далее свя-
жите эти компоненты между собой обычным образом, подключив объект Tablel к таблице
Items базы данных MyData. Для свойства Active объекта Tablel задайте значение true,
открыв тем самым набор данных. Нам понадобится еще один объект типа ТТаЫе — раз-
местите его на форме и подключите к таблице Category. Имя объекта по умолчанию
(ТаЫе2) оставьте без изменения.
Теперь нужно воспользоваться редактором полей. Отобразите его на экране, дважды
щелкнув на объекте Tablel. Выберите из контекстного меню пункт Add all fields, чтобы
добавить в список постоянных полей все поля из набора данных. Далее, нам нужно до-
бавить новое поле — поле подстановки. Выберите из контекстного меню редактора полей
пункт New field. В окне определения нового поля, в области Field properties, укажите имя
поля (Name) (поле Category в списке уже есть, поэтому в качестве имени можно выбрать
CategoryName), его тип (Туре) и размер (Size) (мы предполагаем использовать наимено-
вание категории из таблицы Category, поэтому нужно выбрать тип String размером в 25
символов).
Затем в области Field type выберите значение переключателя Lookup, чтобы указать,
что создаваемое поле — это поле подстановки. В результате в области Lookup definition
станут доступны два комбинированных списка: Key Fields и Dataset. Элемент управления
Key Fields служит для того, чтобы задать одно или несколько полей, на основании которых
будет осуществляться подстановка. Чаще всего здесь указывается внешний ключ текуще-
го набора данных. Одно поле можно выбрать из списка элемента Key Fields, а несколько
полей можно указать самим, разделяя их наименования точкой с запятой. В нашем при-
мере нужно выбрать поле Category.
Список элемента Dataset содержит перечень всех доступных наследников класса
TDataSet (в нашем случае их два: Tablel и Table!). Нужно выбрать тот набор данных,
значения из которого будут использоваться для подстановки. Несмотря на присутствие
в списке объекта Tablel, задавать его в качестве подстановочного набора данных не сле-
дует, иначе при выполнении приложения будет возбуждена исключительная ситуация (ци-
клическая ссылка). В нашем примере следует выбрать объект ТаЫе2. После этого станут
доступны еще два комбинированных списка: Lookup Keys и Result Field.
90 Borland C++ Builder 6. Разработка приложений баз данных

: Field ptoperties
: Цата: |CrtegoiyJome Component |ToblelC3tegoryName2 ;.,;

Type:.. '.'Sting ^J Site: |re

; Field уре- '

''. gey field*: ' JGategoi

Lookup Keys: jCainqoiyNo _Tj Besuft Field; fCotegoi

Рис. 2.16. Определение поля подстановки CategoryName


Элемент управления Lookup Keys должен содержать одно или несколько полей по;
становочного набора данных (Lookup DataSei). Список полей, содержащийся в элемет
управления Lookup Keys, должен точно соответствовать списку полей, указанных в элем<
нте управления Key Fields. По совпадению значений полей из этих списков и будет ос>
1
ществляться подстановка. В простейшем случае здесь нужно указать первичный клю
соответствующий внешнему ключу текущего набора данных, указанному в элементе Kt
Fields. В нашем примере следует выбрать поле CategoryNo.
И последний элемент управления из группы Lookup definition— Result Field. И
списка полей подстановочного набора данных, прикрепленного к элементу Result Fieh
нужно выбрать поле, значения из которого и будут представлены в текущем набор
данных в качестве подстановки. Выберите из этого списка поле Category. На рис. 2.1
представлено диалоговое окно New Field, содержащее определение поля подстановк
CategoryName.
Теперь набор данных Tablel содержит два поля, описывающих категорию. Пол
Category, содержащее числовые значения внешнего ключа, в итоговой выборке нам н
нужно, поэтому необходимо задать для свойства Visible этого поля значение/я/уе. Пол
CategoryName будет смотреться лучше, если оно будет следовать непосредственно з
полем Item. Чтобы добиться этого, достаточно перетащить его при помощи мышк:
вверх по списку в редакторе полей. И последний штрих: задайте свойству DisplayLabt
поля CategoryName значение Category. Это значение и будет отображаться в качеств
заголовка столбца категорий в приложении. Откомпилируйте приложение и запустит
его на выполнение. Примерный вид приложения в режиме выполнения представлю
на рис. 2.17.

» 1 Samsung ML-4500 Принтеры А< GDI, ОЗУ 2 Мб, 600 dpi. .—


J

2 SamsungML-1210 Принтеры А4. GDI, ОЗУ В Мб. 600 dpi, . :


3 Epson EPL-59QQL Принтеры А4. GDI, ОЗУ 2(1 Э) МБ. 600 с ^ J
A Canon LBP-81Q Принтеры А4. GDI, 600 dpi. аи 8 pprn, i *j
5 Brother HL-1Z40 Mono Laser Принтеры A4. ОЗУ 2М6. 600x600 dpi, a \
e.EpsonAcuLossfCIOOO Принтеры A4, цвет. ОЗУ 1 6(256) Мб. Е ,;i.
7 EpsonAcuLaserCZQOO Принтеры A4, цвет, ОЗУ 64(51 2) Мб, Е ":|
8 HPLeserJetlCQQw Принтеры :А< ОЗУ! Мб, GDI, 600 dpi, .-*'v:|
• 9 HPLeserJel1200 Принтеры М ОЗУ 8(72) Мб. 1200 dpi, T" 'J
10 HP LaserJet 1220 Принтеры А4, сканер 600 dpi • 24 бит : ! i
11 HP LaserJet 3200 Принтеры А4. сканер 600 dpi- 24 бит ,J. '.
«1J JLJ

Рис. 2.17. Набор данных, содержащий поле подстановки Category


Глава 2. Использование механизма ВОЕ 91

Выделите в редакторе полей поле подстановки CategoryName и обратите внимание на


свойства, отображенные в инспекторе объектов. Можно заметить, что указанные в диало-
говом окне New field значения отобразились на соответствующие свойства поля. Свойству
FieldKind присвоено значение JkLookup, в полном соответствии с выбранным в диало-
говом окне значением переключателя Field type. Значения четырех комбинированных
списков из области Lookup definition отображены, соответственно, на свойства KeyFields,
LookupDataSet, LookupKeyFields и LookupResultField. В принципе, все эти свойства можно
настроить и без участия редактора полей, например, если вам нужно их отредактировать
после того, как поле подстановки уже было создано.
В заключение следует отметить еще одно свойство полей подстановки, которое может
значительно повлиять на производительность приложения, особенно при удаленном под-
ключении к источнику данных. Это свойство логического типа LookupCache. Если задать
для него значение true, то необходимые значения из подстановочного набора данных
при открытии текущего набора данных будут кэшироваться. В этом случае при переходе
к другой записи значение для поля подстановки будет выбираться из кэша, а не из табли-
цы. Если таблица расположена на удаленном сервере и качество связи оставляет желать
лучшего, то кэширование полей подстановки позволяет значительно увеличить произво-
дительность приложения.
С другой стороны, если значения поля подстановки для каждой записи отличны друг
от друга, значительные расходы на поиски необходимого значения в кэше могут вызвать
обратный эффект, снижая производительность приложения.

Организация связи между таблицами


Одна из задач, очень часто встречающихся при проектировании приложений управ-
ления базами данных,- организация между таблицами связи типа главная/подчиненная
(Master/Detail). На практике эта связь обычно выглядит так: выбор записи или отдельного
значения в одном из элементов управления, подключенных к одному набору данных, при-
водит к тому, что связанный набор данных будет содержать только те записи, которые со-
ответствуют сделанному выбору. Как и следует из названия, в такой связи участвуют две
таблицы — главная (Master) и подчиненная, или детальная (Detail). С точки зрения орга-
низации базы данных главная таблица — это таблица-справочник, содержащая описание
одной из главных сущностей, используемых в других, детальных таблицах. Эти понятия
относительны, как и все в этом мире. Таблица, детальная по отношению к одной таблице,
может быть главной по отношению к другой.
Создание связи главная/подчиненная между двумя таблицами легче всего объяснить
на примере. Именно так мы и поступим. Предположим, нам нужно создать приложение,
отражающее информацию о товарах, принадлежащих конкретной категории. Категорию
можно выбирать при выполнении приложения. В этом случае информацию о товарах
удобно отображать в элементе управления TDBGrid, а категорию — в элементе управле-
ния TDBLookupComboBox.
Создайте новое приложение и разместите в верхней части пустой формы компонент
TDBLookupComboBox, а под ним — компонент TDBGrid. Далее, разместите на форме по паре
объектов TTable (Tablel и ТаЫе2) и TDataSource (DataSourcel и DataSource2). Присоедините
объекты DataSourcel и DataSource2 к объектам Tablel и ТаЫе2 соответственно. Затем Tablel
подключите к таблице Category базы данных MyData, а ТаЫе2 — к таблице Items той же ба-
зы. Таким образом, объект Tablel будет представлять главную таблицу в формируемой связи
(Category), а ТаЫе2 — подчиненную, или детальную (Items).
92 Borland C++ Builder 6. Разработка приложений баз данных

Объект DBGridlсвяжите с DataSource2. Объект DBLookupComboBoxl настрою


следующим образом. Для свойства ListSource в качестве значения укажите набо
данных DataSourcel, для свойства ListField укажите поле Category, а для свойств
KeyField— поле CategoryNo. Таким образом вы устанавливаете, что источнике:
строк для списка элемента управления DBLookupComboBoxl будет DataSource
1
(таблица Category), отображаться в нем будут значения из поля Category исто
ника строк (текстовое представление категории), а значением элемента управлени
DBLookupComboBoxl будет значение из поля CategoryNo (числовое представление
Именно поле CategoryNo, указанное в качестве ключевого (свойство KeyField), и буде
т использоваться далее для формирования связи.
Итак, мы получили два независимых потока данных: один от таблицы Categor
посредством объектов Table] и DataSourcel к элементу управления данным:
DBLookupComboBoxl, а второй — от таблицы Items посредством объектов ТаЫе2 и Data
Source! к элементу управления DBGridl. Чтобы эти потоки данных работали во взаимо
действии, необходимо задать связь между Tablel и ТаЫе2.
Выделите на форме объект ТаЫе2, который будет играть роль подчиненного набор;
данных, и задайте в качестве значения свойства MasterSource объект DataSourcel, подклю
ченный к главному источнику данных. Затем щелкните мышкой в инспекторе объектов н<
кнопке построителя, расположенной в правой части поля ввода для свойства MasterField
На экране будет отображено диалоговое окно Field Link Designer (конструктор связей)
В верхней части окна расположен комбинированный список Available Indexes (доступные
индексы). В нем перечислены все индексы, определенные для подчиненной таблицы. Дл?
таблицы Items это Primary (первичный ключ), Category (внешний ключ) и ItemNDX (вто-
ричный индекс). Выберите в этом списке индекс Category.

Моду пи памяти __»]

Swat (tern ; (Category JDescnpliorv . *|


Descripsc * 1
DfMM 64 Mb PC1 33. Infineon 5 1 69 pin. 54 bit
DIMM 64 Mb РСШ NCP 5 68 pin. 64 bit
DIMM 1 28 Mb PC! 33, POI original 5 60pm, 64 bit В chip— '•-
DIMM 1 28 Mb PC1 33, NCR 5 68 pin. 64 bit
DIMM 1 26 Mb PCI 33. Samsung original 5 : 63 pin. 5-1 bit 8 chip ..,;.
DIMM 1 28 Mb PCI DO. Transcend 5 rS16MLE?2V6XN.w |
DIMM ?SG Mb PCI 33. Spectek 5 6 chips
DIMM 256 Mb PCI 33. M tec 5 68pm. 64 bit
.QIMM256MbPC133.PQI 5 68 pin. 54 bit
J DIMM 256 Mb PCI 33. Samsung original 5 1 68 pin. 64 bit
1
LLJ jr
Рис. 2.18. Окно редактора связей Рис. 2.19. Приложение, демонстрирующее
между таблицами (Field Link Editor) связь между таблицами. При выборе
категории в комбинированном списке
элемент TDBGrid будет отображать список
товаров, ей соответствующих

Чуть ниже комбинированного списка 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 будет автоматически обновляться, отображая список товаров вы-
бранной категории.

Режим кэширования изменений


Механизм BDE, как, впрочем, и другие механизмы доступа к данным, поддерживает
режим кэширования изменений. В режиме кэширования изменений (Cached Updates Mode)
все изменения, вносимые в набор данных, не записываются в базу данных сразу, а хранятся
в локальной памяти того компьютера, на котором все эти изменения и были сделаны. Все
операции по модификации содержимого полей записей набора данных, а также по удалению
и вставке записей, хранятся в текущем каталоге в виде файлов с расширением MB, например,
Dell.MB или Del2.MB. Иногда эти файлы автоматически не удаляются, засоряя диск ненужной
информацией. В этом случае их придется удалять самому.
Режим кэширования изменений поддерживается в BDE на нескольких уровнях: компо-
нента базы данных (TDatabase), отдельного набора данных (ТТаЫе или TQuery), а также
клиентского набора данных (компонент TBDEClientDataSef). Использование клиентского
набора данных — рекомендованный подход к работе в режиме кэширования изменений.
Если вы хотите получить все преимущества этого режима, то должны воспользоваться
услугами компонента TBDEClientDataSet. В простейших случаях ограничиваются воз-
можностями компонентов TDatabase и ТТаЫе (TQuery). Компоненты TDatabase и TBDE-
ClientDataSet будут рассматриваться в следующих разделах, а в этом остановимся на воз-
можностях компонента ТТаЫе.
Чтобы перевести набор данных в режим кэширования изменений, нужно присвоить
свойству CachedUpdates компонента ТТаЫе значение true. Начиная с этого момента все
сохраненные (при помощи метода Post) изменения в наборе данных, включая вставку или
удаление записей, а также редактирование информации, записываются в специальную
область локальной памяти (кэш, или cache). Информация обо всех изменениях набора
данных накапливается в локальном кэше до тех пор, пока не будет явно вызван или метод
ApplyUpdates, или метод CancelUpdates.
94 Borland C++ Builder 6. Разработка приложений баз данных

Метод ApplyUpdates переписывает все несохраненные изменения из кэша в базу да]


ных, однако, в отличие от одноименного метода компонента TDatabase, не очищает кэ!
Чтобы очистить кэш, нужно вслед за вызовом ApplyUpdates вызвать метод CommitUpdate
Метод CancelUpdates просто очищает кэш, тем самым отменяя все изменения, внесеннь
в набор данных со времени последнего вызова метода ApplyUpdates или CancelUpdati
(или с момента включения режима кэширования изменений).
Такой механизм взаимодействия приложения с набором данных (таблицей в 6asi
е данных) очень выгоден в распределенных приложениях клиент-серверного TI
па. В этом случае набор данных или его часть копируется на клиентскую машин
и дальнейшая работа с ним происходит без непосредственного взаимодействия с базо
данных. Когда необходимые изменения внесены, вызов метода ApplyUpdates отсылае
всю необходимую информацию о сделанных изменениях на сервер БД. Это позволяе
снизить нагрузку на сеть и сервер, а также не мешать другим клиентам, которые тож
работают с этой базой данных.
После вызова одного из методов — ApplyUpdates или CancelUpdates — набор данны
все еще будет находиться в режиме кэширования изменений. Чтобы его отключить, нужн
задать для свойства CachedUpdates компонента ТТаЫе значение true. Если перед выходи
из режима кэширования в кэше была информация о несохраненных изменениях, то поел
задания для свойства CachedUpdates значения false вся она будет безвозвратно утерян
(без предупреждения). Чтобы избежать этой неприятности, можно применить очен
полезное свойство UpdatesPending. Это свойство логического типа содержит значение
true, если в кэше набора данных есть несохраненные изменения. Таким образом, пере,
выходом из режима кэширования изменений (или перед выходом из приложения) можн<
проверить значение свойства UpdatesPending. Конечно, свойство CachedUpdates должж
при этом иметь значение true. Если свойство UpdatesPending имеет значение false, можн<
вызвать или метод ApplyUpdates, чобы сохранить накопившиеся в кэше изменения, шп
метод CancelUpdates — чтобы отменить их. Вполне естественно, вначале необходимо за
просить мнение оператора, работающего с приложением.
В классе TBDEDataSet объявлены два события, непосредственно связанные с механи-
змом кэширования изменений:
typedef void fastcall ( closure *TUpdateRecordEvent)
(Db::TDataSet* DataSet, Db::TUpdateKind UpdateKind,
TUpdateAction &UpdateAction);
typedef void fastcall ( closure *TUpdateErrorEvent)
(Db::TDataSet* DataSet, b::EDatabaseError * E,
Db::TUpdateKind UpdateKind, TUpdateAction &Upd'ateAction) ;
Каждое из этих событий может происходить при пересылке несохраненных изменений
в базу данных, т.е. в промежутке времени между вызовом метода ApplyUpdates и заве-
ршением сохранения кэшированных изменений. Обработчик события OnUpdateRecord
типа TUpdateRecordEvent вызывается для каждой записи — вставленной, удаленной или
модифицированной, — информация о которой хранится в кэше. Этим событием можно
воспользоваться для дополнительной обработки, включающей обновление связанных за-
писей при каскадном обновлении или удалении, а также для дополнительного контроля
над параметрами подстановки и в других подобных случаях.
Кроме традиционного для такого рода события указателя на объект набора данных
DataSet, обработчик события OnUpdateRecord принимает еще два важных аргумента.
Аргумент UpdateKind типа TUpdateKind, как и следует из его названия, указывает на тип
Глава 2. Использование механизма ВОЕ 95

обновления, который применяется для данной записи. Аргументы этого типа могут при-
нимать одно из следующих значений: 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. Разработка приложений баз данных

Упорядочьте все элементы управления в соответствии с рис. 2.20. Далее необхо


димо создать код, который заставит работать все элементы управления совместно
В первую очередь следует позаботиться об установке правильного значения для флажк;
CBUpdatesPending. Так как все изменения кэшируются после явного или неявного вы
зова метода Post, то для этого нужно воспользоваться событием AfterPost объекта Table!
Создайте обработчик этого события и введите в его тело следующий код:
CBUpdatesPending->Checked=TaЫel->UpdatesPending;
После любого вызова метода Post состояние флажка CBUpdatesPending будет в точно-
сти соответствовать значению свойства UpdatesPending.

Не завершенные

* 1 ; Упаленнычзеписей: В Сшиби»; ni«'! а&нквоении:!}-" Отменить

r.t. . * •о jftem - - - " |C«»gp - ""- - - 1 -_ij


1 Samsung МЫ530 A4. GDI. ОЗУ 2 Мб. 600 dpi, до В ppm, поток 1 50 г-1
2 Samsung ML-1^10 АЧ GDI. ОЗУ 8 Мб. 600 dpi, до П ppm, лоток 1 %'
3 Epson EPL-5900L А4, GDI, ОЗУ 2(13) Мб, 600 dpi, до 12 ppm. лото-: :;:
4 Canon LBP-810 А4. GDL 600 dpi, до 8 ppm, лот ок 1 00 листов. LF :.:.
5 Brother HL-1 240 Mono Lase '-. А4, ОЗУ 2М&, 600x600 dpi, до 1 2 ppm, лоток 250 ;. - '..
\ 6 Epson AcuLaser C1 OOOw
1
А4, цвет, ОЗУ 1 6(256) МБ. 600 dpi/2400 dpi RIT. а \
? EpsonAcul_oserC20GQ А4, цвет, ОЗУ 84(512) Мб, 600 dpi/2400 dpi RIT. «, .Д
8 HP LaserJet 1000 А4. ОЗУ 1 Мб, GDI. 600 dpi до 1 D ppm, лоток 2БО |
_ 9 HP LaserJet ?200 А4. ОЗУ 0(72) Мб. 1?00 dpi, до 1 4 ppm, лоток 250 Щ

iLd ±Г

Рис. 2.20. Приложение — пример использования режима кэширования изменений


Основную функциональную нагрузку в этом приложении, конечно, несут обра-
ботчики событий нажатия кнопок ButtonApply и ButtonCancel. При нажатии первой
из них должен вызываться метод ApplyUpdates и вслед за ним метод CommitUpdates.
Основное действие, которое должно быть произведено при нажатии второй кнопки,
- вызов метода CancelUpdates. В каждом из обработчиков событий нажатия кнопки
нужно позаботиться и о флажке CBUpdatesPending, так как и метод CommitUpdates,
и метод CancelUpdates очищают кэш несохраненных изменений. Таким образом,
в каждый из обработчиков этих событий нужно вставить в точности такой же опера-
тор, как и в обработчике события AfterPost.
После нажатия кнопок ButtonApply к ButtonCancel следует обновить информацию
о количестве сохраненных изменений в зависимости от типа, и о количестве происшед-
ших в процессе сохранения ошибок. Эта информация будет храниться в переменных
целого типа NDel, NIns, NMod и NErr. Чтобы они были доступны везде в модуле Unitl.cpp,
вслед за объявлением в верхней части модуля:
TForml *Forml;
введите следующее объявление:
int NDel, NIns, NMod, NErr;
В конструкторе формы для каждой из этих переменных задайте значение 0 (следует
придерживаться правил явной инициализации). Заботу о правильном значении каждой из
переменных NDel, NIns, NMod и NErr разумно будет возложить на обработчики событий
OnUpdateRecord и OnUpdateError. Создайте обработчики этих событий. Далее, в обра-
ботчике события OnUpdateRecord следует проверить значение аргумента UpdateKind и,
в зависимости от него, увеличить соответствующую переменную на 1. Кроме того, нужно
Глава 2. Использование механизма BDE 97
не забыть задать для аргумента UpdateAction значение uaApplied. В обработчике собы-
тия OnUpdateError нужно увеличить на 1 значение переменной NErr, а для аргумента
UpdateAction задать значение uaAbort.
Функцию обновления содержимого меток bins, LDel, LMod и LErr берет на себя приват-
ный метод UpdateLabel. В нем на основании значений переменных NIns, NDel, NMod и NErr
обновляется свойство Caption каждой из четырех меток. Кроме того, здесь же все соответ-
ствующие переменные обнуляются. Следует позаботиться о том, чтобы метод UpdateLabel
вызывался из обработчиков событий нажатия кнопок ButtonApply и ButtonCancel.
Приложение-пример полностью приведено в листингах 2.7 и 2.8, а его вид в режиме
выполнения показан на рис. 2.20.
Листинг 2.5. Файл заголовка приложения CachedUpdates
11-
#ifndef UnitlH
«define UnitlH
//-.
«include <Classes.hpp>
«include <Controls.hpp>
«i nclude <5tdCtrls.hpp>
«include <Forms .hpp>
«include <DB.hpp>
«include <DBGrids.hpp>
«include <DBTables.hpp>
«include <Grids . hpp>
«i nclude <ExtCtrls.hpp>
«include <DBClient.hpp>
«include <DBLocal .hpp>
«i nclude <DBLocalB.hpp>
«include <Provider .hpp>
//..
class TForml : public TForm
published: // IDE-managed Components
TDBGrid «DBGridl;
TTable *Tablel;
TDataSource *Data5ourcel;
TButton *ButtonUpdate;
TButton *ButtonCancel;
TCheckBox *CBUpdatesPending;
TPanel *Panell;
TPanel *Panel2;
TLabel *LIns;
TLabel *Label2;
TLabel *LDel;
TLabel *LMod;
TLabel *LErr;
void fastcall ButtonUpdateClick(TObject «Sender)
void fastcall ButtonCancelClick(TObject «Sender)
void fastcall TablelAfterPost(TDataSet *DataSet)
void fastcall TablelUpdateRecord(TDataSet «DataSet,
TUpdateKind UpdateKind, TUpdateAction &UpdateAction);
void fastcall TablelUpdateError(TDataSet *DataSet,
EDatabaseError *E, TUpdateKind UpdateKind,
4 Зак. 319
98 Borland C++ Builder 6. Разработка приложений баз данных

TUpdateAction &UpdateAction) ;
void _ fastcall FormCloseQuery(TObject *Sender, bool &CanClose)
private: // User declarations
void _ fastcall UpdateLabel(void) ;
public: // User declarations
_ fastcall TForml (TComponent* Owner);

extern PACKAGE TForml *Forml;


//---
#endif

Листинг 2.6. Главный модуль приложения CachedUpdates

#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;

void _ fastcall TForml: :ButtonUpdateClick(TObject «Sender)


{
Tablel->ApplyUpdates() ;
Tablel->CommitUpdates() ;
CBUpdatesPending->Checked=Tablel->UpdatesPending;
UpdateLabeK) ;

void _ fastcall TForml : :ButtonCancelClick(TObject *5ender)


{
Tablel->CancelUpdates() ;
CBUpdatesPending->Checked=Tablel->UpdatesPending;
UpdateLabeK) ;

void _ fastcall TForml : :TablelAfterPost(TDataSet *DataSet)


{
CBUpda te s Pending->Checked=Tabl el ->Updates Pending;

void _ fastcall TForml : :TablelUpdateRecord(TDataSet *DataSet,


TUpdateKind UpdateKind, TUpdateAction &UpdateAction)
Глава 2. Использование механизма BDE 99

UpdateAction=uaApplied;
switch(UpdateKind)
case ukModify:
{ NMod++; break; }
case uklnsert:
{ NIns++; break;}
case ukDelete:
{ NDel++; break; }

void fastcall TForml::TablelUpdateError(TDataSet *DataSet,


EDatabaseError *E, TUpdateKind UpdateKind,
TUpdateAction &UpdateAction)
С
NErr++;
UpdateAction=uaAbort;

void fastcall TForml::UpdateLabel(void)


1Лп5->Сариоп="Вставленных записей: " + IntToStr(NIns);
NIns=0;
Юе1->Сар11оп="Удаленных записей: " + IntToStr(NDel);
NDel=0;
1Мой->СарНоп="Модифицированных записей: " + IntToStr(NMod);
NMod=0;
LErr->Caption="Oujn6oK при обновлении: " + IntToStr(NErr);
NErr=0;

void fastcall TForml::FormCloseQuery(TObject *Sender, bool &CanClose)


if((Tablel->CachedUpdates) && (Tablel->UpdatesPending))
swi tch(Application->MessageBox(
"Остались несохраненные изменения! Сохранить?",
"Внимание!", MB_YESNOCANCEL + MB_ICONQUESTION))
case IDYES:
{ Tablel->ApplyUpdates();
Tablel->CommitUpdatesO ;
break;}
case IDCANCEL:
{ CanClose=false;
break; }
case IDNO:
{ Tablel->CancelUpdates();
break; }
100 Borland C++ Builder 6. Разработка приложений баз данных

Замечание
В этом разделе рассмотрены не все возможности режима кэширования изменений
присущие компоненту ТТаЫе. Например, при помощи метода RevertRecord можнс
отменить изменение только последней модифицированной записи, при помощ!
свойства UpdateObject - указать объект типа TUpdateSQL, который будет обновляв
соответствующий набор данных, и т. д. Вы можете сами ознакомиться со всем!
возможностями режима кэширования изменений, обратившись к разделу Using the BD
to cache updates справочной системы C++ Builder.

Создание таблиц «на лету»


Новую таблицу можно создать не только в процессе конструирования при помощи одно
го из визуальных инструментов (Database Desktop и тому подобных), но и «на лету», при
бегнув к различным средствам программирования. К этим средствам относятся языки про
граммирования, различного рода языки сценариев, язык запросов SQL. В механизме BDI
возможности по программному созданию новых таблиц собраны в компоненте ТТаЫе.
Начинать программное создание таблицы нужно с создания объекта ТТаЫе и, при необхо
димости, нового псевдонима. Новый псевдоним можно создать при помощи метода AddAlia
компонента TSession. Объект Session типа TSession создавать не нужно, это происходит ав
тематически, если в программе используется механизм BDE. Более подробно о компонент!
TSession будет рассказано далее. Для создания псевдонима базы данных типа STANDARL
(Paradox, dBASE, текстовые таблицы) проще пользоваться методом AddStandardAlias ком
понента TSession, а не методом AddAHas. Например, для создания псевдонима MyDatal базь
данных Paradox можно воспользоваться следующим кодом:
if ( ! S e s s i o n - > I s A l i a s ( " M y D a t a l " ) ) S e s s i o n - > A d d S t a n d a r d A l i a s (
"MyDatal", " D : \ \ M y D a t a l " , "PARADOX");
Первый аргумент метода AddStandardAlias задает имя псевдонима, второй аргумент со
держит путь и имя каталога, в котором будут храниться таблицы базы данных. Третий ар
гумент также имеет тип AnsiString и может содержать одно из трех значений: "PARADOX"
"DBASE", "ASCUDRV" (последнее предназначено для текстовых таблиц). Прежде чел
создать новый псевдоним, нужно проверить, нет ли уже псевдонима с указанным именем
Для этого следует воспользоваться методом IsAlias компонента TSession. В качестве един
ственного аргумента этот метод принимает строку, содержащую имя псевдонима. Есл!
такой псевдоним уже есть, метод возвращает значение true.
Для создания объекта типа ТТаЫе нужно воспользоваться оператором new. Как толью
объект станет не нужен, его следует удалить при помощи оператора delete:
ТТаЫе* Tablel= new T T a b l e ( t h i s ) ;

delete(Tablel);
Как только объект ТТаЫе будет создан, нужно задать параметры подключения и тиг
таблицы:
Tablel->DatabaseName="MyDatal";
Tablel->TableName="Itemsl.db";
Tablel->TableType=ttParadox;
На следующем этапе нужно создать определения полей и индексов. Для этого ис-
Глава 2. Использование механизма ВОЕ 101

пользуются свойства FieldDefs и IndexDefs компонента ТТаЫе. Прежде чем воспользо-


ваться этими свойствами, нужно для каждого из них вызвать метод Clear. Чтобы добавить
определение нового поля в коллекцию FieldDefs, нужно вызвать метод AddFieldDef, воз-
вращающий указатель на новый объект типа TFieldDef. Потом можно воспользоваться
свойствами этого объекта, чтобы задать для него имя, тип значений и другие атрибуты
поля. Ниже приводится определение поля Item:
NewFieldDef->Name="Item";
NewFieldDef->DataType=ftString;
NewFieldDef->Size=50;
NewFieldDef->Requi red=true;
Аналогично создается и определение нового индекса таблицы. Вначале вызывается
MQToaAddlndexDef, возвращающий указатель на новый объект типа TIndexDef. Далее этот
объект используется для задания свойств индекса. Например, следующий код определяет
первичный индекс на основе поля ItemNo:
NewIndexDef->Name="<Primary>";
NewlndexDef->FieIds="ItemNo";
NewIndexDef->Options=TIndexOptions() « ixPrimary « i x U n i q u e
Собственно создание таблицы в соответствии со всеми заданными свойствами объекта
ТТаЫе происходит после вызова его метода CreateTable. В листинге 2.7 приведен код соз-
дания таблицы Item, которая ранее использовалась в качестве примера.
Листинг 2.7. Код создания таблицы Item
Table* Tablel= new TTable(this);
if (!Session->IsAlias("MyDatal"»
Session->AddStandardAlias("MyDatal", "D:\\MyDatal", "PARADOX");
Tablel->DatabaseName="MyDatal";
Tablel->TableName="Itemsl.db";
Tablel->TableType=ttParadox;
Tablel->FieldDefs->Clear();
Tablel->IndexDefs->Clear();
TFieldDef* NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="ItemNo";
NewFieldDef->DataType=ftAutoInc;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Item";
NewFieldDef->DataType=ftStг ing;
NewFieldDef->Size=50;
NewFieldDef->Requi red=true;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Category";
NewFieldDef->DataType=ftlnteger;
NewFieldDef->Requi red=true;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Description";
102 Borland C++ Builder 6. Разработка приложений баз данных

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. Разработка приложений баз данных

на вкладках, связанных с определенным механизмом доступа (BDE, dbExpress, ADC


InterBase). Кроме того, в модулях данных можно размещать и другие невизуальные ком
поненты, не связанные с базами данных, например, TTimer, TOpenDialog, TSaveDialo^
и все остальные компоненты с вкладки Dialogs.
Очевидное преимущество такого подхода не только в том, что из формы удаляютс
загромождающие ее в режиме конструктора невизуальные компоненты, а из модуля фор
мы — десяток-другой связанных с ними методов. Главный смысл использования модуле]
данных заключаются в том, что с их помощью можно разделить логику управления ба
зами данных («бизнес-правила») и пользовательский интерфейс, размещаемый в форме
В простейшем случае это происходит следующим образом. В модуле данных размеща
ются компоненты ТТаЫе, TDataSource и тому подобные, и создаются методы, выполняю
щие различные действия непосредственно с наборами данных: манипуляции с записям!^
фильтрацию, поиск, обслуживание различных режимов работы и тому подобные опера
ции. Все элементы визуализации и управления данными размещаются в форме, подклю
чаясь к компоненту TDataSource, размещенному в модуле данных. Таким образом, модул
формы будет содержать только код, обслуживающий интерфейс пользователя, а при не
обходимости выполнять ориентированные на базу данных задачи, будут вызываться ее
ответствующие методы модуля данных. Такое разделение позволит менять бизнес-логик
приложения, модифицируя только модуль данных и не затрагивая интерфейс.
Для того чтобы создать новый модуль данных, нужно выбрать пункт Data Module ме
ню File \ New. Новый модуль данных в C++ Builder 6 снова выглядит так же, как и в С+
Builder 4 (см. рис. 3.1). В пятой версии модуль данных имел достаточно сложный ви/
включая панель иерархического представления компонентов, вкладку для размещени
объектов, а также вкладку для отображения схемы данных (Diagram). В шестой вереи
C++ Builder вкладка Diagram перекочевала в редактор кода — этой вкладкой теперь снаб
жен каждый модуль проекта, а иерархия объектов доступна через окно Object TreeViev
В связи с этим модуль данных и приобрел свой первоначальный простой вид.

Рис. 3.1. Модуль данных в режиме конструктора

Umtl cpp UniG.cpp j

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 используется для противоположных целей: закрытия наборов данных, от-
ключения от базы данных и т.д.
В дальнейшем практически во всех более-менее сложных примерах приложений будут
использоваться модули данных.
'

Управление подключением к базе данных


при помощи объекта TDatabase
При использовании механизма BDE в информационных приложениях все свойства
подключения к конкретной базе данных и возможности манипулирования подключением
инкапсулированы в компоненте TDatabase. Объект типа TDatabase подключается к базе
данных и контролирует работу всех наборов данных, у которых в качестве свойства
DatabaseName указано его имя.
Чтобы воспользоваться интерфейсом, предоставляемым компонентом TDatabase,
необязательно явно создавать в приложении объект этого типа. Объект типа TDatabase
создается неявно при первом подключении какого-либо набора данных к базе данных.
Например, при подключении двух объектов типа ТТаЫе к базе данных MyData будет соз-
дан только один объект типа TDatabase, а при подключении в том же приложении тре-
тьего объекта типа ТТаЫе к другой базе данных, например BCDEMOS, будет создан еще
один объект типа TDatabase. Получить доступ к неявно созданному объекту TDatabase
можно при помощи свойства Database, которое объявлено в классе TDBDataSet.
Например, в результате выполнения следующей строки кода в элементе редактирования
Edit] будет содержаться количество подключенных к неявно созданному объекту типа
TDatabase наборов данных:
Editl->Text=Tablel->Database->DataSetCount;
В частности, если к базе данных MyData кроме объекта Tablel подключен еще
и объект ТаЫе2, то после выполнения предыдущей строки кода в Editl будет содер-
жаться значение 2.
106 Borland C++ Builder 6. Разработка приложений баз данных

Если вы хотите более полно контролировать свойства подключения к базе данных, BI


должны явно объявить объект типа TDatabase. В этом случае вы сможете управлять спс
собом подключения и сохранения связи с базой данных, задавать различные параметрь
зависящие от формата базы данных, получать информацию о составе базы данных, управ
лять транзакциями и тому подобное.

Основные свойства компонента TDatabase


Имеется несколько возможностей подключения компонента TDatabase к базе данны>
Самый простой вариант — подключение при помощи псевдонима (alias). Если для баз!
данных ранее был создан псевдоним (при помощи SQL Explorer или BDE Administrator), т
достаточно выбрать его имя из списка, прикрепленного к свойству AliasName. После этоп
нужно указать подходящий идентификатор в качестве значения свойства DatabaseName
Именно этот идентификатор будет появляться в списке свойства DatabaseName наборе
данных наряду с именами зарегистрированных псевдонимов.
При подключении к базе данных можно обойтись и без псевдонима. В этом случа^
нужно выбрать из списка, прикрепленного к свойству DriverName, наименование нужно
го драйвера. Например, STANDARD (для баз данных Paradox или dBASE), INTRBASE (дл:
баз данных InterBase) и т. д. После этого, как и в первом случае, нужно указать в свойств!
DatabaseName подходящее имя, при помощи которого можно будет подключать наборь
данных (ТТаЫе или TQuery) к нужной базе данных. Если свойство AliasName имело дс
этого какое-либо значение, то оно будет очищено, так как свойства AliasName и Driver
Name — взаимоисключающие. Это следует иметь в виду при программном измененш
значений этих свойств.
Если для задания подключения было использовано свойство DriverName, то нужно за
дать еще и базу данных, с которой должен быть связан объект типа TDatabase. Для этогс
служит свойство Params, представляющее собой список строк (тип TStrings). Свойстве
Params компонента базы данных может содержать список параметров и их значений
в виде ИмяПараметра=Значение. Состав параметров, а также набор допустимых для ни>
значений зависят от формата используемой базы данных (т. е. от указанного драйвера)
В любом случае, свойство Params может содержать те же самые параметры, что и псевдо-
ним соответствующего типа.
Способ задания базы данных при помощи параметров также зависит от указанно-
го драйвера. Ниже приведены примеры для форматов STANDARD (Paradox и dBASE)
INTRBASE и MSACCESS соответственно:
PATH=d:\MyData //STANDARD
SERVER NAME=d:\IBase\MyIBase.gdb //INTRBASE
DATABASE N A M E = d : \ D a t a \ N o r t h w i n d . m d b //MSACCESS
Обратите внимание на то, что слева и справа от знака равенства нет пробелов.
Кроме задания базы данных для подключения, свойство Params часто используется
и для указания других параметров подключения, например, имени пользователя и пароля
для доступа к данным, языкового драйвера и т. д. Особенно важно правильно настроить
специфические параметры подключения в случае использования драйверов ODBC или
SQL Links, являющихся частью BDE. О том, как правильно настроить параметры баз
данных типа SQL, можно узнать, обратившись к соответствующей документации, постав-
ляемой вместе с конкретным сервером базы данных.
Глава 3. Использование механизма ВОЕ (продолжение) 107

Рис. 3.3. Для заполнения свойства Params компонента TDatabase


можно использовать редактор списка значений (Value List editor)
Свойство Params, как и любое другое свойство типа TString, имеет прикрепленный
редактор списка значений (Value List editor). Получить доступ к этому редактору можно
при помощи кнопки построителя свойства Params. Центральную часть редактора зани-
мает стандартный список значений, состоящий из двух столбцов. В столбец Key нужно
вводить наименование параметров, а в столбец Value — соответствующие значения (см.
рис. 3.3). Воспользовавшись кнопкой Code Editor..., которая расположена в левой нижней
части окна редактора списка значений, можно перейти в окно редактора кода, где и ввести
необходимые параметры (в формате ИмяПараметра=Значение, по одному параметру
в строке, без разделителей).
Компонент TDatabase имеет свой собственный редактор, значительно облегчающий
настройку подключения к базе данных. Получить доступ к нему можно, дважды щел-
кнув мышкой на изображении объекта в форме. На рис. 3.4 изображен редактор компо-
нента TDatabase с настройками для подключения к базе данных photodatabase.gdb типа
INTRBASE, расположенной в каталоге Temp диска D:. В верхней части окна редактора
компонента TDatabase — элементы управления Name, Alias Name и Driver Name. Они
соответствуют свойствам компонента TDatabase DatabaseName, AliasName и DriverName.
Вы должны обязательно задать значение для свойства DatabaseName в элементе редакти-
рования Name (это значение будет использоваться объектами наборов данных в качестве
имени псевдонима базы данных). После этого вы должны задать или имя псевдонима
(элемент Alias Name), или наименование драйвера базы данных (элемент Driver Name),
памятуя о том, что эти свойства взаимоисключающие.

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 размещен элемент MEMO, c<


держащий список параметров подключения. Этот список, недвусмысленно называемы
переопределением параметров (Parameter overrides), соответствует свойству Params 061
екта типа TDatabase. Здесь вы можете задать любые параметры подключения (конечн
же, воспринимаемые соответствующим сервером базы данных). Хорошо, если вы у»
давно работаете с конкретным типом баз данных или у вас есть подробная документ:
ция о возможных параметрах подключения. Если это не так, то некоторую информ;
цию вы можете получить при помощи редактора компонента базы данных (компонет
TDatabase). Если вы уже указали драйвер базы данных в элементе Driver Name, то можн
воспользоваться кнопкой Defaults. После нажатия этой кнопки элемент Parameter oven
ides будет отображать список всех параметров подключения к базе данных выбранног
типа, а также принятые для них значения по умолчанию. Часть этих параметров можн
переопределить по своему усмотрению (например, задать имя базы данных и пароль
а остальные можно или оставить неизменными, или удалить их из списка, чтобы не засс
рять элемент лишней информацией.
Окно редактора компонента базы данных содержит еще два элемента. Это флажк
Login prompt и Keep inactive connection. Значения этих флажков отображаются на значе
ния свойств логического типа LoginPrompt и KeepConnection соответственно. Свойств
LoginPrompt указывает, будет ли отображаться диалоговое окно для ввода имени пользе
вателя и пароля непосредственно перед подключением к базе данных. Значение по умол
чанию для этого свойства — true, однако на период разработки и отладки приложени
лучше задать значение false, чтобы избавить себя от лишних хлопот. Если для свойств
LoginPrompt задано значение false, а подключение к базе данных требует обязательно:
авторизации, то вы должны указать имя пользователя и пароль в свойстве Params объект;
TDatabase (иначе вы не сможете установить связь с базой данных).
Выше уже говорилось о том, что в контексте одного объекта типа TDatabase може'
быть открыто несколько наборов данных. Открыть любой набор данных в контексте объ
екта TDatabase можно лишь в том случае, если этот объект связан с базой данных (т. е
открыт сеанс связи с базой данных). За состояние сеанса связи объекта TDatabase с базо!
данных отвечает свойство Connected. Если свойство Connected имеет значение true — се
анс связи с БД открыт, а если задать для этого свойства значение false, то связь с БД об
рывается. Кроме того, открыть и закрыть сеанс связи можно при помощи методов Opei
и Close соответственно.
Как только вы тем или иным способом открыли сеанс связи объекта TDatabast
с базой данных, вы можете в контексте этого объекта открыть любое количество на
боров данных (связав с объектом БД свойство DatabaseName набора данных). Бел}
вы закроете все наборы данных, связанные с конкретным объектом БД, то возникав!
вполне естественный вопрос: а что же делать с сеансом связи? Оставить его откры-
тым или закрыть? На этот вопрос отвечает свойство логического типа KeepConnection
Если оно имеет значение true, сеанс связи остается открытым, даже если нет ни одногс
открытого (активного) набора данных. В противном случае при закрытии всех связан-
ных с объектом TDatabase наборов данных сеанс связи также будет закрыт. Это може!
несколько сэкономить ресурсы, однако расходы при повторном открытии сеанса связи
могут намного превысить такую экономию.
Компонент TDatabase имеет еще несколько полезных свойств, о которых хотелось бы
упомянуть. Свойства DataSets и DataSetCount можно использовать для получения досту-
па к активным (т. е. открытым в данный момент) наборам данных, связанным с объектом
Глава 3. Использование механизма ВОЕ (продолжение) 109

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) недопустимо использовать транзакции вообще.

Получение имен таблиц и полей базы данных


Получить информацию об именах таблиц базы данных и о полях каждой из таблиц
можно при помощи методов GetTableNames и GetFieldNames компонента TDatabase. Эти
методы объявлены следующим образом:
void fastcall GetTableNames(Classes::TStrings* List,
bool SystemTables = false);
void fastcall GetFieldNames(const AnsiString TableName,
Classes::TStrings* List);
Метод GetTableNames принимает в качестве аргумента указатель на список строк
(TStrings*). В результате работы метода объект TStrings будет содержать список имен
таблиц, содержащихся в базе данных, к которой подключен объект TDatabase. При этом
необязательно открывать сеанс связи (т. е. свойство Connected может иметь значение
false). Второй аргумент логического типа SystemTables указывать необязательно. Если за-
дать для этого аргумента значение true, то в список таблиц базы данных будут включаться
системные таблицы. В некоторых базах данных (например, в Microsoft Access, Interbase
и др.) автоматически создаются такие таблицы для хранения внутренней (системной)
информации. Если аргумент SystemTables не указывать, то системные таблицы в список
включаться не будут.
Метод GetFieldNames принимает два аргумента. Первый аргумент (типа AnsiString)
должен содержать имя таблицы. Второй аргумент имеет тип TString* (указатель на список
строк). Этот список строк будет после выполнения метода содержать перечень всех полей
указанной таблицы.
Чтобы проверить, как работают методы GetTableNames и GetFieldNames, можно
создать небольшое тестовое приложение. Откройте новый проект и на пустую форму
приложения добавьте два списка (объекта типа TListView). Далее создайте новый модуль
данных (пункт меню File->New->Data Module) и поместите на него один объект типа
TDatabase. Оставьте имена всех объектов без изменения, а для модуля данных задайте
имя DModl (свойство Name). Объект Database! подключите к псевдониму MyData (или
любым другим образом задайте подключение к какой-нибудь базе данных). Чтобы объект
112 Borland C++ Builder 6. Разработка приложений баз данных

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 Т

Рис. З.5. Приложение для иллюстрации применения методов


GetTableNames и GetFieldNames
На следующем шаге необходимо обеспечить возможность обновлять список ListView.
в ответ на выбор таблицы в списке ListViewl. Для этого можно воспользоваться событи
ем OnClick компонента TListView. Для списка это событие происходит не только в отве1
на щелчок мышкой на одном из его элементов, но и при переходе по элементам списк;
с помощью клавиш управления курсором. Создайте обработчик события OnClick да
объекта ListViewl и введите в его тело следующий код:
void fastcall TForml::ListBoxlClick(TObject *Sender)
{
DModl->Databasel->GetFieldNames(
ListBoxl->Items->Strings[ListBoxl->ItemIndex] ,
ListBox2->Items) ;
Глава 3. Использование механизма BDE (продолжение) 113

Откомпилируйте приложение, запустите его на выполнение и посмотрите, как это все


работает (см. рис. 3.5).
Замечание
На самом деле приложение сразу работать не будет, а при его запуске будет возбуж-
дена исключительная ситуация с сообщением самого любимого программистами типа:
«Access violation at address 00407C59 in module 'PROJECT! F.XE'». Все дело в том, что при
создании формы в обработчике события OnCreate используется обращение к модулю
данных DModl. Однако на момент создания формы объект модуля данных еще не создан
и поэтому недоступен. Чтобы избежать этой ошибки, нужно поменять очередность соз-
дания объектов формы и модуля данных. Для этого выберите пункт Options меню Project.
В окне Project Options перейдите на вкладку Forms. В левой части вкладки расположен
список автоматически создаваемых форм (Auto-create forms). Вверху списка располо-
жена Form? - это значит, что первой будет создаваться именно форма FormJ. Чтобы по-
менять очередность создания, можно перетащить при помощи мышки наименование
модуля данных DModl в верхнюю часть списка.
Того же результата можно добиться при помощи исходного кода проекта. Выберите
в меню Project пункт View Source. В окне редактора кода появится вкладка модуля^проекта
Project).срр. В этом модуле расположены вызовы методов CreateForm объекта Application.
Чтобы решить проблему, нужно просто поменять местами вызовы методов:
Application->CreateForm( classid(TDModl), &DModl);
Appl1cation->CreateForm( classld(TForml), &Forml);

События компонента TDatabase


Компонент TDatabase имеет всего пять событий, и все они имеют отношение
к открытию или закрытию сеанса связи с базой данных. Непосредственно в классе-
е TDatabase объявлено одно событие — OnLogin. События BeforeConnect, AfterConnect,
BeforeDisconnect и AfterDisconnect объявлены в классе TCustomConnection, который яв-
ляется непосредственным предком класса TDatabase. Последние четыре события имеют
стандартный тип TNotifyEvent, а событие OnLogin объявлено следующим образом:
typedef void fastcall ( closure *TDatabaseLoginEvent)
(TDatabase* Database, Classes::TStrings* LoginParams);
Событие OnLogin происходит при подключении приложения к базе данных, но только
в том случае, если свойство LoginPrompt имеет значение true и подключение к базе данных
требует авторизации (указания имени пользователя и пароля). Это событие обычно приме-
няется для задания специфических действий при подключении к базе данных, чаще всего
для задания, имени пользователя и пароля, которые переопределяют соответствующие зна-
чения, указанные в свойстве Params объекта TDatabase. Для этого используется аргумент
LoginParams, имеющий тип TStrings. Ниже приведен код, который может использоваться
в обработчике события OnLogin для подключения к базе данных типа INTRBASE:
void fastcall TForml::DatabaselLogin(TDatabase *Database,
TStrings *LoginParams)
{
LoginParams->Add("USER NAME=SYSDBA");
LoginParams->Add("PASSWORD=masterkey");
114 Borland C++ Builder 6. Разработка приложений баз данных

Если обработчик события OnLogin не задан, то сведения об имени пользователя


и пароле берутся из свойства Params или из диалогового окна, выдаваемого на экран
перед подключением к базе данных.
События BeforeConnect и AfterConnect происходят непосредственно перед открытием
сеанса связи и соответственно сразу после его открытия. Этими событиями можно восполь-
зоваться для каких-либо действий, специфичных для данного приложения. Например, можно
организовать в приложении ввод информации о подключении к конкретному типу БД, а затем
в обработчике события BeforeConnect соответствующим образом настроить свойство Params
объекта TDatabase. В следующем отрывке кода непосредственно перед подключением к базе
данных задается ее имя и путь, а также имя пользователя и пароль. Эта информация берется
из соответствующих переменных, куда она вводится пользователем.
void fastcall TForml::DatabaselBeforeConnect(TObject *Sender)
{
Databasel->Params->Clear();
Databasel->Params->Add("SERVER NAME=" + DBName);
Databasel->Params->Add("USER NAME=" + UName);
Databasel->Params->Add("PASSWORD=" + PWord);
}
Аналогичным образом используются и события BeforeDisconnect и After Disconnect,
с той лишь разницей, что первое из них происходит непосредственно перед разрывом
сеанса связи, а второе — сразу после того, как сеанс связи будет закрыт.

Управление сеансами связи при помощи


компонента TSession
Точно так же, как компонент TDatabase определяет общий фон для всех активных в его
контексте наборов данных, компонент TSession определяет набор свойств, задающих об-
щий фон для всех сеансов связи (объектов TDatabase), открытых в его контексте. Объект
типа TSession, или сессию можно использовать не только для задания свойств, определя-
ющих поведение создаваемых неявно объектов базы данных (TDatabase). С его помощью
можно также:
Q получать информацию об объектах, используемых в контексте текущей сессии (псев-
донимах, базах данных, наборах данных, полях таблиц и т. д.);
Q открывать или закрывать на уровне сессии все фигурирующие в ее контексте объекты
баз данных или наборов данных;
G обрабатывать события OnPassword и OnStartUp компонента TSession;
Q устанавливать для таблиц Paradox сетевые (NetFileDir) и локальные (PrivateDir) ди-
ректории;
Q задавать пароль для доступа к таблицам Paradox, закрытым паролем.
Обычно нет необходимости явно создавать объект TSession. Объект этого типа соз-
дается неявно при использовании в приложении любого из объектов, требующих под-
ключения к базе данных (объектов TDatabase или одного из наборов данных). Вы можете
сами в этом убедиться, разместив на форме или модуле данных, например, объект ТТаЫе
или TDatabase. В окне Object TreeView появится узел Default, соответствующий неявно
Глава 3. Использование механизма BDE (продолжение) 115

созданному объекту TSession. Обратите внимание, что если вы добавили на форму или
модуль данных один из объектов набора данных (например, ТТаЫе), то неявно создается
и связывается с Session и объект типа TDatabase. Доступ к неявно созданному объекту
TSession можно получить через глобальную переменную Session.
Если есть необходимость в настройке свойств объекта TSession в режиме конструкто-
ра, то нужно создать объект явно, поместив его с вкладки BDE или на форму, или в модуль
данных. В большинстве случаев в приложении достаточно одного объекта TSession, соз-
данного явно или неявно. Однако иногда бывает необходимо использовать несколько та-
ких объектов, например, если вы применяете несколько наборов объектов баз данных, для
которых предусмотрены разные глобальные настройки поведения. Кроме того, если вы
используете в своем приложении несколько потоков (Threads), то необходимо для каждого
из потоков использовать свой объект TSession.
Далее будет рассмотрено несколько задач, решаемых при помощи компонента
TSession.

Свойства и события компонента TSession


Если вы явно создаете в приложении объект типа TSession, то нужно задать для него
имя (свойство Name) и имя сессии (свойство SessionName). Имя объекта используется для
ссылки на него в программном коде, а имя сессии — для ассоциации с сессией явно или
неявно создаваемых объектов баз данных. Явно создаваемые объекты баз данных связы-
ваются с конкретной сессией при помощи свойства SessionName. Список этого свойства
кроме имен явно созданных объектов сессий содержит и имя сессии, создаваемой по
умолчанию (Default).
Неявно создаваемые объекты баз данных автоматически связываются с сессией по
умолчанию. Например, если разместить на форме объект ТТаЫе, автоматически будет
создан объект Session, представляющий сессию Default. Этот объект будет создан в любом
случае, даже если форма уже содержит явно созданные объекты типа TSession. Также авто-
матически будет создан неявный объект базы данных, связанный с сессией по умолчанию.
И, наконец, объект типа ТТаЫе связывается с неявно созданным объектом базы данных.
Все эти связи между объектами можно наблюдать в окне Object TreeView. Если вы хотите
перевести объект набора данных под управление явно созданной сессии, то следует задать
имя этой сессии в качестве значения свойства SessionName набора данных.
Компонент TSession имеет одно интересное свойство, связанное с именованием соз-
даваемой сессии. Это свойство логического T:vm& AutoSessionName. Если задать для этого
свойства значение true, то BDE будет автоматически генерировать для объекта уникальное
имя сессии (значение свойства SessionName). Имя сессии генерируется на основе имени
объекта, после которого ставится символ подчеркивания «_» и цифра. Например, для объ-
екта Session! будет сгенерировано имя сессии наподобие Session!_1. Основное назначе-
ние автоматически генерируемых имен сессий в том, чтобы гарантировать разработчику
многопоточных приложений, что создаваемые в разных потоках объекты TSession не вы-
зовут конфликт имен.
После того как для свойства AutoSessionName устанавливается значение true, свой-
ство SessionName всех объектов наборов данных (наследников TDataSet) или баз данных
(TDatabase) в качестве значения получает автоматически сгенерированное имя этой сес-
сии. Другими словами, все наборы данных переходят под управление той сессии, свойство
которой AutoSessionName было установлено в значение true. Отсюда вытекает три вполне
естественных ограничения на использование этого свойства. Нельзя разрешить автомати-
116 Borland C++ Builder 6. Разработка приложений баз данных

ческую генерацию имен, если форма или модуль данных содержит более одного объект
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

события можно воспользоваться для действий, специфичных для данного приложе-


ния, например, с информационными целями. Событие OnPassword используется при
подключении к таблицам Paradox, закрытым паролем. Это событие происходит, когда
BDE пытается в первый раз подключиться к закрытым паролем таблицам Paradox,
если пароль не был предоставлен другими средствами. В этом случае в обработчике
события OnPassword необходимо задать подходящий пароль (например, при помощи
вызова метода AddPaSsword объекта TSessiori). Кроме стандартного аргумента Sender
типа TObject, обработчик события OnPassword получает еще и аргумент логического
типа Continue. Можно задать для этого аргумента значение true, в результате чего добав-
ленный пароль будет доступен для BDE, иначе пароль будет отвергнут. Если обработчик
события OnPassword отсутствует, BDE выдает стандартное диалоговое окно для ввода
пароля. Более подробно о работе с таблицами Paradox и dBASE, которые закрыты паро-
лями, будет рассказано в следующем разделе.
Замечание
В тело обработчика события OnPassword передается аргумент Sender, указывающий
на объект типа TObject. К сожалению, этот аргумент указывает на объект TSess/on, а не
на объект набора данных, при помощи которого производится попытка подключиться
к таблице базы данных.

Использование паролей для доступа


к таблицам Paradox и dBASE
Таблицы Paradox и dBASE можно закрыть паролями. Для этого можно воспользо-
ваться SQL Explorer или Database Desktop. Например, чтобы для таблицы Paradox за-
дать пароль при помощи утилиты Database Desktop, следует открыть нужную таблицу
в режиме изменения структуры (пункт Restructure меню Table) и выбрать позицию
Password Security из комбинированного списка Table properties. Если пароль для табли-
цы еще не был установлен, то нужно нажать кнопку Define, иначе — кнопку Modify.
В первом случае в открывшемся диалоговом окне вам нужно указать пароль в поле вво-
да Master pass word и затем повторить его в поле ввода Verify master password. Во втором
случае вы можете воспользоваться или кнопкой Delete для удаления пароля, или кноп-
кой Change для его изменения. При помощи кнопки Auxiliary Passwords можно задать
права доступа более изощренно — например, права доступа к отдельным полям, права
на удаление, вставку записей, и т.д.

Рис. 3.6. Стандартное диалоговое окно для ввода пароля


После этого при использовании таблицы нужно указывать один или несколько паролей,
на основании которых и будет определяться набор прав, предоставляемых пользователю.
Компонент TSession содержит набор методов, позволяющих управлять наборами паролей
для доступа к таблицам Paradox и dBASE. Механизм управления паролями основан на
118 Borland C++ Builder 6. Разработка приложений баз данных

том, что объекты типа 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 сразу
после определения пользователя, не дожидаясь реального подключения к таблицам.

Управление сеансами связи


Компонент TSession содержит достаточно большую группу методов, позволяющих ма-
нипулировать сеансами связи с базами данных. Часть этих методов предназначена для ра-
боты с псевдонимами и драйверами, часть — управляет сеансами связи с базами данных.
При помощи методов AddAlias viAddStandardAlias к списку псевдонимов, зарегистри-
рованных (например, при помощи BDE Administrator) и доступных для любой сессии
BDE, можно добавить новый псевдоним, доступный только в рамках текущей сессии.
Метод AddA lias объявлен следующим образом:
void fastcall AddAlias(const AnsiString Name,
const AnsiString Driver, Classes::TStrings* List);
Аргументы Name и Driver задают, соответственно, имя псевдонима и использующийся
драйвер базы данных. Третий аргумент метода AddAlias имеет тип указателя на абстракт-
ный класс TStrings. В приложении вместо класса TStrings следует использовать один из
его наследников — например, класс TStringList. При помощи аргумента List можно за-
дать список параметров для нового псевдонима, например, имя и путь к базе данных, имя
пользователя и т.д.
Ниже приведен отрывок кода, используемый для создания нового псевдонима для
базы данных employee.gdb типа INTRBASE. Вначале создается экземпляр ParList класса
TStringList. Затем в теле оператора try этот объект заполняется информацией о парамет-
рах. Имя и путь к базе данных задается в параметре SER VER NAME, имя пользователя
и пароль — соответственно, в параметрах USER NAME и PASSWORD. В заключение де-
лается попытка создания нового псевдонима. Операторы заключительной части отрывка
кода связывают сетку DBGridl с объектом DataSourcel, который в свою очередь связы-
вается с таблицей Tablel. В заключение объект Tablel связывается с сессией MySession
(свойство SessionName), с созданным псевдонимом (свойство DatabaseName) и таблицей
Sales (свойство TableName). После вызова метода Open набор данных будет открыт
и сетка DBGridl заполнится данными.
void fastcall TForml::ButtonlClick(TObject *Sender)
{
TStringList *ParList = new TStringList();
try
{
ParList->Add("SERVER NAME=d:\\Temp\\employee.gdb");
ParList->Add("USER NAME=SYSDBA");
ParList->Add("PASSWORD=masterkey");
Sessionl->AddAlias("MyIntrbase", "INTRBASE", ParList);
120 Borland C++ Builder 6. Разработка приложений баз данных

}
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, приводится информа-
ция о том, как использовать сессию для получения исчерпывающей информации обо всех
соединениях и наборах данных, выполняющихся в ее контексте.

Получение информации о соединениях


Компонент TSession имеет большую группу методов, наименование которых начинает-
ся с префикса Get. При помощи этих методов можно получать список псевдонимов, драй-
веров, их параметров, а также баз данных, таблиц и полей таблиц, содержащихся в конте-
ксте данной сессии. Каждый из этих методов принимает аргумент типа TStringList*, ко-
торый и заполняется запрошенной информацией при работе метода. Приложение должно
само поз'аботиться о том, чтобы объект типа TStringList был предварительно создан и,
когда необходимости в его использовании не будет, уничтожен.
Ниже приведено объявление трех однотипных методов, принимающих только аргу-
мент типа TStringList* (TString*).
122 Borland C++ Builder б. Разработка приложений баз данных

void fastcall GetAliasNames(Classes::TStrings* List);


void fastcall GetDatabaseNames(Classes::TStrings* List);
void fastcall GetDriverNames(Classes::TStrings* List);
После завершения выполнения этих методов аргумент будет содержать список псев-
донимов, баз данных или драйверов соответственно. Вот как, например, можно получить
список доступных сессии драйверов:
TStringList *MyList=new TStringList();
try
{
Sessionl->GetAliasNames(MyLi st);
Li stBoxl->Items=MyList;
Li stBoxl->ItemIndex=0;
}
fi n a 11 у
{
delete MyLfst;
}
В первой строке отрывка кода, приведенного выше, создается объект типа TStringList,
а затем предпринимается попытка заполнить его наименованиями всех псевдонимов,
доступных сессии Session 1. Если это удается, то этот список присваивается свойству
Items объекта ListBoxl, который и будет теперь отображать полученный список псевдо-
нимов. И в заключение активным делается первый псевдоним в списке объекта ListBoxl.
В случае неудачи объект типа TStringList удаляется, освобождая ресурсы.
Остальные методы с префиксом Get имеют сходную структуру, но кроме аргумента
типа TStringList* (TString*), получают еще и другие аргументы:
void fastcall GetAliasParams(const AnsiString AliasName,
Classes::TStrings* List);
void fastcall GetDriverParams(const AnsiString DriverName,
Classes::TStrings* List);
void fastcall GetStoredProcNames(const AnsiString DatabaseName,
Classes::TStrings* List);
void fastcall GetTableNames(const AnsiString DatabaseName,
const AnsiString Pattern, bool Extensions, bool SystemTables,
Classes::TStrings* List);
void fastcall GetFieldNames(const AnsiString DatabaseName,
const AnsiString TableName, Classes::TStrings* List);
Первые три метода кроме списка получают еще один аргумент — имя объекта, для ко-
торого список и должен быть заполнен соответствующей информацией. Для первых двух
методов список List заполняется параметрами, соответственно, псевдонима и драйвера,
чье имя было передано в первом аргументе, а третий метод заполняет список именами
хранимых процедур указанной базы данных.
Структура метода GetTableNames немного сложнее. Этот метод возвращает в списке
List перечень всех таблиц базы данных DatabaseName. На возвращаемый перечень влия-
ют значения еще трех аргументов, передаваемых в метод GetTableNames. Строка Pattern
может содержать шаблон возвращаемых таблиц и стандартные символы замены. Если
аргумент Pattern — пустая строка, возвращаются все таблицы. Если аргумент Extensions
Глава 3. Использование механизма ВОЕ (продолжение) 123

имеет значение 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. Разработка приложений баз данных

void _ fastcall FormCreate(TObject *Sender) ;


void _ fastcall ComboBoxlChange(TObject *Sender)
void _ fa'stcall ListBoxlClick(TObject *Sender) ;
void _ fastcall ListBox2Click(TObject *Sender) ;
private: // User declarations
public: // User declarations
_ fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


//
#endif

Листинг З.2. Главный модуль приложения GetSessionlnfo

#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)

void _ fastcall TForml : :FormCreate(TObject *Sender)


{
ComboBoxl->ItemIndex=0;
Width=336;
ComboBoxlChange(NULL) ;
ListBoxlClick(NULL) ;

void _ fastcall TForml : :ComboBoxlChange(TObject *Sender)


{
TStringList *MyList=new TStri ngLi st () ;
try
{
switch (ComboBoxl->ItemIndex)
{
case 0: //Псевдонимы
{
Sessionl->GetAliasNames(MyList) ;
ListBox3->Visible=false;
Label3->Visible=false;
Width=330;
break;
Глава 3. Использование механизма ВОЕ (продолжение) 125

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. Разработка приложений баз данных

case 2: //Базы данных


{
Sessionl->GetTableNames(Li stBoxl->I terns ->
Strings[ListBoxl->ItemIndex] ,
"" , false, false, MyList) ;
Label2- >Сар11оп="Таблицы: " ;
break;

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;

Работа приложения GetSessionlnfo начинается с выполнения кода обработчика события


OnCreate главной формы Forml. Первая строка кода активизирует первый элемент списка
элемента ComboBoxl. Первый элемент комбинированного списка, как уже упоминалось
выше, — это «Псевдонимы». В этом случае задействованы только два списка: в крайнем
слева списке ListBoxl будут отображаться все псевдонимы, а в списке ListBox2 — список
соответствующих параметров. Третий список, ListBoxS, и метка Labels сразу отображать-
ся не должны, поэтому нужно соответствующим образом настроить ширину формы. Для
этого ее свойству Width задается значение 330.
Чтобы обновить содержимое списков, вызываются обработчики события OnChange ком-
бинированного списка ComboBoxl и OnClick объекта ListBoxl. Первый из этих обработчиков
предназначен для обновления списка ListBoxl , второй — для обновления списка ListBox2.
Глава 3. Использование механизма ВОЕ (продолжение) 127
Как видно из листинга 3.2, обработчики событий, возбуждаемых при изменении актив-
ного элемента в комбинированном списке и списках TListBox, объединены в иерархичес-
кую цепочку. При выборе элемента в комбинированном списке ComboBoxl возбуждается
его событие OnChange. После выполнения необходимых действий в коде обработчика
этого события вызывается обработчик события OnClick списка ListBoxl. Таким обра-
зом, обработчик события OnClick списка ListBoxl может быть вызван или вручную (из
обработчика ComboBoxl Change), или при выборе нового элемента списка (при помощи
мышки или клавиатуры). В коде обработчика этого события при необходимости (если
в комбинированном списке ComboBoxl был выбран элемент «Базы данных») вызывается
обработчик события OnClick списка ListBox2. Таким образом происходит цикл обновле-
ния содержимого всех элементов главной формы приложения.
Обработчики событий OnClick и OnChange, участвующие в обновлении элементов
управления формы, имеют сходную структуру. В начале кода обработчика создается объект
MyList типа TStringList. Далее в теле оператора try проверяется содержимое комбиниро-
ванного списка ComboBoxl. В зависимости от сделанного выбора вызывается нужный
метод, заполняющий объект MyList, должным образом настраивается ширина формы,
а также видимость элементов ListBoxS и LabelS, и задаются подписи для меток Label 1
и Label2. В заключение список MyList присваивается свойству Items следующего в иерар-
хии списка TListBox, и его первый элемент активизируется.
Откомпилируйте приложение, запустите его на выполнение и поэкспериментируй-
те с ним, наблюдая действия приложения в ответ на выбор тех или иных элементов.
Примерный вид главной формы приложения приведен на рис. 3.7.

DefauUDO
IBLocel
MyOata
MvOotol
База ценных MS Access
:unl FoxPro
Та&лииы Visual FoxPro
Файлы dSASE

Рис. З.7. Приложение GetSessionlnfo


Замечание
Приведенное выше в качестве примера приложение GetSessionlnfo, как и всякий при-
мер, не является сбалансированным и устойчивым. В частности, при настройке ширины
формы нужно учитывать разрешение экрана, при котором приложение выполняется.
Кроме того, ни один список не проверяется на отсутствие записей. Этот перечень мож-
но продолжать долго. Все недостатки приложения-примера оправдываются тем, что
его целью было продемонстрировать определенные принципы работы с компонентом
TSession, а не все до единого принципы программирования.

Замечание
К упомянутой группе методов можно отнести также и методы 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).

Простейшие средства управления данными


К простейшим элементам визуализации и управления данными можно отнести все те,
которые предназначены для предоставления доступа к значению поля одной (текущей)
записи. На вкладке Data Controls к этой категории относятся следующие компоненты:
TDBText, TDBEdit, TDBMemo, TDBImage, TDBCheckBox и TDBRichEdit. Каждый из них
Глава 3. Использование механизма ВОЕ (продолжение) 129
имеет два свойства, связывающие его с конкретным полем выбранного источника данных.
Свойство DataSource должно содержать имя источника данных (объекта TDataSource),
подключенного к одному из наборов данных, доступных в приложении. Связать эле-
мент управления с конкретным полем источника данных можно при помощи свойства
DataField, выбрав имя поля из выпадающего списка свойства.
Если набор данных, с которым связан элемент управления, открыт, то вы немедленно
увидите значение поля первой записи набора. При переходе к другой записи набора все
связанные с набором данных элементы управления обновятся, отображая соответствую-
щие значения полей этой записи. Перемещаться по записям набора данных в режиме кон-
структора можно при помощи панели навигации редактора полей (Field Editor), который
можно отобразить на экране, дважды щелкнув мышкой на иконке объекта набора данных.
В режиме выполнения приложения нужно предоставить пользователю какой-либо способ
навигации по набору данных. Для этого можно использовать компонент TDBNavigator,
также расположенный на вкладке Data Controls, или обеспечить какую-либо другую воз-
можность вызывать методы навигации (First, Last, Next и т. д.).
Остальные свойства, а также методы и события простейших элементов визуализации
и управления данными мало отличаются от свойств, методов и событий соответствующих стан-
дартных элементов управления. Смысл использования элементов обеих групп также совпадает.
Так, например, объекты типа TDBEdit предназначены для отображения и редактирования одно-
строчного текста, являющегося значением поля набора данных; объекты типа TDBText предна-
значены только для отображения однострочного текста; объекты типа TDBRichEdit позволяют
отображать и редактировать многострочный форматированный текст, и т. д.
Несколько особняком от остальных элементов управления с вкладки Data Controls
стоит компонент TDBNavigator (панель навигации). На этой вкладке это единственный
элемент управления, не предназначенный для отображения информации. С его помощью
можно перемещаться по записям набора данных, а также удалять, добавлять, сохранять
записи или отменять сделанные изменения. Для подключения к набору данных нужно
указать имя имеющегося объекта TDataSource. Управлять наличием кнопок панели на-
вигации можно при помощи свойства VisibleButtons. Это свойство типа множество (Set)
может содержать набор констант, каждая из которых соответствует конкретной кнопке
панели навигации (например, nbFirst, nbPrior и т. д.). Чтобы удалить кнопку с панели на-
вигатора в режиме конструктора, достаточно задать в инспекторе объектов для соответ-
ствующей константы значение false. Кнопка будет удалена с панели, и соответствующая
функция в режиме выполнения приложения доступна не будет.

Рис. 3.8. Форма для отображения и редактирования подробной информации о сотруднике.


Объект типа TDBNavigator служит для перемещения по записям таблицы Employee
5 Зак.319
130 Borland C++ Builder 6. Разработка приложений баз данных

На рис. 3.8 изображена форма для отображения подробной информации о сотруднике


содержащая набор простых элементов визуализации и управления данными. Для переме-
щения по записям используется панель навигации (компонент TDBNavigator). Эта форме
могла быть вызвана из другой формы, содержащей элемент типа TDBGrid. Сетка, в свок
очередь, могла бы содержать краткий список сотрудников.

Списки, комбинированные списки и группы


На вкладке Data Controls находится группа компонентов, представляющих в прило
жении значение поля одной записи, но предоставляющих возможность для выбора это
го значения из набора элементов. К этим компонентам относятся списки (TDBListBo)
и TDBLookupListBox), комбинированные списки (TDBComboBox и TDBLookupComboBox
и группы переключателей (TDBRadioGroup).
Компонент TDBListBox, как и обычный список, отображает элементы, перечисленньк
в его свойстве Items. При помощи свойств DataSource и DataField объект типа TDBListBo)
подключается к полю набора данных (таблицы). При переходе от одной записи наборе
данных к другой меняется и активный элемент списка, отображая значение поля текуще!
записи. При выборе элемента списка при помощи мышки или клавиатуры его значение
присваивается присоединенному полю набора данных.
Несколько большие возможности предоставляет компонент TDBLookupListBox. Благодар5
этому TDBLookupListBox и используется гораздо чаще, чем компонент TDBListBox. В первук
очередь следует отметить, что компонент TDBLoohipListBox не имеет свойства Items. Вместе
этого задать источник строк для отображения в списке можно при помощи двух других свойств
ListSource и ListField. Свойство ListSource должно содержать имя объекта TDataSource, под
ключенного к тому набору данных, значения одного или нескольких полей которого должнь
отображаться в списке. Этот набор данных называется таблицей подстановки (Lookup Table)
Имена отображаемых полей должны указываться в свойстве ListField. Имя одного поля можнс
выбрать из списка свойства, а имена нескольких полей следует вводить вручную, разделяя го
символом точки с запятой.
Еще одно важное свойство компонента TDBLookupListBox, связанное с таблице?
подстановки, — KeyField. Это свойство должно содержать имя поля таблицы подста
новки, которое будет представлять отображаемые в списке объекта значения. Чаще всегс
в качестве значения Свойства KeyField выступает первичный ключ таблицы (что и следу
ет из его названия). Например, можно в качестве таблицы подстановки для объекта тип;
TDBLookupListBox использовать таблицу Category, содержащую перечень всех катего
рий товаров. В этом случае в качестве значения свойства ListField удобно задать пол(
Category, а для свойства KeyField — поле CategoryNo, являющееся первичным ключо\
таблицы. Таким образом, отображаться будут текстовые описания категорий, а использо
ваться — соответствующие первичные ключи.
Как и почти все остальные элементы визуализации и управления данными, компонеш
TDBLookupListBox имеет свойства DataSource и DataField. Эти свойства служат для под
ключения к полю набора данных, которое объект TDBLookupListBox должен представ
лять. Поле, указываемое в свойстве DataField, должно по типу соответствовать полю, ука
занному в свойстве KeyField, так как по этим полям должна осуществляться связь межд)
основным набором данных и таблицей подстановки. Чаще всего свойство KeyField содер
жиг имя первичного ключа таблицы подстановки, а свойство DataField— имя внешнегс
ключа основной таблицы.
Глава 3. Использование механизма ВОЕ (продолжение) 131

Продолжая начатый ранее пример (в котором как таблица подстановки используется


Category), в качестве основной таблицы для объекта TDBLookupListBox можно взять
Items. Тогда свойство DataField можно установить в значение Category, так как именно это
поле является внешним ключом по отношению к таблице Category. Таким образом, при
выборе в списке объекта TDBLookupListBox наименования категории, внешнему ключу
Category таблицы Items будет присвоено соответствующее значение первичного ключа та-
блицы Category. Используя такую схему редактирования таблицы Items, можно добиться
максимальной наглядности и избежать ошибок из-за нарушения целостности данных.
Замечание
Объект типа TDBLookupListBox можно использовать только после того, как для него
заданы значения всех упомянутых ранее свойств: ListSource, ListField, KeyField, DataSource
и DataField. Если вам не нужны функциональные возможности компонента, связанные
сего направленностью на связь с базой данных, следует воспользоваться компонентом
TListBox, а если нет необходимости в таблице подстановки, лучше использовать компо-
нент TDBListBox.

Почти то же самое можно сказать и относительно компонентов TDBComboBox


и TDBLookupComboBox. Объект типа TDBComboBox может отображать в своем-
м выпадающем списке элементы, перечисленные в его свойстве Items, а объект типа
TDBLookupComboBox для отображения элементов выпадающего списка использует меха-
низм таблиц подстановки (свойства ListSource, ListField и KeyField). В остальном работа
с комбинированными списками обоих типов практически ничем не отличается от работы
с обычным компонентом ТСотЬоВох.
Еще один элемент управления, предоставляющий возможность отображать или
выбирать значения для поля из набора фиксированных элементов — - компонент
TDBRadioGroup. Для подключения к полю набора данных используются свойства
DataSource и DataField, а набор отображаемых в группе элементов хранится в свойстве
Items. Чтобы разместить элементы в несколько столбцов внутри группы, можно восполь-
зоваться свойством Columns. По умолчанию это свойство имеет значение 1, т. е. элементы
отображаются в одном столбце. Свойство Values типа TString должно содержать столько
же элементов, сколько и свойство Items. При выборе одного из отображаемых в группе
элементов соответствующий элемент из свойства Values присваивается полю текущей за-
писи, имя которого указано в свойстве DataField.

Диаграмма (компонент TDBChart)


Компонент TDBChart предназначен только для отображения информации, модифици-
ровать данные с его помощью нельзя. Диаграмма TDBChart — наиболее сложно органи-
зованный компонент из всех, расположенных на вкладке Data Controls. Чтобы убедиться
в этом, достаточно взглянуть на диалоговое окно редактирования ее свойств (см. рис.
3.9), которое отображается на экране после двойного щелчка мышкой на изображении
объекта. Окно редактирования свойств диаграммы содержит несколько десятков вкладок
различного уровня вложения, вмещающих огромное количество элементов управления.
Весь этот гигантский набор предназначен для тонкой настройки объекта диаграммы: от
задания способа получения набора данных до тонких аспектов отображения различных
элементов диаграммы (сетки, осей, области данных и т.д.).
Однако, несмотря на обилие, большинство свойств диаграммы автоматически по-
лучает значения по умолчанию. Поэтому на самом деле пользоваться диаграммой не так
132 Borland C++ Builder 6. Разработка приложений баз данных

уж сложно. В этом разделе будет рассмотрен пример, описание которого поможет понят
логику работы с компонентом TDBChart.
Прежде чем приступить к примеру, следует добавить в базу данных с псевдониме»
MyData еще одну таблицу. Эта таблица будет называться Storage, и в ней будут хранить
ся данные об остатках товара на складе. Описание структуры таблицы Storage приведен!
в таблице 3.1.
Таблица 3.1. Описание структуры таблицы Storage

Поле Tun Размер Ключ Обязательное


(Field Name) (Type) (Size) (Key) (Required)
StoreltemNo Autoincrement (+) - * Yes
Item Long Integer (I) - Yes
Qty Short (S) - No
Description Memo (M) 240 No

Как видите, таблица Storage невелика— всего четыре поля. Поле StoreltemNo яв
ляется первичным ключом таблицы, а поле Item — внешним ключом, ссылающимся н<
первичный ключ таблицы Items. Поле Qty предназначено для хранения количества товара
имеющегося на складе. Вводить значение в это поле не обязательно, так как отсутстви*
значения просто будет расцениваться как отсутствие товара на складе (т. е. отсутствующее
значение будет считаться нулем). В поле Description можно будет хранить дополнитель
ную информацию, например, о необходимости заказать товар и т.д. В заключение таблиц)
Storage необходимо связать с таблицей Items по полям Item и ItemNo. Как это можнс
сделать при помощи утилиты Database Desktop, уже рассказывалось в предыдущих гла
вах. Сохраните таблицу под именем Storage в том же каталоге, где хранятся и остальные
таблицы базы данных (псевдонима) MyData.

Format. | General | Mtrts Do» Source |

labels: (C

Рис. З.9. Диалоговое окно редактирования свойств диаграммы (компонент TDBChart)


Обязательным для заполнения в таблице Storage является поле Item; желательно так-
же заполнить некоторой информацией поле Qty, иначе склад будет считаться пустым. Поле
Item должно хранить значения первичных ключей таблицы Items. Один из самых простых
вариантов заполнения поля Item значениями — использование компонента TBatchMove,
который как раз и предназначен для подобных групповых операций. Более подробно ком-
понент TBatchMove будет рассматриваться чуть позже в этой главе. Пока же проделайте
Глава 3. Использование механизма ВОЕ (продолжение) 133

следующее. Откройте новый проект и добавьте на форму приложения два компонента


ТТаЫе и один компонент TBatchMove. Объект Tablel подключите к таблице Storage базы
данных MyData, а объект ТаЫе2 — к таблице Items той же базы. Для свойства Active обо-
их объектов задайте значение true.
Далее, для свойства Source объекта BatchMovel задайте значение ТаЫе2, а свойство
Destination установите в значение Tablel. Таким образом, информация будет копироваться
из таблицы Items в таблицу Storage. Затем щелкните на кнопке построителя для свойства
Mappings и в окне редактора строк введите следующий текст:
Item=IternNo
При помощи свойства Mappings можно указать поля таблицы-источника, значения
из которых будут копироваться. Имена полей таблицы-источника указываются справа от
знака равенства, а соответствующие имена полей таблицы-приемника — слева от знака
равенства.
Добавьте на форму кнопку (TButton), а в тело обработчика ее события OnClick введите
всего одну строку:
BatchMovel->Execute();
Эта строка кода запускает копирование информации, заданное в настройках объекта
BatchMovel, в результате чего будет заполнено значениями поле Item таблицы Storage.
Поле Qty можно, конечно, заполнить и вручную, однако это слишком длительный процесс.
Лучше поступить следующим образом. Добавьте на форму еще одну кнопку и в тело ее
обработчика события OnClick введите следующий код:
randomizeO ;
Tablel->First() ;
while (!Tablel->Eof)
{
Tablel->Edit() ;
Tablel->FieldByName("Qty")->Value=random(100);
Tablel->Post();
Tablel->Next();
}
В результате выполнения этого отрывка кода поле Qty таблицы Storage будет заполне-
но случайными значениями в диапазоне от 0 до 100. Впрочем, если вам неохота возиться,
выполняя все вышеописанные действия, просто воспользуйтесь уже готовой таблицей
Storage, которая находится на поставляемом с книгой диске в каталоге Data.
Теперь можно вернуться к примеру использования компонента TDBChart. Создайте
новое приложение и на его главной форме расположите компонент TDBChart, задав его
размеры так, чтобы заполнить всю рабочую область формы. После этого добавьте на фор-
му компонент TQuery с вкладки BDE. Подробно этот компонент будет рассмотрен позже,
а пока просто следуйте инструкциям. В качестве значения свойства DatabaseName объек-
та Query 1 выберите псевдоним MyData. Затем щелкните на кнопке построителя свойства
SQL и в окне редактора строк введите следующий текст:
SELECT Category.Category, Sum(Storage.Qty*Items.Price) AS Cost
FROM (Items INNER JOIN Category ON Items.Category =
134 Borland C++ Builder 6. Разработка приложений баз данных

Category.CategoryNo) INNER JOIN Storage ON Items.ItemNo = Storage.Item


GROUP BY Category.Category;
Приведенный выше текст — это оператор, записанный на языке запросов SQL. В этом
запросе участвуют три таблицы (Category, Items и Storage), связанные между собой по
ключевым полям (т.е. так, как это было запланировано при проектировании). Из таблицы
Category выбираются все наименования категорий, а из таблицы Storage — стоимость
всех товаров каждой категории, хранящихся на складе. Таблица Items служит для того,
чтобы связать между собой таблицы Category и Storage. Более подробно о языке запро-
сов SQL будет рассказано далее, при изучении компонента TQnery. В заключение устано-
вите свойство Active объекта Query! в значение true.
Теперь пора настроить объект DBChartl. После двойного щелчка мышкой на диа-
грамме на экране появится диалоговое окно редактирования ее свойств. Откройте окно на
вкладке Chart (вкладка самого верхнего уровня) и затем внутри этой вкладки, на вкладке
Series. В центральной части этой вкладки располагается список серий данных, которые
должны будут отображаться на диаграмме. Пока этот список пуст. Нажмите кнопку Add,
в появившемся диалоговом окне TeeChart Gallery выберите вид диаграммы Pie и нажмите
кнопку ОК. В результате в списке серий появится Seriesl.

Рис. 3.10. Диаграмма, отражающая соотношение


стоимости товаров каждой категории на складе в процентах
Далее, выберите эту серию и перейдите на вкладку верхнего уровня Series. Внутри
этой вкладки перейдите на вкладку Data Source и из расположенного там комбинирован-
ного списка выберите элемент Dataset. В результате на вкладке Data Source появятся еще
три комбинированных списка (см. рис. 3.9). В комбинированном списке Dataset выберите
Query 1. Из комбинированного списка Labels выберите поле Category, а из комбиниро-
ванного списка Pie— поле Cost. Таким образом, круговая диаграмма будет поделена
на секторы в зависимости от значений поля Cost (стоимость товаров каждой категории),
а помечен каждый сектор будет при помощи значений поля Category.
Удобно было бы, чтобы кроме наименований категорий, метки на диаграмме также со-
держали долю каждой категории в общей стоимости всех товаров. Перейдите на вкладку
Marks и в группе переключателей Style выберите переключатель Label and Percent. Теперь
вновь перейдите на вкладку верхнего уровня Chart и внутри нее — на вкладку Titles. На
этой вкладке вместо стандартного заголовка TDBChart введите что-нибудь более подходя-
щее. Например, Остатки на складе по категориям. Можно также отключить отображение
легенды, так как она занимает слишком много места. Для этого перейдите на соседнюю
вкладку Legend и снимите флажок Visible. Etce, диаграмма готова (см. рис. 3.10).
Глава 3. Использование механизма BDE (продолжение) 135
Компонент TDBCtrlGrid
Компонент TDBCtrlGrid, как и компонент TDBGrid, предназначен для одновременного
отображения информации сразу из нескольких записей. Однако в этих элементах отобра-
жение организовано по-разному. В то время как компонент TDBGrid отображает записи
набора данных в табличной форме, TDBCtrlGrid предоставляет возможность отображать
каждую запись на отдельной панели. При этом используются обычные элементы визуали-
зации и управления данными (TDBEdit, TDBImage и другие).

Рис. 3.11. Компонент TDBCtrlGrid в режиме конструктора


После размещения компонента TDBCtrlGrid на форме он приобретает вид прямоуголь-
ника, разделенного на три горизонтальные полосы или панели (см. рис. 3.11). Верхняя
панель предназначена для размещения элементов управления данными, связанных
с набором данных. Подключить объект TDBCtrlGrid к набору данных можно при помощи
свойства DataSource. После этого на верхней панели можно разместить и сгруппировать
нужным образом элементы визуализации и управления данными, подключив их к соот-
ветствующим полям набора данных. В режиме выполнения главная форма приложения
будет содержать также три панели. Эти три панели будут отображать информацию из трех
последовательно расположенных записей набора данных (см. рис. 3.12). Перемещаться по
записям набора данных можно при помощи полосы прокрутки. Можно также воспользо-
ваться услугами компонента TDBNavigator, подключив его к тому же источнику данных.
Две нижние панели компонента TDBCtrlGrid отображаются заштрихованными в режиме
конструктора. На этих панелях тоже можно размещать элементы управления, в том числе
и элементы визуализации и управления данными. Однако в режиме выполнения приложения
эти элементы управления будут размещаться только на той панели, на которой они размещены
в режиме конструктора (конечно же, имеются в виду заштрихованные панели).

Рис. 3.12. Объект типа TDBCtrlGrid в режиме выполнения


136 Borland C++ Builder 6. Разработка приложений баз данных

В заключение краткого обсуждения компонента TDBCtrlGrid следует упомяну


о нескольких важных его свойствах.
G ColCount и RowCoimt. Эти свойства задают способ размещения и количество панеле
Значение по умолчанию для свойства ColCount — 1, а для свойства RowCount — 3. Э'
означает, что всего панелей 3 и расположены они в один столбец и в три строки. Таки
образом можно задать любое количество панелей и разместить их в несколько стрс
и столбцов.
Q Orientation. Свойство задает ориентацию панелей (вертикальную или горизонтальную).
Q AllowDelete и Allowlnsert. Эти свойства логического типа задают возможность удал!
ния или вставки записей.

Использование сетки (компонент TDBGrid)


Последний компонент с вкладки Data Controls, который еще не был рассмотре
в этом разделе, — это TDBGrid. Мы уже пользовались им ранее, создавая разные прк
меры. Компонент TDBGrid (или сетка) предназначен для отображения информации и
набора данных в табличной форме, т. е. в виде строк и столбцов. Кроме отображени
данных, сетка предоставляет возможность (при определенных условиях) добавлят
или удалять строки набора данных, а также модифицировать данные полей, представ
ляемые ячейками сетки.
Сетка — это наиболее мощный и гибкий элемент визуализации и управления данны
ми, предоставляющий широкие возможности отображать и модифицировать их. Больша
часть этих функциональных возможностей заложена в свойстве-коллекции Columns, по
этому изучение сетки логично начать именно с этого свойства.

Свойство Columns. Объекты TColumn


Свойство Columns имеет тип TDBGridColumns и является индексированной коллек
цией объектов TColumn. Объекты типа TColumn представляют отдельный столбец сетки
включая его заголовок. Свойство Columns можно использовать в режиме выполнения про-
граммы для управления коллекцией объектов TColumn, а также для доступа к отдельным
колонкам сетки.
В режиме конструктора столбцы сетки (набор объектов TColumn) можно настро-
ить при помощи редактора столбцов (Column Editor). Отобразить редактор столбцов
на экране можно, щелкнув мышкой на кнопке построителя свойства Columns (см. рис.
3.13). Почти всю рабочую область окна редактора столбцов занимает список объектов
TColumn, однако при первом вызове редактора этот список будет пуст. Следует заметить,
что между редактором столбцов и редактором полей существует определенная аналогия.
В частности, если список объектов TColumn пуст, BDE создаст динамический набор объ-
ектов-столбцов на основании всех полей набора данных, и именно этот набор столбцов
и будет отображаться в сетке.
При помощи редактора столбцов можно создать подходящий набор объектов TColumn,
которые будут отображать в сетке информацию только из выбранных полей набора дан-
ных. Чтобы создать новый объект TColumn, можно или нажать клавишу Ins на клавиатуре,
или кнопку Add New (крайняя слева на панели инструментов редактора столбцов), или
выбрать пункт Add контекстного меню. В результате в рабочей области окна редактора
столбцов появится строка наподобие следующей: 0 — TColumn. В этой строке 0 (или 1, 2,
Глава 3. Использование механизма ВОЕ (продолжение) 137
3 и т.д.) — это индекс данного объекта TColumn в коллекции TDBGridColumns. Для даль-
нейшей настройки объекта-столбца нужно воспользоваться инспектором объектов.

0 - Cetegoiy
1 -Item
"

Рис. 3.13. Редактор столбцов содержит определение трех объектов TColumn


В первую очередь следует указать наименование поля, которое будет источником дан-
ных для объекта-столбца. Это можно сделать, выбрав имя поля из списка доступных по-
лей набора данных, прикрепленного к свойству FieldName. Тогда в рабочей области окна
редактора столбцов вместо надписи TColumn появится наименование выбранного поля.
Имя поля будет использоваться и в качестве заголовка столбца в сетке. В принципе, на
этом можно было бы закончить настройку объекта-столбца. Добавив в коллекцию нужное
количество объектов TColumn и настроив каждый из них на определенное поле набора
данных, можно добиться того, что сетка будет отображать только нужные поля и в нужной
последовательности.
Дальнейшая настройка обычно заключается в задании различных свойств отображе-
ния в сетке самого столбца и его заголовка. Цвет фона столбца можно задать при помощи
свойства Color, а параметры шрифта данных — при помощи свойства Font. При помощи
свойства Alignment можно задать выравнивание данных в пределах ячейки (влево, вправо
или по центру).
Для настройки свойств заголовка столбца служит свойство Title объекта TColumn.
Свойство-объект Title имеет тип TColumnTitle. Развернув это свойство в инспекторе объектов,
можно получить доступ к основным свойствам заголовка. Текст заголовка по умолчанию
совпадает с именем поля, данные из которого представляет столбец в сетке. Изменить текст
подписи можно при помощи свойства Caption. Остальные свойства объекта Title позволяют
задать выравнивание подписи, цвет фона и параметры текста. На рис. 3.13 изображен редак-
тор столбцов, содержащий определения трех объектов TColumn. Для заголовков объектов-
столбцов заданы подходящие тексты подписей, а также цвет и выравнивание по центру. Как
это выглядит в сетке в режиме выполнения приложения, изображено на рис. 3.14.

Кит» гори | Товар l.[«nn j *j


Samsung ML-ЧШ 18900 — ^
Samsung ML-121Q 20300 .
Epson EPL-5900L 239 00
^ MH •Ш | Салоп LBP-81 0 215QO
Brother HL-1240 Mono Loser 26БОО
Epson AcuLaser C1 000 1.34900
Epson AcuLaser C2QOO 2,15500
HP LaserJet 100Qw 22-1 OOt =
HP LaserJet! 200 332.00*
HP Laser Jen 220 44000* :

1 = HP LaserJet 3200 637 00| • •

Рис. 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 строк.

Категорий .Товар I ц*на.,Ы


»> 1 V Samsung ML-450G 10900$ — 'I
ИИИИ1 * Samsung ML-1 21 0 203.001
Epson EPL-590QL 239.001
ft Canon LBM10 215001 ,1

Рис. 3.15. Если свойство ButtonStyle имеет значение cbsAuto


и свойство PickList содержит список значений подстановки,
то ячейка столбца работает как комбинированный список

I Кнп« прим 1 tump I .№».« |£|


[принтеры Samsung ML-450Q
[(Принтеры Samsung ML,-1210 203 DOt -
^Принтеры •» Epson EPL-5900L 239 JO
ЙЯЕЯЯЯЯИС* Салоп |_8Р-в10 215.00
Brother НЫ240 Mono Laser 266 00
_ Процессоры Epson Aculeser Cl 000 i 1.3-19 00 :

Модули пвм«" Epson AcuLaser C2000 2.156.00 I-


Виаеокарты
221 00
- Мониторы ^_
\ [рнитвры '' — * HP LaserJet 1200 332.00 ;.

Рис. 3.16. Если в качестве источника значений


для столбца используется поле подстановки (lookup field),
для ячейки автоматически применяется стиль комбинированного списка
Если в качестве источника значений для столбца используется поле подстановки (look-
up field), определенное в наборе данных (постоянное поле), то ячейки столбца также будут
работать как комбинированный список, даже если список значений свойства PickList пуст.
В приложении, изображенном на рис. 3.14, источником значений для столбца Категория
служит поле Category, имеющее тип длинное целое (long integer). В предыдущей главе
приводился пример создания на основе поля Category поля подстановки CategoryName.
Если для первого столбца вместо поля Category в качестве значения свойства FieldName
указать поле подстановки CategoryName и очистить список свойства PickList, то из спи-
ска подстановки в ячейке можно будет выбирать не абстрактные числа, а наименования
категорий (см. рис. 3.16).
Глава 3. Использование механизма ВОЕ (продолжение) 139

Значение cbsNone свойства ButtonStyle предполагает, что ячейка в любом случае будет
вести себя как обычный элемент редактирования.
Если для свойства ButtonStyle задать значение cbsEllipsis, то при переходе в режим ре-
дактирования, ячейки соответствующего столбца будут снабжаться кнопкой построителя
(кнопка с троеточием —Ellipsis). При нажатии этой кнопки (см. рис. 3.17) будет генериро-
ваться событие OnEditButtonClick компонента TDBGrid. Кнопкой построителя и событи-
ем OnEditButtonClick удобно пользоваться, чтобы вызвать на экран диалоговое окно, при
помощи которого, например, можно не только выбрать нужное значение, но и добавить
к списку новое (предварительно добавив новую запись в соответствующую таблицу).

Категория Товар j Ценп |*j


>1 Samsung ML-45M las.ous1^
1 Samsung Mi_-1 21 0 "03.00$ .
1 Epson EPL-59GQL 239 00*
1 Canon LBP-810 21500$ ,
----- --•• :.. -^

Рис. 3.17. Если для свойства ButtonStyle задать значение cbsEllipsis,


ячейки столбца будут снабжаться кнопкой построителя (Ellipsis)
Поясним сказанное на примере. Обратившись к форме, изображенной на рис. 3,16, мож-
но сделать следующие выводы. Форма предназначена для отображения значений трех полей
таблицы Items, причем вместо значений поля Category отображаются значения из сформи-
рованного на его основе поля подстановки (поля Category таблицы Category). Кроме того,
форма предоставляет возможность модифицировать значение поля Category, выбрав его из
списка, прикрепленного к ячейке. При выборе из списка имени категории соответствующее
значение первичного ключа присваивается полю Category текущей записи.
Все это работает хорошо до тех пор, пока не понадобится ввести новую категорию,
информация о которой отсутствует в таблице. В этом случае удобнее пользоваться кноп-
кой построителя; при нажатии на нее на экране отображается форма, предоставляющая
интерфейс для управления содержимым таблицы Category.
Для иллюстрации сказанного создадим приложение-пример. Откройте новое приложе-
ние и создайте модуль данных (File \ New \ Data Module). Для свойства Name модуля дан-
ных задайте значение DModl, а в сам модуль поместите два объекта ТТаЫе и два объекта
TDataSource, оставив их имена по умолчанию. Объект Tablel подключите к таблице Items
базы данных MyData, а объект Tablel — к таблице Category той же базы. В качестве зна-
чения свойства DataSet объекта DataSourcel укажите Tablel, а для свойства DataSet объек-
та DataSource2 задайте значение ТаЫе2.
Далее, дважды щелкнув на изображении объекта ТаЫе2, добавьте в список постоянных
полей все поля из таблицы Category. Точно так же добавьте в список постоянных полей
объекта Tablel все поля таблицы Items. В заключение нужно создать поле подстановки
(lookupfield), основанное на поле Category. Для этого, дважды щелкнув на значке объекта
Tablel, отобразите на экране редактор полей и выберите из контекстного меню пункт New
Field. В диалоговом окне определения нового поля задайте следующие значения:
Q Name — CategoryName.
U Type — String.
Q Size —25.
Q Field Type — Lookup.
Q Key Fields — Category.
140 Borland C++ Builder 6. Разработка приложений баз данных

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

Свойство SelectedField содержит указатель на объект TField, соответствующий выде-


ленной в сетке ячейке. Ниже приведен код обработчика события OnEditButtonClick объ-
екта DBGridl формы Form/:
void _ fastcall TForml : : DBGridlEdi tButtonClick(TObject *Sender)
{
TField* SelF;
SelF=DBGridl->SelectedField;
if (SelF)
if (SelF->FieldName=="CategoryName")
{
TForm2* Form2 = new TForm2(Application) ; ,
Form2->ShowModal() ;
Form2->Free() ;

В первой строке кода объявляется переменная 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);

В этом обработчике события OnCreate снова используется свойство SelectedField.


С его помощью переменной ValString присваивается значение ячейки сетки DBGridl,
142 Borland C++ Builder 6. Разработка приложений баз данных

выделенной в форме 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

Рис. 3.18. Используя кнопку построителя, можно организовать достаточно сложный


интерфейс для ввода значений в ячейку сетки (компонент TDBGrid)
Излишне напоминать, что всю настройку объектов TColumn, проводимую в режиме
конструктора, с тем же успехом можно производить и в режиме выполнения приложения.
Для доступа к столбцам сетки в режиме выполнения приложения предназначено индек-
сированное свойство-коллекция Columns типа TDBGridColumns. Как и любая коллекция,
класс TDBGridColumns обладает двумя свойствами, позволяющими осуществлять итера-
цию по всем элементам коллекции: Items (индексированный массив элементов) и Count
(количество элементов в коллекции). Ниже приведен отрывок кода, демонстрирующий
принципы использования этих свойств. В этом отрывке осуществляется итерация по всем
колонкам сетки DBGridl и для каждой колонки в список ListBoxl добавляется наименова-
ние поля, ассоциированного с ней.
for(int i=8; i <DBGridl->Columns->Count ;

Li stBoxl->I terns- >Add(DBGridl->Columns->I terns [i] ->FieldName) ;

Класс TDBGridColumns имеет стандартный для классов-коллекций набор методов, по-


зволяющих управлять составом коллекции. Метод Add создает новый объект TColumn, до-
бавляет его в коллекцию и возвращает указатель на него. Используя этот указатель, мож-
но настроить новый объект-столбец нужным образом. При помощи метода Insert можно
создать новый объект TColumn и вставить его в коллекцию столбцов в позицию, заданную
индексом. Как и в случае метода Add, метод Insert возвращает указатель на новый объект
TColumn. Метод Delete позволяет удалить элемент коллекции, заданный передаваемым
в качестве аргумента индексом, а при помощи метода Clear можно удалить все объекты
TColumn сразу, очистив тем самым коллекцию TDBGridColumns.
Определения всех объектов TColumn можно сохранить в файле. Это можно сделать
при помощи метода SaveToFile. Метод принимает единственный аргумент — имя файла,
в котором будет сохранена информация из коллекции Columns. Обратную задачу решает
метод LoadFromFile: с его помощью можно из файла загрузить в объект TDBGridColumns
данные, предварительно сохраненные при помощи метода SaveToFile. Используя эту пару
методов, можно один и тот же объект TDBGrid подключать к разным источникам данных,
по мере необходимости подгружая определения нужных столбцов. Соответствующая пара
методов чтения из потока и записи в поток — LoadFromStream и SaveToStream.
Класс TDBGridColumns имеет еще два интересных метода, позволяющих воздей-
ствовать на набор столбцов целиком. Вызов метода RestoreDefaults восстанавливает для
свойств всех столбцов, входящих в коллекцию, их значения по умолчанию. Тем самым
144 Borland C++ Builder 6. Разработка приложений баз данных

отменяются все изменения настройки набора столбцов. Метод RebuildColumns удаляе


определения всех столбцов из коллекции и генерирует новые объекты TColumn, основы
ваясь на ассоциированном с сеткой наборе данных.

Параметры сетки
Еще одно важное интегральное свойство компонента 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

Понять смысл использования каждого из описанных выше элементов проще всего


на примере. Создайте новое приложение и расположите в верхней части пустой формы
панель (компонент ТРапеГ), а на ней — тринадцать флажков (TCheckBox) в три ряда.
Каждый из этих флажков будет представлять один из элементов параметров сетки, поэто-
му в качестве подписи флажков следует последовательно задать наименования элементов,
предварительно удалив префикс dg (имеется в виду свойство Options). Последовательно
задайте для свойства Tag флажков значения от 1 до 13. Далее следует очистить свойство
Caption объекта TPanel. Можно также задать для свойств Bevellnner и BevelOuter панели
значения bvLowered и bvRaised соответственно.
Ниже панели поместите компоненты TDBGrid, TTable и TDataSource. Объект DBGridl
подключите к DataSourcel, а объект DataSourcel — к объекту Table 1. Набор данных Table!
подключите к таблице Items базы данных MyData и активизируйте его. Подровняйте все
элементы управления так, чтобы форма выглядела более-менее прилично.
Чтобы было удобнее пользоваться набором флажков, нам понадобится массив
элементов типа TCheckBox*. Кроме того, в дальнейшем нам будет нужна переменная
IsFormCreate логического типа, принимающая значение true после того, как форма будет
создана. Перейдите на вкладку заголовочного файла модуля формы (Unit.h) в редакторе
кода и введите следующие объявления в разделе private класса TForml:
bool IsFormCreate;
TCheckBox* CBOpt[13] ;
Сразу после отображения главной формы приложения на экране группа флажков
должна в точности отражать состояние набора параметров сетки. Для этого в обрабо-
тчике события OnCreate формы свойству Checked каждого из флажков нужно присвоить
значение true, если соответствующий элемент присутствует в наборе параметров сетки,
и false — в противоположном случае. Однако прежде нужно инициализировать массив
CBOpt, объявленный в классе TForml. Для этого можно воспользоваться итерацией по
всем элементам управления формы. Каждый элемент управления проверяется на принад-
лежность к типу TCheckBox*, и в случае успеха соответствующий указатель присваива-
ется очередному элементу массива CBOpt. Ниже приведен отрывок кода, выполняющий
описанные действия:
i n t j=0;
for(int i=Q; IComponentCount ; i++)
{
TCheckBox* CBox=dynamic_cast<TCheckBox*> (Components [i ] ) ;
if (CBox)
{
CBOpt [j]=CBox;

Теперь несложно организовать инициализацию флажков значениями, отражающими


состояние набора параметров сетки по умолчанию. Ниже приведен соответствующий от-
рывок кода:
for(int i=8; i<13; -i++) CBOpt [i] ->Checked=
DBGridl->Options.Contains((TDBGridOption) (CBOpt [i] -
146 Borland C++ Builder 6. Разработка приложений баз данных

Сначала в этом отрывке кода в теле цикла при помощи свойства 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

#include <Classes .hpp>


#include «Controls .hpp>
#include <5tdCtrls.hpp>
#include <Forms.hpp>
«include <DB.hpp>
#include <DBGrids .hpp>
«include <DBTables.hpp>
«include <ExtCtrls.hpp>
«include <Grids.hpp>
// ------- ............ - ..... ---------------------
class TForml : public TForm
{
_ published: // IDE-managed Components
TDBGrid «DBGridl;
liable "Tablel;
TDataSource *DataSourcel;
TPanel *Panell;
TCheckBox *CBEditing;
TCheckBox *CBAlwaysShowEditor ;
TCheckBox *CBTitles;
TCheckBox *CBIndicator ;
TCheckBox *CBColumnResize;
TCheckBox *CBColumnLines;
TCheckBox *CBRowLines;
TCheckBox *CBTabs;
TCheckBox *CBRowSelect ;
TCheckBox "CBAlwaysShowSelection;
TCheckBox "CBConfirmDelete;
TCheckBox *CBCancelOnExit ;
TCheckBox "CBMultiSelect ;
void _ fastcall FormCreate(TObject *Sender) ;
void _ fastcall CBEditingClick(TObject *Sender)
private: // User declarations
bool IsFormCreate ;
TCheckBox* CBOpt[l*] ;
public: // User declarations
_ fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


// ---- ........ ------ ........
#endif
Листинг З.4. Главный модуль приложения GridOptionsDemo

«include <vcl .h>


«pragma hdrstop
«include "Unitl.h"
148 Borland C++ Builder 6. Разработка приложений баз данных
//
#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
// __________________ _
_ fastcall TForml : :TForml (TComponent* Owner)
: TForm(Owner)

II ___________________________________
void _ fastcall TForml: : FormCreate(TObject *Sender)
{
int j=0;
IsFormCreate=false;

for(int i=0; iComponentCount ; i

TCheckBox* CBox=dynamic_cast<TCheckBox*> (Components [i] ) ;


if (CBox)
{
CBOpt[j]=CBox;

•for(int i=0; i!3;


CBOpt[i]->Checked=
DBGridl->Options . Contains (
(TDBGridOption) (CBOpt [i] ->Tag-l) ) ;

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

Г t^limin liriss Г Row Select K? Cancel On b'xii


. Р Titles

• P indicator F RowLmee Г Always Snow Selection Г Mute Select

IternNo tern. . CetegoQ Descriptor!1 ' -*•)


> 1 Samsung ML-45GO A4. GDI. 03V 2 Мб. 600 dpi до 8 рргт— *
2 Samsung ML-1 210 A4. GDI, 03V 8 Мб, 600 dpi, до 1 2 pp- ;
3 Epson EPL-590QL A GDI, ОЗУ 2(13) Mfj.6DQdpi.no 1J -
A Canon LBP-81 0 A GDI 600 dpi, по В ppm, лоток It
5 Brother HL-1 240 Mono lasei A .03Y2M6,6QQ*6DOapi.ao12pF>r
6 Epson AcuLeseiCI 000 А цвет. ОЗУ 18(256) Мб. BOO dpi/2- j
'? Epson AcuLoser C2000 A . цвет, ОЗУ 64(5! 2) Mb, 600 dpi/2-т]

iU

Рис. 3.19. Приложение GridOptionsDemo после того, как был снят флажок Column Lines

Приемы использования событий сетки


С компонентом TDBGrid связан ряд интересных событий, продуманная обработка ко-
торых поможет придать приложению профессиональный вид. Событие OnEditButtonClick,
наступающее при щелчке мышкой на кнопке построителя в ячейке, уже описывалось ра-
нее, когда речь шла о свойстве Columns сетки. Обзор событий начнем с OnTitleClick.
Событие OnTitleClick происходит при щелчке мышкой на одном из заголовков столбцов
сетки непосредственно после того, как пользователь отпускает левую клавишу. Объявлено
оно следующим образом:
typedef void fastcall ( closure *TDBGridClickEvent)(TColumn* Column);
В обработчик события OnTitleClick передается аргумент типа TColumn, при помощи
которого можно определить, на заголовке какого из столбцов сделан щелчок. Кроме то-
го, с помощью аргумента Column можно проделать необходимые действия или с самим
столбцом, или с его заголовком.
Самый яркий пример использования события OnTitleClick — изменение порядка сор-
тировки записей в ответ на щелчок мышкой на заголовке столбца. Можно назвать много
приложений, использующих подобную практику (в частности, широко известный Windows
Commander и многие другие файл-менеджеры). При щелчке на заголовке столбца, записи
в нем сортируются по значениям. Повторный щелчок на заголовке того же столбца пере-
ключает порядок сортировки на обратный.

Пример использования события OnTitleClick


В качестве примера использования события OnTitleClick можно создать приложение,
реализующее описанную выше технологию. Итак, создайте новое приложение; на форме
разместите сетку (TDBGridl), а также компоненты TDataSource и TQuery. Оставьте для
всех элементов имена, присвоенные им по умочанию. Подключите сетку к источнику
данных DataSourcel, который, в свою очередь, подключите к набору данных Query].
Свойство DatabaseName объекта Query 1 установите в MyData.
То, что в качестве набора данных используется компонент TQuery, а не TTable, можно
объяснить следующим образом. Для сортировки записей в объекте ТТаЫе применяются
индексы. Нужный порядок сортировки можно установить, задав соответствующий ин-
декс при помощи свойства IndexName или IndexFieldNames. Если подходящего индекса
150 Borland C++ Builder 6. Разработка приложений баз данных

нет, его придется создать при помощи метода 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

Queryl->SQL->Add(SQLString + " ; " ) ;


Queryl->Acti ve=true;
TColumnTitle* Ttl=DBGr idl->Columns->I terns [0] ->Title;
Ansi String TtlCaption=Ttl->Caption;
Ttl->Caption="> " + TtlCaption;
}
В первой строке обработчика инициализируются переменные SQLString и Curlndex.
Строке SQLString присваивается текст оператора запроса, возвращающего все поля всех
записей таблицы Items. Так как в этом операторе никакая другая сортировка не задана,
то записи будут отсортированы по первичному индексу (поле ItemNo) по возрастанию.
(Подробнее о языке запросов SQL — в одном из следующих разделов.) Переменная
Curlndex получает значение 0. Это подразумевает, что первоначально активным столбцом
сортировки будет нулевой столбец сетки, т.е. столбец ItemNo.
В следующих двух строках иода обработчика настраивается свойство SQL объекта Query 1.
Это свойство имеет тип TString, поэтому используются стандартные методы этого класса.
Метод Clear очищает список строк свойства SQL, а метод Add добавляет к этому списку стро-
ку оператора SQL и в конец этой строки — символ «;». Этим символом по соглашению должен
заканчиваться любой оператор SQL. Затем набор данных Query 1 активизируется.
И в заключение кода обработчика события OnCreate задается новая подпись для
первого столбца сетки (точнее, нулевого). Для этого сначала извлекается подпись заго-
ловка столбца ItemNo, затем она объединяется со строкой «> » (так как первоначально со-
ртировка идет по возрастанию), и затем полученная таким образом строка присваивается
свойству Caption заголовка того же столбца. Пробел, следующий за символом «>», нужен
для большей наглядности.
После того как приложение загрузится в память и на экране отобразится его главная
форма, можно щелкать мышкой на любом из заголовков столбцов, задавая нужный по-
рядок сортировки записей. Это обеспечивается обработчиком события OnTitleClick. Ниже
приведен его код.
void _ fastcall TForml: :DBGridlTi tleClick(TColumn «Column)
{
char fc=*(Column->Title->Caption.c_str()) ;
Ansi String ColField=Column->FieldName;
int ind=Column->Index;
AnsiString OrderByString;
AnsiString CString;
i f ( f c ==

OrderByString=" ORDER BY " + ColField + " DESC;


CString=AnsiString(" ") + ColField;

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.

ftemNo • Kern JGa;sgorvJDescnpnon ' -!


j НгТ^ДУ^1лЩ|||рЩН||~ 4200 rpm, UDMA 100, 2.5" (дг™^
"3" (1 1 6") LG StudioWorks 563N 0 28 LR-NI. 1 024x763 (61 H:),
185 5" (1 3.8") LG SUidioWorks 575E 0.28 LR-ГЛ
5" Samsung 15- В LR-NI. Pivot Block TFT LCD:'
', ' 5"Samsungl51BM Pivot LR-NI. TFT LCD актив
218 5" Samsung 1 51 S.297 Pivot TFT LCD эктивнач м
1Э1 5" Samsung 550B ;0.24LR-Ni, 1280»:1024(60Нг) - '
90 5"SamsungS51S 0.24 LR-NI. 1 024x768 (68 Нг),
169 6" Sarntton Ь6Е LR-NI. 1024x768 (60 Нг). MPF
221 5" Sony SDM-N50 LCD -0297 LR-NI. TFT LCD а*.тив. . ,
:

7" (16'T Samsung 753DFX, ; 0.2C LR-NI. OSD digital cortrc


-••', 7" (16'T Samsung 753S 7 0 23 LR-NI. OSD digital conirc ^ j

iU Jj

Рис. 3.20. Набор данных отсортирован по полю Item в порядке возрастания


И последний важный момент. Если сортировка установлена для нового столбца, подпись
заголовка предыдущего столбца нужно восстановить в ее значение по умолчанию. Для про-
верки факта смены столбца сортировки используются переменные ind и Curlndex.
В заключение указатель текущей записи переводится на первую запись набора данных,
а курсор ввода — на ячейку отсортированного столбца. Откомпилируйте приложение
и запустите его на выполнение. На рис. 3.20 изображено тестовое приложение в момент,
когда набор данных отсортирован по полю Item в порядке возрастания.

Оформление отдельных ячеек сетки


при помощи события OnDrawColumnCell
Компонент TDBGrid имеет достаточно удобное событие OnDrawColumnCell, при помощи
которого можно управлять рисованием отдельных ячеек (или строк) сетки при выполнении
приложения. Событие OnDrawColumnCell происходит всякий раз, когда необходимо нарисо-
вать или перерисовать одну или несколько ячеек сетки. В частности, это событие происходит
при отображении на экране формы, содержащей сетку, для всех видимых ячеек. Кроме того,
оно может произойти при прокрутке сетки, при перемещении курсора ввода с одной ячейки на
другую, а также после того как перекрывающий одну или несколько ячеек объект был убран.
Обработчик этого события объявлен следующим образом:
typedef void fastcall ( closure *TDrawColumnCellEvent)
(System::TObject* Sender, const Types::TRect &Rect, int DataCol,
TColumn* Column, Grids::TGridDraw5tate State);
Первый аргумент — Sender, — передаваемый в тело обработчика, имеет тип указателя
на объект TDBGrid, для которого произошло событие. Прежде чем применить перемен-
ную Sender в этом качестве, необходимо воспользоваться динамическим приведением ее
к типу TDBGrid:
TDBGrid* grid=dynamic_cast<TDBGrid*>(Sender);
Следующий аргумент — переменная Rect типа TRect -указывает положение прямоу-
гольника, занимаемого текущей ячейкой, на канве сетки. Используя эту переменную, мож-
но организовать самостоятельный вывод в ячейку текстовой и графической информации,
а также задать для нее любое оформление.
154 Borland C++ Builder 6. Разработка приложений баз данных

Аргумент целого типа DataCol содержит индекс столбца, которому принадлежа


текущая ячейка, в коллекции Columns. Наличие этого аргумента не совсем понятно, та
как информацию об индексе столбца можно почерпнуть из свойства Index следующей
аргумента — переменной Column типа TColumn*. Переменная Column ссылается на o6i
ект-столбец, содержащий текущую ячейку.
Последний аргумент обработчика события OnDrawCoIumnCell, — State —- имеет ти
множества TGridDrawState и может содержать дополнительную информацию о состояни
текущей ячейки. Переменные этого типа могут содержать один из следующих элементе!
Q gdSelected—текущая ячейка выбрана. Определив это, вы можете оформить выделени
ячейки по-своему.
G gdFocused— текущая ячейка имеет фокус ввода. Отображение фокуса ввода можн<
дополнить каким-нибудь нестандартным образом.
U gdFixed— текущая ячейка находится в фиксированной области сетки.
Замечание
Компонент TDBGrid имеет еще одно похожее событие - OnDrawDataCe//. Оно проис-
ходит, если свойство State коллекции Columns имеет значение csDefault перед событием
OnDrawCoIumnCell. Однако это событие считается устаревшим, и пользоваться им не ре-
комендуется. Оно оставлено только для обратной совместимости.

В качестве примера использования события OnDrawCoIumnCell создадим тестовое


приложение, отображающее в сетке все столбцы и строки таблицы Items, окрашивая при
этом фон строк в разные цвета через одну (так называемая зебра). Такой прием часто ис-
пользуется для лучшего визуального восприятия и разделения строк. Как всегда создайте
новое приложение и расположите на его главной форме сетку (TDBGrid), а также объекты
TTable и TDataSource. Для всех объектов оставьте их имена по умолчанию. Сетку подклю-
чите к Tablel (посредством объекта DataSourcel), а объект Tablel — к таблице Items базы
данных MyData и откройте набор данных.
Создайте обработчик события OnDrawCoIumnCell для сетки и введите в Него следую-
щий код:
void fastcall TForml::DBGridlDrawColumnCell(TObject *Sender,
const TRect &Rect, int DataCol, TColumn *Column,
TGridDrawState State)
{
static TColor ColCol=clSkyBlue;
if(DataCol == 0)
{
if(ColCol == clSkyBlue) ColCol=clMoneyGreen;
else ColCol=clSkyBlue;
}
DBGridl->Canvas->Brush->Color=ColCol;
DBGridl->Canvas->FillRect(Rect);
DBGridl->DefaultDrawColumnCell(Rect,DataCol,Column,State);
}
В первой строке кода обработчика объявляется статическая переменная Со/Со/, име-
ющая тип TColor. Статичность переменной ColCol важна, так как ее значение не должно
Глава 3. Использование механизма ВОЕ (продолжение) 155
изменяться между вызовами обработчика события. Инициализируется эта переменная
значением цвета clSkyBlue.
Цвет строки в сетке должен меняться перед тем, как будет отрисовываться ее первая
ячейка. Это легко определить при помощи аргумента DataCol: если он равен 0, значит,
в данный момент отрисовывается первая ячейка строки. В этом случае нужно поменять
цвет clSkyBlue на цвет clMoneyGreen (или наоборот). (Пару цветов можно подбирать или
на свой вкус, или на вкус заказчика.)
Далее, полученный цвет устанавливается в качестве цвета кисти для канвы сетки.
Именно этим цветом в следующей строке кода заполняется прямоугольник Rect (иначе
говоря, отрисовывается фон текущей ячейки). Делается это при помощи метода FillRect
канвы сетки. И в последней строке обработчика события OnDrawColumnCell вызывается
метод сетки DefaultDrawColumnCell, в функции которого входит отрисовка текста в ячей-
ке. Если вы не организовали вывод текста собственными силами, нужно обязательно вы-
звать этот метод в конце кода обработчика события OnDrawColumnCell, иначе все ячейки
будут пустыми.
Замечание
На последовательность вывода текста в ячейку влияет значение логического свойства
DefaultDrawing. Если это свойство для сетки имеет значение true (так по умолчанию),
отрисовка текста в ячейке будет осуществляться стандартными средствами до вызова
обработчика события OnDrawColumnCell. Таким образом, в приведенном выше примере
текст в ячейках будет вначале отрисован черным цветом по белому фону. Затем в обра-
ботчике события OnDrawColumnCell ячейка будет окрашена заданным цветом и текст бу-
дет отрисован снова методом DefaultDrawColumnCell. Если для свойства DefaultDrawing
задать значение false, то вывод текста нужно организовать в обработчике события
OnDrawColumnCell самостоятельно (например, вызвав метод DefaultDrawColumnCell).

Откомпилируйте приложение и запустите его на выполнение. Примерный результат


изображен на рис. 3.21.

йЭ
Vw^nivjIJ^t'ipUii,-

MGBI.U <'?s,ir, миф n


2 Samsung MH21Q At 001. ОЗУ В Мб, 600 dpi. д

М GPL 600 dpi до 8 ppm. лот


AiG3V2M6.68CxS3Cdpi.fi03

Рис. 3.21. Объект TDBGrid с попеременным окрашиванием строк (зебра)

|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. Если менять цвет ячеек, привязываясь


только к смене строки данных при отображении, возникнут проблемы при прокрутке
На первый взгляд все кажется нормальным, однако, если прокрутить содержимое сет-
ки в горизонтальном или вертикальном направлении, получится довольно неприглядная
156 Borland C++ Builder 6. Разработка приложений баз данных

картина (см. рис. 3.22). Этот сбой вызван тем, что при отрисовке новых ячеек изменен!
цвета или происходит неправильно, или не происходит совсем. Избежать этого можн
если менять окраску ячеек в зависимости от конкретной записи. Например, можно пров
рять значение ключевого поля ИетМои, в зависимости от его четности, использовать оди
из двух цветов. Но если удалить из таблицы несколько записей или применить к таблиг
фильтр, может случиться, что несколько подряд идущих строк будут окрашены одинаю
во. Тогда можно воспользоваться свойством RecNo компонента ТТаЫе. Это свойство С(
держит номер текущей записи. Ниже приведен исправленный вариант кода обработчик
события OnDrawColumnCell.
void fastcall TForml::DBGridlDrawColumnCell(TObject *Sender,
const TRect &Rect, int DataCol, TColumn *Column,
TGridDrawState State)

int ind=Tablel->RecNo & 1;


DBGridl->Canvas->Brush->Color=ColColfind];
DBGridl->Canvas->FillRect(Rect);
DBGridl->DefaultDrawColumnCell(Rect,DataCol,Column,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

Рис. 3.23. В исправленной версии приложения цвет ячеек сетки выбирается


в зависимости от номера отображаемой записи

Замечание
К сожалению, свойство RecNo можно применять для баз данных не всех типов. Для
баз данных в формате SQL свойство RecNo всегда содержит значение -1, независимо
от номера текущей записи. Чтобы воспользоваться этим свойством, необходимо пере-
определить методы GetRecNo и SetRecNo, применяемые для чтения и записи значения
свойства RecNo класса ТТаЬ/е.

Еще несколько аспектов использования события OnDrawColumnCell продемонстриру-


ем на следующем примере. Создадим тестовое приложение, в котором в сетке (компонент
Глава 3. Использование механизма ВОЕ (продолжение) 157

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. Разработка приложений баз данных

В начале обработчика события OnDrawColumnCell переменной Color типа TCol


присваивается значение переменной FDefaultColor. Далее, если отрисовывается ячей]
столбца Qty, надлежащим образом настраиваются переменные Color и Style (если това]
на складе меньше 11, его количество отображается полужирным шрифтом). Наконе
г
значения переменных Color и Style присваиваются соответствующим свойствам шриф
канвы сеткя и вызывается метод DefaultDrawColumnCell, отрисовывающий текст ячейк;
Осталось упомянуть только об одном моменте, а именно об операторе
if(State.Contains(gdSelected)) Color=clWhite;
Если его опустить, в выделенной ячейке сетки не будет отображаться текст. Труди
объяснить, почему так происходит в данной ситуации, но избежать этого можно имени
при помощи вышеприведенного оператора. Если ячейка выделена, т. е. аргумент-множе
ство State содержит элемент gdSelected, то для текста задается белый цвет. Главная форм
приложения изображена на рис. 3.24. Попробуйте закомментировать строку, приведенну!
выше, и посмотрите, что из этого выйдет.

SlofetenijgemName |OV [*]

<*ш
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 т

Рис. 3.24. Ячейка Qty сетки заполняется текстом,


цвет которого зависит от текущего значения поля Qty
В заключение обзора возможностей компонента TDBGrid следует упомянуть о еще
нескольких интересных его событиях. Событие OnColumnMoved происходит при пере-
мещении столбца сетки, но при условии, что в состав параметров сетки включена опция
dgColumnResize. Кроме стандартного аргумента Sender в тело обработчика события пере-
даются два аргумента целого типа. Аргумент Fromlndex содержит первоначальный индекс
столбца в коллекции Columns, а аргумент Tolndex — индекс, который получит столбец
в результате перемещения.
События OnColEnter и OnColExit происходят при перемещении фокуса ввода из
одного столбца в другой. Сначала возникает событие OnColExit, вслед за ним — событие
OnColEnter. Оба имеют стандартный тип TNotijyEvent. Парой этих событий можно вос-
пользоваться, например, чтобы сменить оформление активного столбца (цвет фона и/или
текста ячеек столбца или его заголовка) и для других подобных целей. Еще один вариант
их применения — организация фиксированных (непрокручиваемых) столбцов при пере-
мещении фокуса ввода с помощью клавиш управления курсором.
Последнее событие компонента TDBGrid, о котором хотелось бы упомянуть, —
OnCellClick — присходит при щелчке левой клавишей мышки на ячейке, не содержащей
фокус ввода. Точнее, событие происходит в момент, когда пользователь отпускает левую
клавишу мышки. Если щелчок был произведен на выбранной ячейке, событие не про-
изойдет. Скорее всего, при этом ячейка (сетка) перейдет в режим редактирования (это
зависит от настройки сетки). Обработчик события OnCellClick получает единственный
Глава 3. Использование механизма ВОЕ (продолжение) 159

аргумент — Column типа TColumn. Аргумент Column ссылается на столбец, содержащий


ячейку, на которой был произведен щелчок.
К сожалению, рамки книги не позволяют описать здесь методы применения этих инте-
ресных событий. Скажу только, что, используя их в сочетании с некоторыми событиями
набора данных, можно получить достаточно любопытные эффекты в поведении сетки.

Запросы и компонент TQuery


Компонент TQuery — пожалуй, самый важный наследник класса TDataSet. TQuery
представляет собой программную оболочку вокруг одиночного запроса к базе данных.
Запрос к базе данных — это изложенное на языке SQL требование выполнить определен-
ные действия с данными или метаданными. К числу метаданных относятся описания таб-
лиц, индексов, запросов и других объектов, определяющие структуру базы данных. При
помощи компонента TQuery можно получать доступ или манипулировать данными одной
или нескольких таблиц, расположенных в базах данных формата SQL (серверы баз данных
InterBase, DB2, Oracle, Informix, MS SQL Server, Sybase), в локальных базах данных (MS
Access, Paradox, dBASE, FoxPro) и базах данных ODBC.
Использование объектов TQuery в подавляющем большинстве случаев более оправдан-
но, чем применение наборов данных ТТаЫе. Связано это с большой гибкостью и эффектив-
ностью языка запросов SQL, полагаемых в основу TQuery. Преимущества использования
компонента TQuery связаны с такими факторами, как:
Q возможность объединить несколько таблиц связями, не предусмотренными при проек-
тировании базы данных;
Q возможность в одном наборе данных сочетать информацию не только из разных таб-
лиц одной базы данных, но и из разных баз данных, в том числе баз данных разных
форматов;
О возможность легко задать фильтры любого уровня сложности, порядок сортировки за-
писей, набор полей, которые должны попасть в итоговую выборку, и т.д.;
Q возможность использовать параметры;
[Л простота создания вычисляемых и подстановочных полей, а также различных итого-
вых полей;
Q достаточная универсальность языка запросов SQL, позволяющая с минимальными по-
терями переходить от одного формата базы данных к другому;
G возможность создавать легко масштабируемые приложения.
Так как в основе компонента TQuery лежит строка оператора SQL, вполне естественно
начать изучение этого компонента с краткого обзора языка SQL.

Краткие сведения о языке запросов SQL


Несмотря на заявленную универсальность и существование стандарта ANSI SQL-
92, как водится, каждый из разработчиков серверов баз данных попытался расширить
узкие рамки стандарта, придав своей версии языка SQL дополнительные возможно-
сти, учитывающие особенности формата базы данных. И хотя основа языка остается
неизменной, существуют некоторые различия, зависящие от формата используемого
сервера базы данных. В этом разделе рассматривается так называемый локальный SQL
160 Borland C++ Builder 6. Разработка приложений баз данных

(Local SQL). Большая часть приведенной здесь информации, однако, подходит и до


других версий языка.
Локальный SQL — это подмножество стандарта SQL-92; он предназначен для достуг
к таблицам локальных баз данных (Paradox, dBASE, FoxPro). По сравнению со станда]
том ANSI SQL-92, локальный SQL вносит большое количество дополнений, учитываклщ
специфику вышеупомянутых баз данных. С другой стороны, в списке конструкций АК
SQL-92, которые не поддерживает локальный SQL, более полусотни элементов. В случ;
доступа к локальным таблицам, запросы на локальном SQL обрабатываются не какш
либо конкретным сервером баз данных, а непосредственно BDE. Механизм BDE тран<
лирует SQL-операторы в функции API BDE. В этом, наверное, еще одна причина, почем
локальный SQL так назван.
Операторы SQL делятся на две группы. К одной группе относятся операторы, предн;
значенные для манипулирования данными, а ко второй группе — метаданными. Перва
группа операторов SQL составляет язык (операторы) манипуляции данными (DML — Dai
Manipulation Language), а вторая группа операторов представляет собой язык (операторь
опеределения данных (DDL — Data Definition Language).
Замечание
Стандарт локального SQL требует, чтобы все ключевые слова были написаны заглав
ными буквами. Но так как BDE все равно приводит все ключевые слова оператора к такс
му виду, то, на самом деле, регистр ключевых слов не имеет значения.

Некоторые соглашения

Встроенные комментарии
В операторах SQL можно использовать встроенные комментарии в стиле языка С
Любой текст между парой символов /* и */ считается комментарием и удаляется из опера
тора SQL при его синтаксическом разборе.

Форматы констант даты и времени


Константы типа Дата (Date), как и текстовые константы, заключаются в двойные
кавычки. Существует два формата представления даты: американский и европейский
В американском формате в качестве разделителей используется символ косой черты («/»
и первым указывается месяц (одна или две цифры), затем день (одна или две цифры) и гол
(две или четыре цифры):
"mm/dd/yy"
или
"mm/dd/yyyy"
Европейский формат даты подразумевает использование в качестве разделителя сим-
вола точки («.»). Первым указывается день (одна или две цифры), затем месяц (одна или
две цифры) и год (две или четыре цифры):
"dd.mm.yy"
или
"dd.mm.yyyy"
Константы времени также заключаются в двойные кавычки. Локальный SQL допускает
использование констант времени в формате hh:mm:ss AM/PM, где hh — часы, mm — ми-
нуты, a ss — секунды. Модификаторы AM и РМ указывают, какая половина суток исполь-
Глава 3. Использование механизма ВОЕ (продолжение) 161

зуется для задания времени (РМ— до полудня, AM— после). Регистр ключевых слов AM
и РМ безразличен. Указывать модификаторы необязательно, в этом случае часть суток
определяется автоматически по значению времени. Например: «12:24:00».

Формат логических констант


Логические константы TRUE и FALSE в операторах SQL можно указывать нескольки-
ми способами. Можно указать полное имя логической константы в двойных кавычках или
без них. При этом регистр символов не важен. Например:
Where (Actual=FALSE) And (Reserved="True")
Кроме того, логическую константу можно указать при помощи только первой буквы:
«Т» или «F».

Язык манипуляции данными (DML)


Локальный SQL поддерживает четыре вида операторов DML — операторы выборки
(Select), удаления (Delete), вставки (Insert) и обновления (Update).

Оператор выборки (Select)


Этот оператор предназначен для отбора информации из таблиц. Ниже приведен его
полный синтаксис.
Select [Distinct] список_полей
From список_таблиц
[Where условие_отбора]
[Group By список_полей_группы]
[Having условие_отбора]
[Order By список_полей_сортировки]
Отдельные фрагменты оператора SQL, начинающиеся с ключевого слова SQL и предст-
авляющие собой законченные логические части, называются предложениями (Clauses).
Обозначается предложение ключевым словом, с которого оно начинается. Приведенный
выше оператор выборки может содержать до шести предложений; первые два предложе-
ния обязательны, остальные —'• нет. (При описании языковых конструкций SQL необяза-
тельные части заключаются в квадратные скобки.)

Предложение 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, должны подчиняться определен


ньш правилам именования. В соответствии со стандартом ANSI имена таблиц должнь
состоять только из букв, цифр и символа подчеркивания ("_"). Локальный SQL допол
няет стандарт ANSI, позволяя указывать имена таблиц еще несколькими способами
Указать имя файла таблицы вместе с расширением можно при помощи одинарны>
кавычек: "Items, db". Если имя таблицы содержит не только алфавитно-цифровые
символы (а, например, также пробел), это имя нужно заключить в двойные кавычки
"Item Parts.db". В двойных кавычках также можно указать полный путь и имя файлг
таблицы: "d:\Temp\Items.db". Вместо пути к файлу таблицы можно использовать псев-
доним. В этом случае псевдоним с двух сторон обрамляется двоеточием: ":MyData:
Items, db". Кроме того, допускается использовать в качестве имени таблицы одно из за-
резервированных слов SQL, если заключить его в двойные кавычки: "Select". Список
зарезервированных слов локального SQL приведен в таблице 3.2.
Таблица 3.2. Список зарезервированных слов локального SQL
Active Add All After Alter
And Any As Asc Ascending
At Auto Autoinc Avg Base Name
Before Begin Between Blob Boolean
Both By Bj^tes Cache Cast
Check_Point_
Char Character Check Collate
Length
Column Commit Committed Computed Conditional
Constraint Containing Count Create Cstring
Current Cursor Database Date Day
Debug Dec Decimal Declare Default
Delete Desc Descending Distinct Do
Domain Double Drop Else End
Entry Point Escape Exception Execute Exists
Exit External Extract File Filter
Float For Foreign From Full
Function Gdscode Generator Gen Id Grant
Глава 3. Использование механизма ВОЕ (продолжение) 163
Group Commit
Group Having Hour If
Wait Time
In Int Inactive Index Inner
Input_Type Insert Integer Into Is
Isolation Join Key Long Length
Logfile Lower Leading Left Level
Maximum
Like Log_Buffer_Size Manual Max
Segment
Merge Message Min Minute Module Name
Money Month Names National Natural
Nchar No Not Null Num LogJBuffers
Numeric Of On Only Option
Or Order Outer Output Type Overflow
Page Size Page Pages Parameter Password
Plan Position Post Event Precision Procedure
Protected Primary Privileges Raw Partitions RdbSDb Key
Read Real Record Version References Reserv
Reserving Retain Returning Values Returns Revoke
Right Rollback Second Segment Select
Set Shared Shadow Schema Singular
Size Smallint Snapshot Some Sort
Sqlcode Stability Starting Starts Statistics
Sub Type Substring Sum Suspend Table
Then Time Timestamp Timezone Hour Timezone Minute
To Trailing Transaction Trigger Trim
Uncommitted Union Unique Update Upper
User Value Values Varchar Variable
Varying View Wait When Where
While With Work Write Year

Для таблицы, участвующей в предложении From, можно задать новое имя (псевдоним
таблицы). Псевдоним таблицы указывается вслед за именем таблицы через пробел, напри-
мер: "Items.db" It. Этот псевдоним потом нужно использовать в тексте оператора SQL
вместо имени таблицы (везде). Это удобно в нескольких случаях, например, для упроще-
ния записи вместо длинного имени таблицы можно использовать псевдоним из одной или
нескольких букв. Если имя таблицы указано как имя соответствующего файла с расши-
рением ("Items.db"), то для упрощения доступа к именам полей таблицы также удобно
использовать псевдоним. И, наконец, если в запросе участвует несколько экземпляров
одной и той же таблицы (например, в рекурсивной связи, когда таблица связывается сама
с собой по разным полям) без псевдонима просто не обойтись.
Предложение From служит также и для того, чтобы задать временные (т.е. суще-
ствующие только в рамках данного запроса) связи между таблицами. Простейший вид
связи между таблицами, указанными в предложении From, — отсутствие каких-либо
связей. Такой вид связи называется декартовым (Cartesian). Ниже приведен пример
декартовой связи:
Select * From Items, Storage;
164 Borland C++ Builder 6. Разработка приложений баз данных

Вышеприведенная строка SQL-оператора заставит BDE (как, впрочем, и любой се


вер баз данных) возвратить набор строк, содержащий все поля обеих таблиц, указаны!
в предложении From. Так как не был указан ни один из видов связи, в результирующ*
наборе данных каждой записи из таблицы Items будет соответствовать каждая запи
из таблицы Storage. В частности, если таблица Items содержит 10 записей, атабли;
Storage— 5, результирующий набор, заданный вышеприведенной декартовой связы
будет содержать 10 х 5 = 50 записей.
Реальную связь между таблицами в предложении From можно задать при помои
операторов связи. В операторах связи должны указываться пары полей таблиц, меж;
значениями которых устанавливается равенство (или другая операция отношенш
Предлагаемый в рамках локального SQL способ задания связи между таблицами — опер
торы Inner и Outer. Оператор Inner служит для задания внутреннего объединения табли
Внутреннее объединение таблиц означает, что в итоговый набор данных попадут толы
те записи из обеих таблиц, значения связанных полей которых совпадают. Внутренш
объединение двух таблиц задается так:
имя_таблицы! Inner Join имя_таблицы2
On (имя_таблицы!.имя_поля!=имя_таблицы2.имя_поля2)
Вслед за ключевым словом On указываются соотношения между полями таблиц, кс
торые используются для организации связи. Таких соотношений может быть несколью
В этом случае для их объединения в одно логическое выражение служат логические ош
раторы And (логическое И), Or (логическое Или) и Not (логическое отрицание). Вмест
знака равенства может использоваться любой знак операции отношения (<, >, = и т. д.). Н
так можно получить очень запутанное выражение для связи, которое иногда может привс
дить к непредсказумым результатам.
Например:
From Items Inner Join Storage On (Storage.Item = Items.ItemNo)
В приведенном выше предложении From задано внутреннее объединение между та
блицами Items и Storage. В итоговый набор данных попадут только те записи из обей:
таблиц, у которых значения поля Item (из таблицы Storage) и поля ItemNo (из таблиць
Items) совпадают.
Внешнее объединение таблиц можно задать при помощи оператора Outer. Внешне(
объединение таблиц означает, что в итоговый набор данных попадут не только запис!
таблиц, обусловленные внутренним объединением, но и все остальные записи одно!
из таблиц. При этом соответствующие поля другой таблицы не будут иметь никакого
значения (другими словами, будут иметь значение NULL). Синтаксис внешнего объеди-
нения следующий:
From имя_таблицы! Left [Outer] Join имя_таблицы2
On (имя_таблицы1.имя_поля! = имя_таблицы2. имя_поля2)
From имя_таблицы! Right [Outer] Join имя_таблицы2
On (имя_таблицы!.имя_поля! = имя_таблицы2. имя_поля2)
From имя_таблицы! Full [Outer] Join имя_таблицы2
On (имя_таблицы!.имя_поля! = имя_таблицы2. имя_поля2)
Прииспользованииоператора/,е/?С>м?ет-(.левоевнешнееобг>ес)ннеиме)в итоговуювыборку
попадут все записи из обеих таблиц, для которых значения связанных полей совпадают (т. е.
записи, возвращаемые при внутреннем объединении), и, кроме того, все остальные записи
таблицы имя_таблицы1. В этих записях поля, соответствующие таблице имя_табли-
Глава 3. Использование механизма ВОЕ (продолжение) 165

цы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. Разработка приложений баз данных

В приведенном выше примере отбираются значения всех полей таблицы Item


В итоговую выборку попадут записи только о тех товарах, информация о которых ее
в таблице Storage и количество которых превышает 0 (т.е. об имеющихся в данный MI
мент на складе).
При помощи операции In можно определить, входит ли указанное значение в списс
значений:
значение[Not] In (список_значений)
Элемент список значений может быть или списком констант, перечисленных через запятую
пни подчиненным запросом. В последнем случае запрос может возвращать множество записе:
но только для одного поля (со значениями которого и будет сравниваться значение). В любо
случае, если элемент значение входит в список значений, указанный справа от ключевого СЛОЕ
In, операция возвращает значение true (false, если указывается необязательное ключевое слов
Not). Ниже приведены примеры обоих вариантов использования операции In.
//Запрос отбирает информацию о сотрудниках, имеющих одну из
//трех фамилий, указанных в списке значений операции In
Select EmployeeNo, LName, FName, MName, Birthday
From Employee
Where (LName In ("Иванов", "Петров", "Сидоров"))
//Запрос отбирает из таблицы11етз информацию о товарах,
//количество которых на складе не превышает 10
Select Items.ItemNo, Category.CategoryNo, Items.Item,
Category.Category, Items.Price
From Items Inner Join Category On
(Category.CategoryNo = Items.Category)
Where (Items.ItemNo In (Select Item From Storage Where Qty <= 10))
Операция Like сравнивает текстовое значение с указанным шаблоном и в случае успе-
ха возвращает значение true. Ниже приведен синтаксис операции Like.
значение [Not] Like шаблон [Escape "символ"]
Элемент шаблон представляет собой текстовую строку, используемую для сравнения
с элементом значение. В простейшем случае шаблон — это текстовая константа: LName
Like "Иванов ". Однако вся прелесть операции Like в том, что в текстовой строке шабло-
на можно применять символы подстановки. Их всего два. Символ % можно вставлять
в строку шаблона сколько угодно, как в начале или в конце строки, так и в ее середине.
Символ % означает любое количество символов в строке. Например, операция
Item Like "%Epson%Color%"
вернет значение true для всех товаров, в названии которых встречается слово Epson
и слово Color.
Второй символ подстановки — символ подчеркивания (_) — означает только один
символ. Вставлять его можно также сколько угодно, как в начало или конец текстовой
строки шаблона, так и в ее середину. Например, шаблону «b_t» будут удовлетворять стро-
ки «Ы(» и «but», но не «boot».
Если один из символов подстановки — часть строки шаблона, можно воспользовать-
ся конструкцией Escape. При ее помощи можно указать так называемый escape-символ.
Глава 3. Использование механизма ВОЕ (продолжение) 167

Символы подстановки % и_, указанные вслед за этим символом, считаются частью


данных. Например, нижеприведенная операция может использоваться для отбора строк
таблицы Items, в поле Description которых содержится подстрока «50%».
Description Like "%50Л%%" Escape "л"
Операция Is [Not] Null служит для проверки того, пусто ли поле. Поле пусто (NULL),
если ему не было присвоено никакого значения (или напрямую присвоено значение NULL).
Числовое значение 0 и текстовое значение «»(пустая строка) не равны значению NULL.
И, наконец, операции отношения Any, All и Some используются в сочетании с подчи-
ненными запросами. Синтаксис использования этих операций (на примере операции Any)
приведен ниже:
имя_таблицы.имя_поля > Any (подчиненный запрос)
Подчиненный запрос должен возвращать набор данных, состоящий из множества
строк, но содержащий только одно поле. Со значениями этого поля будут сравниваться
значения поля имя_поля. Вместо «>» можно применять любой другой оператор срав-
нения (<, <=, О и т.д.).
Операции Some и Any функционально одинаковы. И та, и другая вернет значение true,
если будет истинной операция сравнения значения имя_поля с любым значением поля
подчиненного запроса. При использовании операции АИ истинным должен быть результат
сравнения значения имя_поля со значениями поля всех записей подчиненного запроса.
Ниже приведены примеры использования операций АИ и Any.
//Запрос извлекает из таблицы Employee информацию о сотруднике,
//который еще не уволен и
//имеет наибольший стаж работы на предприятии •

Select El.EmployeeNo, El.LName, El.FName, El.MName, El.HireDate


From Employee El
Where El.HireDate All
(Select E2.HireDate From Employee E2
Where (EZ.FireDate Is Null) And (E2.EmployeeNo <> El.EmployeeNo))
//Запрос извлекает из таблицы Employee информацию о сотруднике,
//состоящем в штате предприятия и имеющем наименьший стаж
Select El.EmployeeNo, El.LName, El.FName, El.MName, El.HireDate
From Employee El
Where Not (El.HireDate Any
(Select E2.HireDate From Employee E2
Where (E2.FireDate Is Null) And (E2 .EmployeeNo <> El.EmployeeNo)))
Замечание
Следует избегать использования подчиненных запросов, так как это значительно
снижает производительность запроса и, как следствие, всего приложения. Зачастую
правильно организованные связи между таблицами, а также условия отбора в предложе-
ниях Where и Having позволяют избавиться от применения «запросов в запросе».

Ранее уже упоминалось о том, что предложение Where может использоваться для за-
дания связи между таблицами. Ниже приведены два оператора SQL, выполняющих одну
и ту же задачу, хотя и разными путями.
168 Borland C++ Builder 6. Разработка приложений баз данных

//Внутреннее объединение таблиц Items и Category,


//организованное при помощи оператора связи Inner Join
Select Items.Item, Category.Category, Items.Description, Items.Pric
From Category Inner Join Items On (Category.CategoryNo = Items.Category)
//Внутреннее объединение таблиц Items и Category,
//организованное при помощи предложения Where
Select Items.Item, Category.Category, Items.Description, Items.Pric
From Category, Items
Where Items.Category=Category.CategoryNo

Предложение 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

По смыслу и приемам использования Having аналогично Where. Отличие в том, что


предложение Where применяется до групповых операций, a Having — уже после того
как они выполнены.

Предложение 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
В этом примере запрос возвращает итоговый набор данных, содержащий наимено-
вания категорий товаров, а также стоимость товаров данной категории, хранящихся на
складе. Набор данных будет отсортирован по стоимости товаров каждой категории по
убыванию.

Оператор удаления (Delete)


Этот оператор предназначен для удаления некоторого множества строк из указанного
набора данных. В отличие от оператора выборки, оператор удаления задает выполняе-
мый запрос. Такой запрос предназначен для того, чтобы выполнять какие-либо действия
с набором данных, не возвращая при этом никаких данных. Синтаксис оператора удале-
ния следующий:
Delete From список_таблиц [Where условие]
Этот оператор удаляет из таблиц, указанных в предложении From, записи, удовлетво-
ряющие условию, указанному в предложении Where. Если предложение Where не указано,
удаляются все записи. Приемы использования предложений From и Where те же, что и для
оператора выборки (Select).
Следует заметить, что если при проектировании связей между таблицами вы задали
опцию соблюдать ссылочную целостность (или каскадное удаление), то при удалении
строк из таблицы, участвующей в связи один-ко-многим со стороны один, будут удалены
и связанные записи из таблицы со стороны многие.

Оператор вставки (Insert)


Оператор вставки позволяет добавить к таблице новые записи. Запрос, основанный на
операторе Insert, — также выполняемый. Ниже приведен простейший синтаксис операто-
ра вставки:
170 Borland C++ Builder 6. Разработка приложений баз данных

Insert Into имя_таблицы [(список_полей)] Values (список_значений)


В таком виде оператор Insert вставляет в таблицу имя_таблицы одну новую з;
пись и заполняет поля, указанные в списке полей, значениями, указанными в спис!
значений. Оба списка следует заключать в круглые скобки. Список значений долже
соответствовать списку полей; например, если первое поле в списке полей имеет та
дата/время, то этот же тип должна иметь и первая константа, указанная в списке зн;
чений. Список значений не может быть длиннее списка полей, но может быть короч
В этом случае поля, которым не хватило значений, заполняются или значенями л
умолчанию, или значением NULL. Этими же значениями заполняются также и пол:
не указанные в списке.
Элемент список_полей можно и не указывать. В этом случае константы, указанны
в списке значений, используются для инициализации всех полей таблицы в порядке и
объявления (с учетом всех сделанных выше замечаний).
Оператор Insert можно также использовать для вставки в таблицу множества записей
Для этого применяется форма оператора вставки, в которой вместо конструкции Value
используется подчиненный запрос на выборку. Выглядит это примерно так:
Insert Into имя_таблицы (список_полей) подчиненный_запрос_на_выборк)
Элемент список_полей должен совпадать с набором полей, возвращаемых подчинен
ным запросом. В таблицу имя_таблщы будет добавлено столько новых строк, сколью
возвратит подчиненный запрос.

Оператор обновления (Update)


Оператор обновления предназначен для модификации значений указанных поле{
одной или множества записей таблицы. В простейшем случае оператор Update имеет
следующий синтаксис:
Update имя_таблицы
Set имя_поля = значение [, имя_поля = значение...]
[Where условие]
Приведенный выше оператор обновляет записи таблицы имя_таблицы, удовлетворя-
ющие условиям, заданным в предложении Where. Если предложение Where не указано, то
обновляются все записи таблицы. Модификации подвергаются только поля, имена кото-
рых указаны в предложении Set. Для каждого поля справа от знака равенства указывается
новое значение. Значением может быть константа, выражение, а также подчиненный за-
прос Select, возвращающий только одно значение.
Ниже приведен оператор обновления, увеличивающий цену товаров категории
«Процессоры» на 5%.
Update Items
Set Itemsl.Price=Iterns1.Price*!.05
Where Category=4

Оператор объединения (Union)


При помощи ключевого слова Union можно объединить несколько наборов данных, воз-
вращаемых запросами на выборку, в один набор данных. Выглядит это следующим образом:
Глава 3. Использование механизма ВОЕ (продолжение) 171

Оператор Select I Union [All]


оператор Select 2 Union [All]

оператор Select n
/
Объединенный набор данных формируется так: в конец первого набора данных до-
бавляется второй набор, затем в конец второго набора — третий, и т. д. По умолчанию,
в объединенном наборе данных совпадающие записи группируются в одну запись. Иногда
это может привести к неправильным результатам. Например, при формировании итогово-
го отчета о продажах предприятия можно использовать оператор Union для объединения
в одну выборку сведений о продажах в различных торговых точках. Потом, сгруппировав
полученный таким образом набор данных, можно получить требуемый итоговый отчет.
Однако если в отдельных наборах данных будет содержаться несколько одинаковых строк
(т.е. в нескольких торговых точках предприятия было продано одинаковое количество
какого-либо товара на одну и ту же сумму), то в объединенную выборку попадет только
одна такая строка. В результате, итоговый отчет о продажах после группировки и сумми-
рования будет содержать ошибочную информацию.
Избежать подобных трудноуловимых ошибок можно при помощи ключевого слова
АИ, следующего за ключевым словом Union. В этом случае в итоговую объединенную
выборку попадут все без исключения строки. Несмотря на то, что ключевое слово All
в операторе объединения не обязательное, во избежание непредсказуемых ошибок есть
смысл использовать его всегда.

Запросы с параметрами
Вместо значений, используемых в предложениях Where (в условных выражениях) и Set
(в выражениях обновления) операторов SQL, можно применять параметры. Параметр со-
стоит из символа двоеточия (:) и следующего за ним идентификатора, например:
Select Item, Category, Price From Items
Where Category = :CategoryNo
Передавая оператору SQL разные значения параметра CategoryNo, можно получать
разные выборки. Более подробно вопрос об использовании параметров будет рассмотрен
далее в этой главе.

Несколько полезных функций SQL


Функции Ъоыег(выражение) и иррег(выражение) переводят строку, заданную вы-
ражением, в нижний или верхний регистр соответственно. Выражение должно или быть
именем текстового поля, или приводиться к текстовой константе. Например, результатом
выражения Lower ("Borland") будет "borland", а результатом выражения Upper(" Borland")
будет "BORLAND".
Функция Substring служит для извлечения подстроки из строки. Синтаксис этой функ-
ции следующий:
Substring (выражение From начало [For длина])
Элемент выражение может быть любым выражением, возвращающим текстовую кон-
станту или имя текстового поля. Элемент начало указывает номер первого символа строки,
с которого начинается подстрока, а элемент длина — соответственно, длину подстроки. Если
172 Borland C++ Builder 6. Разработка приложений баз данных

конструкция For не указывается, то подстрока извлекается, начиная с позиции начало и закан


чивая последним символом строки. Например, результатом следующего выражения:
SubstringC'Borland C++ Builder" From 13 For 7)
будет строка "Builder". Тот же результат получится, если опустить конструкцию For.
При помощи функции Trim можно удалить в начале и/или в конце строки все вхожде
ния указанного символа. Синтаксис этой функции таков:
Trim([Leading|Trailing|Both] [символ] From выражение)
Чтобы указать, откуда удаляются все вхождения символа, заданного элементом символ
нужно использовать одно из ключевых слов — Leading, Trailing или Both. В первом случае
символы удаляются в начале строки (лидирующие символы), во втором случае — в ее конце,
а в третьем — и в начале строки, и в конце. Если ни одно из этих ключевых слов не указано, тс
подразумевается Leading. Если не указан элемент символ, имеется в виду символ пробела.
Например, результатом выражения:
Trim(Leading "0" From "00099456")
будет строка "99456".
Функция Cast служит для преобразования указанного значения к заданному типу. Эта
функция имеет такой формат:
;
Cast(выражение As тил_данных)
В качестве выражения может использоваться любое выражение или имя поля. Тип
данных может быть любым, допустимым в данной ситуации. Например, если указано имя
поля, тип данных должен быть одним из типов, поддерживаемых данным типом таблиц.
Ниже приведен пример приведения значения числового поля к текстовому типу.
Cast(Quantity As Char(5)). „
Функция Extract предназначена для извлечения частей даты (день, месяц или год) из
выражений типа Date или Timestamp. Формат функции таков:
Ех1гас1(часть_даты From выражение)
Элемент часть_даты может быть одним из трех ключевых слов: Day (из даты из-
влекается день), Month (извлекается месяц) и Year (извлекается год). Элемент выражение
может быть любым выражением типа Date или Timestamp, или именем поля того же типа.
Ниже приведен запрос на выборку, в котором используется функция Extract.
Select LName, FName, MName, Extract(Year From HireDate) As HireYear
From Employee
Замечание
В операторах SQL в качестве источника данных вместо имен таблиц можно везде
использовать имена файлов, в которых были предварительно сохранены запросы.
Эти файлы могут быть в одном из двух форматов: в текстовом (с расширением sql)
или в формате qbe (с расширением qbe). Имена файлов вместе сраширениями (sql
или qbe) должны заключаться в двойные кавычки. Файлы в формате sql или qbe
называются предварительно сохраненными запросами. В любых операторах запроса SQL
предварительно сохраненные запросы могут везде использоваться вместо имен таблиц
(в том числе и в определении связей). Запросы в формате sql и qbe могут быть сохранены
при помощи утилиты Database Desktop.
Глава 3. Использование механизма ВОЕ (продолжение) 173

Язык определения данных (DDL)


Язык определения данных (Data Definition Language, или DDL), входящий в состав ло-
кального SQL, позволяет только создавать, модифицировать и удалять таблицы и индексы.
Локальный SQL поддерживает всего пять операторов, относящихся к DDL, — это Create
Table, Alter Table, Drop Table, Create Index и Drop Index.

Оператор создания таблицы (Create Table)


Оператор Create Table предназначен для создания новой таблицы. Локальный SQL
предполагает следующий синтаксис этого оператора:
Create Table имя_таблицы (определение_поля [, определение_поля,...]
[, определение_первичного_ключа])
Оператор Create Table» создаст таблицу (в базе данных, к которой подключен запрос)
с названием имя_таблицы. Имя таблицы указывается в двойных кавычках с расшире-
нием db (для таблиц Paradox) или ей/(для таблиц dBASE). Если расширение не указано,
создаваемая таблица будет иметь тип, определенный для базы данных, в рамках которой
выполняется запрос. В этом случае двойные кавычки не нужны. За именем таблицы
в круглых скобках следует набор определений полей для новой таблицы. Определение
поля имеет следующий вид:
имя_поля тип_данных.
Тип данных поля должен быть одним из допустимых для таблиц выбранного формата.
После определений полей таблицы может следовать определение первичного ключа.
Определить первичный ключ можно одним из двух способов. Первый подразумевает ис-
пользование конструкции Primary Key. В ней в круглых скобках через запятую должны
быть перечислены имена полей таблицы, которые должны составлять первичный индекс.
Ниже приведен оператор создания таблицы Items:
Create Table "Items.db" (
ItemNo Autoinc,
Item Varchar (50) ,
Category Integer,
Description Varchar (100),
"Items.db" ."Index" Varchar (5),
Price Money,
Primary Key (ItemNo)
)
Следует обратить внимание на ряд важных аспектов приведенного выше оператора.
Первый аспект связан с набором типов данных, поддерживаемых локальным SQL. Он не
совпадает с набором типов, поддерживаемых Database Desktop, которыми мы пользова-
лись при создании таблицы Items. В частности, вместо типа Autoincrement используется
Autoinc, вместо Alpha — тип Char или Varchar, а вместо Long следует использовать тип
Integer. Набор типов данных, поддерживаемых локальным SQL, приведен в таблице 3.3.
Следующий аспект, о котором стоит упомянуть, — определение поля Index. Так как
имя поля совпадает с одним из ключевых слов локального SQL (см. таблицу 3.2), то для
его определения следует использовать несколько специфичный синтаксис. Само наи-
менование поля должно быть заключено в двойные кавычки, а перед ним, через символ
174 Borland C++ Builder 6. Разработка приложений баз данных

точки, необходимо указать имя таблицы (в том виде, в каком оно было упомянуто поел
ключевых слов Create Table). Этот же синтаксис используется и для определения поле!
чьи имена содержат пробелы (или символы, не являющиеся алфавитно-цифровыми).
Таблица 3.3. Типы данных, поддерживаемые локальным SQL

Тип данных Описание


Smallint Соответствует целому числу.
Integer Соответствует длинному целому.
Decimal[(s[, p])] Число с плавающей точкой. Параметры s и р задают размер
и точность числа.
Numeric[(s[, p])] — и —

Float(s, p) — 11 —

Character(length) Текстовый тип, задающий строку размером от 1 до 254 символов.


Varchar(Iength) _ м _
Date Дата без порции времени.
Time Время без порции даты.
Boolean Логический тип.
Blob(length, type) Потоковый текст или сырые двоичные данные. Тип Blob-
полей задается при помощи констант: 1 (Memo), 2 (Binary), 3
(форматированное поле Memo), 4 (OLE), 5 (графические или
двоичные данные). Для Paradox параметр length должен быть
в пределах от 0 до 240, для таблиц dBASE — от 0 до 32 767.
Timestamp Формат данных, содержащих и дату, и время.
Money Денежный формат данных.
Autoinc Автоинкрементные значения.
Bytes(length) Определенные пользователем данные. Параметр length указывает
размер этих данных.

Задать для создаваемой таблицы первичный ключ можно также при помощи конструк-
ции Constraint. Вслед за ключевым словом Constraint нужно указать имя первичного
ключа, а затем конструкцию Primary Key. Для таблиц Paradox и dBASE имя первичного
ключа, отличное от задаваемого сервером по умолчанию, задать нельзя. Несмотря на это,
в конструкции Constraint все равно нужно указывать какой-нибудь идентификатор — он
ни на что влиять не будет.
Замечание
Нельзя создать таблицу, если в базе данных уже существует таблица с таким именем.
В этом случае, прежде чем запускать на выполнение запрос на создание таблицы, следу-
ет убедиться, что вы задали уникальное (в рамках текущей базы данных) имя таблицы.
Сделать это можно, например, при помощи свойства Exists компонента ТТаЬ/е. Можно
также организовать перехват исключительной ситуации, которая возникает при попытке
создать таблицу с уже существующим в базе данных именем.
Глава 3. Использование механизма ВОЕ (продолжение) 175

Оператор модификации таблицы (Alter Table)


Оператор Alter Table предназначен для изменения структуры уже существующей та-
блицы. С его помощью можно удалить из таблицы одно или несколько имеющихся в ней
полей, а также добавить одно или несколько определений новых полей. Синтаксис опера-
тора модификации таблицы следующий:
Alter Table имя_таблицы Drop имя_поля | Add определение_поля
В операторе Alter Table нужно указать хотя бы одно из действий Drop или Add. Вслед
за ключевым словом Drop должно следовать имя поля, которое требуется удалить из таб-
лицы. Инструкций Drop в операторе Alter Table может быть несколько. Все они перечис-
ляются через запятую. Добавить новое поле в таблицу можно при помощи конструкции
Add. Вслед за ключевым словом Add нужно указать определение поля так, как это делает-
ся в операторе Create Table: имя_поля тип_данных. Допускается использование в одном
операторе Alter Table нескольких конструкций Add одновременно с конструкциями Drop.
Кроме того, в одном операторе Alter Table можно удалить поле и добавить новое поле с
тем же именем:
Alter Table Drop Description, Add Drscription Varchar(50).
Таким образом можно быстро переопределить поле таблицы. Следует иметь в виду,
что при этом все данные, хранящиеся в поле, будут утеряны. Поэтому таким способом пе-
реопределения поля обычно пользуются только на стадии проектирования таблицы. Если
таблица уже содержит какие-нибудь данные, переопределить поле можно в несколько эта-
пов. Сначала нужно создать временное поле подходящего типа и скопировать в него все
данные из поля, которое нуждается в переопределении. Затем описанным выше способом
переопределить поле и скопировать в него все данные из временного поля. На последнем
шаге нужно удалить временное поле из таблицы.
Несколько замечаний в заключение. Если удалить из таблицы поле, являющееся
частью первичного ключа, из таблицы будет удален и сам первичный ключ. Определить
первичный ключ при помощи оператора Alter Table невозможно. Кроме того, невозможно
удалить из таблицы поле, если это приведет к нарушению ссылочной целостности (напри-
мер, если первичный ключ связан с внешним ключом другой таблицы).

Оператор удаления таблицы (Drop Table)


При помощи оператора Drop Table можно удалить таблицу из базы данных:
Drop Table имя_таблицы.

Оператор создания вторичного индекса (Create Index)


При помощи оператора Create Index можно в существующей таблице создать вторич-
ный индекс. Оператор имеет следующий синтаксис:
Create [Unique] [Asc | Desc] Index имя_индекса
On имя_таблицы (имя_поля! [, и м я _ п о л я 2 . . . ] ) .
В приведенном выше операторе имя_таблщы — это имя существующей в базе данных
таблицы. Имя индекса должно быть уникальным в пределах таблицы и не может содер-
жать пробелов. Перечень полей таблицы, составляющих индекс, помещается в круглых
176 Borland C++ Builder 6. Разработка приложений баз данных

скобках после имени таблицы. В частности, таблицы Paradox могут содержать составной
индекс. Однако для таблиц dBASE можно указать одно имя поля, на основе которого i
будет создан вторичный индекс.
Если вторичный индекс должен быть уникален (т.е. набор полей, составляющих ин-
декс, не должен содержать совпадающих наборов значений), нужно указать ключевое
слово Unique. При помощи ключевых слов Asc (Ascending) и Desc (Descending) можнс
задать порядок сортировки для индекса. Если ни одно из этих ключевых слов не указано
подразумевается сортировка по возрастанию (Asc).
Ниже приведен пример создания в таблице Employee составного индекса NameNDX
основанного на значениях полей LName, FName и MName:
Create Index NameNDX On Employee (LName, FName, MName).

Оператор удаления индекса (Drop Index)


При помощи оператора Drop Index можно удалить из таблицы вторичный или первич-
ный индекс. Этот оператор используется в следующей форме:
Drop Index имя_таблицы.имя_индекса.
При удалении первичного индекса из таблицы Paradox в качестве имени индекса сле-
дует использовать ключевое слово Primary.

Свойство SQL компонента TQuery.


Построитель запросов (SQL Builder)
Чтобы воспользоваться услугами объекта TQuery, достаточно всего-навсего двух его
свойств. Нужно указать базу данных, в рамках которой будет выполняться запрос (свой-
ство DatabaseName), и ввести оператор SQL в свойство SQL. Хотя, впрочем, можно обой-
тись и одним свойством SQL, так как базу данных, с таблицами которой будет оперировать
запрос, можно задать и в строке оператора SQL. Дальнейшие действия по использованию
объекта TQuery зависят от типа запроса, заданного строкой SQL-оператора. Если это
запрос, возвращающий набор записей (запрос на выборку или запрос на объединение),
можно установить свойство Active объекта TQuery в true, после чего объект TQuery можно
использовать так же, как и объект ТТаЫе (т.е. как обычный набор данных).
Если же строка запроса, заданная в свойстве SQL, представляет один из выполняемых
запросов (оператор на вставку, удаление или обновление записей, или один из операторов
языка DDL), то попытка открыть набор данных вполне естественно вызовет возбуждение
исключительной ситуации. Запустить запрос такого типа на выполнение можно в режиме
конструктора при помощи пункта Execute контекстного меню объекта TQuery, а при вы-
полнении приложения — с помощью метода ExecSQL:
Queryl->ExecSQL();
Свойство SQL представляет собой указатель на набор строк (класс TString). Как и всякое
свойство этого типа, SQL снабжено редактором набора строк (String List Editor). При помощи
этого редактора можно ввести любой требуемый оператор SQL. При выполнении приложения
удобно пользоваться индексированным доступом к строкам свойства SQL. Например, в тело
обработчика события OnCreate формы можно ввести следующий код:
Глава 3. Использование механизма ВОЕ (продолжение) 177
Queryl->Close() ;
Queryl->SQL->Clear() ;
Queryl->SQL->Strings[0]="Select * From Items";
Query l->SQL->Strings[l]="Whe re Category=l" ;
Queryl->0pen() ;
В результате при открытии формы расположенная на ней сетка будет отображать за-
писи таблицы Items, соответствующие категории со значением 1 . Далее, на форме можно
расположить элемент редактирования Editl и кнопку Button 1, введя в тело обработчика
события нажатия кнопки следующий код:
Queryl->Close() ;
Queryl->SQL->Strings[l]="Where Category=" Edi tl->Text;
Queryl->0pen() ;
Тогда, указав в элементе Editl номер категории и нажав кнопку Button 1, можно по-
лучить итоговую выборку, отфильтрованную нужным образом. Конечно, этот код следует
дополнить, добавив проверку допустимости введенного в элемент редактирования Editl
значения.
Если вам нужно ввести сложный оператор запроса на выборку, особенно если он дол-
жен содержать множество связанных между собой таблиц, многочисленные группировки,
условия и т.д., удобно пользоваться построителем запросов (SQL Builder), который связан
с компонентом TQuery.
Вызвать построитель запросов можно, выбрав пункт SQL Builder из контекстного
меню объекта TQuery. Если в свойстве SQL уже содержался какой-нибудь SQL-оператор,
то SQL Builder откроется, попытавшись отобразить в своем окне соответствующие визу-
альные элементы. Если это ему не удастся, на экран будет выдано сообщение об ошибке
и SQL Builder не откроется. Это может произойти, если свойство SQL содержит один из
выполняемых операторов (операторы вставки, удаления, обновления или один из опера-
торов языка DDL) или неверный оператор выборки. Чтобы открыть SQL Builder в такой
ситуации, сначала придется очистить свойство SQL.
На панели инструментов SQL Builder расположены два комбинированных списка:
Table и Database. Прежде чем выбрать имя таблицы, следует выбрать имя базы данных.
Комбинированный список Database содержит все псевдонимы баз данных, доступные
в системе. Кроме имени псевдонима, из комбинированного списка Database можно вы-
брать элемент <Local Directory>, тогда на экране будет отображено диалоговое окно для
выбора директории. С его помощью можно выбрать директорию с нужными локальными
таблицами. Если для объекта TQuery задано свойство DatabaseName, это значение будет
автоматически подставлено в комбинированный список Database.

Рис. 3.25. Панель таблиц SQL


Builder содержит таблицу Items
из базы данных MyData в формате
Paradox и связанную с ней таблицу
Items из базы данных IBLocal
в формате InterBase

ITEMJp. Items ITEMJp ;


tern Items^l .Item
Category ltems_1 .Category
Price Items 1 .Price
178 Borland C++ Builder 6. Разработка приложений баз данных

Как только нужная база данных указана, из комбинированного списка 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 панели запро-
са. Она предоставляет средства, позволяющие организовать связь между таблицами более
гибко. В частности, вместо знака равенства между полями таблицы можно выбрать любую
другую операцию сравнения.
•••ВШИ

SELECT Items ITEMJD. ltems_1 Item, ItemsJ Category.


ltems_l .Price
FROM -Items.DB- ltems_l
INNER JOIN ":IBLocal:ITEMS" Items
ON (Items.lTEMJD = ltems_l.ltemNo)

Рис. 3.26. Окно SOL Query Text Entry содержит текст


сформированного SQL Builder оператора SQL
Слева от имени поля в окошке таблицы расположен флажок. Чтобы поле таблицы по-
пало в список полей предложения Select, этот флажок нужно установить. Если нужно, что-
бы все поля таблицы попали в выборку, можно установить флажок, расположенный слева
от имени таблицы (в заголовке окошка). Того же можно достичь при помощи средств,
предоставляемых вкладкой Selection. На ней можно не только выбрать поле таблицы для
представления, но и задать для него новое имя.
При помощи вкладки Criteria можно организовать представление условий отбора,
которые будут фигурировать в предложении Where оператора SQL. Вкладка Grouping
позволит указать поля, которые будут участвовать в группировке записей, а при помощи
вкладки Group Criteria можно задать условия, которые будут фигурировать в предложе-
Глава 3. Использование механизма ВОЕ (продолжение) 179

нии 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. Разработка приложений баз данных

Такой набор называется «живым». В этом случае вы сможете модифицировать данные


этого набора при помощи элементов визуализации и управления данными. Тогда в приве-
денном выше примере не будет никакой видимой разницы между подключением объекта
DataSourcel к объекту ТТаЫе и к объекту TQuery.
Следует иметь в виду, что не каждый запрос на выборку возвращает обновляемый на-
бор данных. Даже если задать для свойства RequestLive значение true, это не гарантирует,
что вы сможете редактировать данные. Единственное, что гарантирует такое значение
свойства, -— объект TQuery затребует от сервера БД «живой» набор данных, если это воз-
можно. После этого можно проверить свойство CanModify набора данных. Если его значе-
ние — true, набор данных можно редактировать, иначе — нет. Если свойство RequestLive
имеет значение false, то и свойство CanModify также будет иметь значениеуа&е, независи-
мо от обновляемое™ набора данных.
Запрос на выборку (Select) возвращает необновляемый набор данных в следующих
случаях.
О В предложении Select используется ключевое слово Distinct.
G В предложении From заданы связи между таблицами при помощи операций Inner Join
или Outer Join.
О В операторе SQL используются агрегатные функции (Sum, Max и т.д.).
О Базовая таблица, к которой обращается запрос, открыта только для чтения или открыта
другим пользователем в эксклюзивном режиме.
О В операторе SQL используются подчиненные запросы.
Q В предложении Order By указана сортировка не по индексным полям.
Если значение свойства RequestLive — true, но сервер БД (BDE) не в состоянии вер-
нуть «живой» набор данных, результирующий набор данных будет только для чтения.
Следует иметь в виду, что некоторые серверы БД вместо набора данных только для чтения
вернут код ошибки, в результате чего будет сгенерировано исключение.
Если используется режим кэширования изменений, можно модифицировать набор
данных только для чтения. Для этого применяется свойство UpdateObject. В качестве
значения этого свойства используется ссылка на объект типа TUpdateSQL. Компонент
TUpdateSQL имеет три свойства, каждое из которых содержит оператор SQL: DeleteSQL,
InsertSQL и ModifySQL. Эти операторы используются при попытке удалить, вставить или
модифицировать запись набора данных, возвращаемого объектом TQuery. Более подробно
компонент TUpdateSQL будет рассмотрен чуть позже в этой главе.

Использование параметров
Если свойство SQL объекта TQuery содержит текст параметризованного запроса (т.е.
запроса с параметрами), то прежде чем этот запрос использовать (открыть при помощи
метода Open или свойства Active, или запустить на выполнение при помощи метода
RunSQL), нужно задать для всех параметров подходящие значения. Сделать это можно
двумя способами: при помощи свойства DataSource или свойства-коллекции Params.
Свойство DataSource может содержать ссылку на объект типа TDataSource, подклю-
ченный к другому набору данных (ТТаЫе или TQuery). Если этот набор данных содержит
поля, имена которых совпадают с именами параметров, то текущие значения этих полей
будут автоматически использованы в качестве значений параметров.
Глава 3. Использование механизма ВОЕ (продолжение) 181

I b-r,,|,-.vN , j L'fltHorijry ! *.]


£j 3 Плоты материнские jjdjs?*

ttemfJo jSem ^!CeJegciyjDesmpfe№ |*j

59 Socket APC Poriner 91 1/1 00 3 VIA Apollo tCUsJ^


60 Socket APC Partner (Menli) 249 3 VIAKT133A.-- •-.-
61 SocketAMenliM-KT133AB 3 V1AKT133A, '/..'.-I
62 .Socket AGIGA-BVTE GA-7ZXE з VIA KT133A, -;''-•!
63 :Socket A GIGA-BYTE GA-7ZX Rev 5.X 3 VlAKT133,Cr' " |
64 : Socket A ChainTech CT-7AJA2 3 V1AKT133A," ,
65iSocketAGIGA-BYTEGA-7ZMMH 3 'VIAKM133A, .
68 SocketAASUSA7V133-C 3 VIAKT133A'..;|
67 Socket 370 FC-PGA/PPGA Canyon 6LEBM! 3 VtA Apollo ^jj?№

jU

Рис. 3.27. Текущее значение поля CategoryNo набора данных,


отображаемого в верхней сетке, используется в качестве значения
одноименного параметра в наборе данных, который отображается в сетке внизу
Для иллюстрации использования свойства DataSource создадим небольшое приложе-
ние-пример. Откройте новое приложение и на его главной форме разместите две сетки
(TDBGrid)— так, как на рис. 3.27 (вверху— DBGrid2, внизу— DBGridl). Добавьте на
форму по одному объекту TQuery, TTable и два объекта TDataSource, оставив все име-
на по умолчанию. Сетку DBGrid2 подключите к объекту DataSource2, который, в свою
очередь, подключите к объекту Tablel. Table 1 подключите к таблице Category базы дан-
ных MyData. Далее, при помощи редактора полей добавьте в набор постоянных полей
CategoryNo и Category и откройте набор данных Tablel.
Вторая информационная цепочка: DBGridl — DataSource 1 — Query 1. Объект Query 1
подключите к базе данных MyData, а в качестве значения свойства SQL укажите следую-
щую строку:
Select * From Items Where category=:CategoryNo
Так как имя параметра CategoryNo совпадает с именем одного из полей набора дан-
ных Tablel, то этот набор данных можно использовать в качестве источника значений
для параметра. Для этого нужно задать для свойства DataSource объекта Query 1 значение
DataSource2. Вот, в принципе, и все. Откройте набор данных Query! и запустите при-
ложение на выполнение. При перемещении по записям в сетке DBGrid2 сетка DBGridl
будет отображать записи из таблицы Items, отфильтрованные в соответствии с текущим
значением категории (см. рис. 3.27).
Стандартный способ задания значений для параметров запроса— использование
свойства-коллекции Params компонента TQuery. Коллекция Params содержит набор всех
параметров запроса. Стиль задания значений параметров аналогичен стилю задания зна-
чений полей — можно использовать индексацию в стиле массива, а можно прибегнуть
к методу ParamByName компонента TQuery. В частности, с учетом приведенного выше
примера, следующие две строчки кода выполняют одно и то же действие:
Queryl->Params->Items[0]->AsInteger=3;
Queryl->ParamByName("CategoryNo")->AsInteger=3;
Прежде чем задавать значения для параметров, необходимо закрыть набор данных.
Обычно схема этого процесса выглядит так:
Queryl->Close();
Query!->ParamByName("CategoryNo")->AsInteger=3;
182 Borland C++ Builder 6. Разработка приложений баз данных

Queryl->Prepare();
Queryl->0pen();
Вызывать метод Prepare перед открытием набора данных не обязательно, хотя и pt
комендуется. При вызове этого метода BDE или сервер БД выполняет некоторые предвг
рительные действия, в частности распределяет необходиме ресурсы. Если метод Ргераг
не вызывать, он будет вызван неявно. В паре с этим методом иногда используется мето,
UnPrepare— он выполняет обратные действия, освобождая выделенные для запрос
ресурсы. Использование этих методов может немного оптимизировать выполнение за
проса, особенно если запрос вызывается очень часто (за счет удаления лишних операцш
распределения/освобождения ресурсов). Определить, подготовлен ли запрос к выполне
нию, можно при помощи логического свойства Prepared.

Выполняемые запросы
К выполняемым относятся запросы на вставку, удаление и модификацию записей
набора данных, а также все запросы DDL. Попытка задать для свойства Active объекта
TQuery значение true (или вызвать метод Open), если его свойство SQL содержит строку
выполняемого SQL-оператора, вызовет генерацию исключения. Заставить выполняться
такой оператор можно при помощи метода RunSQL:
Queryl->RunSQL();
После того как выполнение оператора будет завершено, можно узнать, сколько записей
подверглось действию запроса (т.е. было удалено, добавлено или модифицировано). Для
этого нужно проверить свойство RowsAffected. Значение 0 предполагает, что ни одна из
записей набора данных не подверглась изменениям. Если при выполнении запроса про-
изошла ошибка, то свойство RowsAffected вернет значение -1.

Несколько предыдущих разделов главы были посвящены основным отличиям в ис-


пользовании компонента TQuery по сравнению с другими наследниками класса TDataSet.
В остальном его поведение практически полностью соответствует поведению наборов
данных, описанному в предыдущей главе. Еще один характерный для компонента TQuery
момент, о котором чуть выше уже упоминалось, — возможность обновлять набор данных
только для чтения при помощи объекта TUpdateSQL.

Компонент TUpdateSQL
Есть одна лазейка, которая позволяет модифицировать записи таблицы (или таблиц),
полагаемой в основу необновляемого запроса (набора данных только для чтения). Для
этого понадобится режим кэширования изменений и компонент TUpdateSQL. Подход
этот прост, как и все гениальное; в основе его — особенности режима кэширования из-
менений, при котором все изменения в наборе данных сохраняются на локальном диске,
не затрагивая до поры сам набор данных. Все изменения набора данных, кэшированные
на локальном диске, все равно не могут вступить в силу, если набор данных предназначен
только для чтения. Более того, набор данных будет невозможно редактировать без помощи
объекта TUpdateSQL, даже если режим кэширования изменений включен. С другой сто-
Глава 3. Использование механизма ВОЕ (продолжение)

роны, само по себе использование объекта TUpdateSQL также не позволит редактировать


набор данных только для чтения.
Пожалуй, слишком длинное вступление. Как всегда, стоит идти от простого к сложно-
му. Кроме того, для облегчения задачи воспользуемся примерами. Точную информацию
об использовании компонента TUpdateSQL можно почерпнуть из справочной системы
Borland C++ Bulder 6. Пока следует принять ко вниманию информацию о том, что не-
обновляемый набор данных, возвращаемый запросом TQuery, может быть подвергнут
редактированию при совместном использовании режима кэширования изменений и как
минимум одного объекта типа TUpdateSQL.
Как сказал один из корифеев программирования (к сожалению, не помню, кто имен-
но), самый простой путь научиться программированию — начать программировать (за
точность фразы тоже не ручаюсь, но смысл, как мне кажется, передан верно). Поэтому
предлагаю проделать следующее упражнение. Расположите на форме приложения сетку
(DBGridl), а также объекты типа TQuery (Queryl), TDataSource (DataSourcel) и TUpda-
teSQL (UpdateSQLl). Сетку DBGridl подключите к объекту DataSourcel, а его, в свою
очередь, — к объекту Queryl. Запрос Queryl подключите к базе данных (псевдониму)
MyData и введите в его свойство SQL оператор:
SELECT StoreltemNo, Item, Qty, Description
FROM Storage
Задайте для свойства Active набора данных Queryl, а также для его свойства
CachedUpdates значение true, переведя набор данных в режим кэширования изменений.
Запустите программу на выполнение. Вы можете убедиться, что набор данных не поддает-
ся редактированию. Теперь задайте для свойства CachedUpdates значение/a/se, а в качест-
ве значения свойства UpdateObject объекта Queryl задайте объект UpdateSQLl. Еще раз
запустите приложение на выполнение. Вам опять не удастся отредактировать значение ни
одного из полей. Теперь переведите набор данных Queryl в режим кэширования измене-
ний, задав для свойства CachedUpdates значение true. В этом случае откомпилированное
приложение предоставит вам возможность редактировать данные. Обратите внимание на
то, что объект UpdateSQLl вообще не был инициализирован.
Рассмотрим простой пример, иллюстрирующий элементарный способ использова-
ния компонента TUpdateSQL. Откройте новое приложение VCL и на его главной форме
разместите объекты TDBGrid (DBGridl), TDataSource (DataSourcel), TQueryl (Queryl)
и TUpdateSQL (UpdateSQLl). Свяжите объекты DBGridl, DataSourcel и Queryl в одну
информационную цепочку. Для свойства DatabaseName объекта Queryl укажите значение
MyData, а для его свойства SQL задайте строку:
SELECT CategoryNo, Category, Description
FROM Category
Этот запрос будет выбирать все записи из таблицы Category (для всех полей). Для
свойства RequestLive оставьте его значение по умолчанию —false. Набор данных в этом
случае должен быть открыт только для чтения. В качестве значения свойства UpdateObject
укажите UpdateSQLl, а для свойства CachedUpdates— значение true. Так как свойство
RequestLive имеет значение false, набор данных, возвращаемый в этом случае объектом
Queryl, не может быть подвергнут редактированию. Однако предпринятые нами действия
обеспечивают возможность модифицировать записи как угодно. Мы можем добавлять или
удалять их, а также модифицировать информацию в них.
184 Borland C++ Builder 6. Разработка приложений баз данных

Для завершения настройки приложения-примера воспользуемся редактором ком


понента TUpdateSQL. Отобразить его на экране можно при помощи пункта UpdateSQl
Editor контекстного меню объекта UpdateSQLl (не забудьте указать этот объект в каче
стве значения свойства UpdateObject объекта Query 1). Редактор компонента TUpdateSQL
откроется на вкладке Options. Основные элементы управления этой вкладки — Tablt
Name (имя таблицы), Key Fields (ключевые поля) и Update Fields (обновляемые поля)
Комбинированный список Table Name будет содержать одно из имен таблиц, фигурирую-
щих в строке запроса объекта Queryl. Здесь можно ввести имя другой таблицы и нажать
кнопку Get Table Fields — если имя таблицы известно BDE, то списки Key Fields и Update
Fields будут заполнены соответствующими значениями (см. рис. 3.28). В списке Key Fields
можно выбрать ключевые поля, т. е. те, по которым будет производиться идентификация
записей. В простейшем случае здесь нужно просто выбрать имя первичного ключа табли-
цы. В списке Update Fields можно выбрать поля, которые будут подвергаться модифика-
ции. Необходимые поля можно выбрать в обоих списках при помощи несвязного выделе-
ния (посредством клавиш Shift или Ctrl).
ятвятщ
: !
i

Рис. 3.28. Редактор компонента TUpdateSQL, открытый на вкладке Options

Opto» .SOL

Category - :Cetegory.

,:,:;.
Description * Description

Can
ColegoryNo • :OLD_CategcryNo

~1

Рис. 3.29. Вкладка SQL редактора объекта TUpdateSQL


Поля, указанные в списке KeyFields, будут использоваться для иденитификации запи-
си. Чаще всего здесь указываются поля, представляющие первичный индекс. Для нашего
примера в этом списке нужно указать поле CategoryNo. В списке Update Fields нужно
выбрать все поля, которые будут подвергаться модификации — в данной ситуации это
Глава 3. Использование механизма ВОЕ (продолжение) 185

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. Разработка приложений баз данных

Это стандартный (минимальный) набор функций, используемых при работе в режиме


кэширования изменений.
После компиляции приложения вы сможете удалять, добавлять и редактировать
записи таблицы Category, несмотря на то, что свойство RequestLive соответствую-
щего набора данных имеет значение false (т.е. он предназначен только для чтения).
Внесенные изменения вступят в силу после нажатия кнопки Apply Updates. Отменить
их можно будет, нажав Cancel Updates. Главная форма описанного приложения-при-
мера представлена на рис. 3.30.

1 Принтеры :Принтвры струйные матричные, лазерные


2 Сканеры Сканеры планшетные
3 Платы материнские Платы материнские а ля Intel и AMD
4 Процессоры Процессоры Intel. Arnd и VIA
5 Модули помати Модупи помдти DIMM, DIMM DDR и RIMW
Видеокарты от различных производителей
7 Мониторчик-и ЭЛТ- и ЖКИ-мониторы
8 Винчестеры Винчестеры Fujitsu, Samsung и Wettern Digital
9 Устройства ввода Клавиатуры, мышки, джойстики и т.о..
10 Мультимедиа Звуковые карты, колонки, приеоды СП4ЧОМ
11 Модемы .Внешние и внутренние модемы
12 Сетевое оборудовав Сетевые к 1|.хооы. коммутаторы

Рис. 3.30. Простейшее приложение, иллюстрирующее использование


режима кэширования изменений совместно с компонентом TUpdateSQL.
Набор данных, отображаемый сеткой, предназначен только для чтения,
но подвержен модификации
Более сложный случай модификации немодифицируемых наборов данных (предна-
значенных только для чтения) — это случай, когда необходимо изменять записи сразу
нескольких связанных между собой таблиц. Такой набор данных необновляем по опреде-
лению (в соответствии со стандартом SQL-92), но его тоже можно модифицировать с ис-
пользованием режима кэширования изменений и объектов TUpdateSQL. В этом случае
используется несколько объектов TUpdateSQL - по одному на каждую таблицу, которую
нужно изменять. Эти приемы мы рассмотрим на несложном примере.
Предположим, у нас есть набор данных, содержащий наименование товара, его кате-
горию, индекс, а также цену и количество товара на складе. Чтобы получить такой набор
данных, нужно объединить в одном запросе три таблицы — Items, Category и Storage.
Такой набор невозможно редактировать, даже если задать для свойства RequestLive значе-
ние true. Предположим также, что необходимо предоставить пользователю возможность
редактировать значения цены и количества, запретив редактировать остальные поля,
а также не позволяя вставлять или удалять записи.
Итак, создайте новое приложение и разместите на его форме сетку и объекты
TDataSource и TQuery. Кроме того, нам понадобится два объекта TUpdateSQL, так
как обновлять придется две таблицы — Storage (поле Qty) и Items (поле Price). Как
обычно, оставьте все имена по умолчанию. Сетку подключите к DataSourcel, а объект
DataSourcel— к. Query 1. Объект Query 1, в свою очередь, подключите к базе данных
MyData и задайте для его свойства SQL следующую строку:
SELECT Category.Category, Items.Item, Items."Index", Storage.Qty,
Items.Price, Items.ItemNo, Storage.StoreltemNo
Глава 3. Использование механизма ВОЕ (продолжение) 187

FROM Items INNER JOIN Storage ON (Items.ItemNo = Storage.Item)


INNER JOIN Category ON (Items.Category = Category.CategoryNo)
ORDER BY Items.Item
Этот запрос возвращает набор данных, содержащий текстовое представление катего-
рии, наименование и индекс каждого товара, имеющегося на складе. Для каждого товара
приведена цена и количество на складе. Набор данных отсортирован по возрастанию по
наименованиям товаров.
На следующем шаге необходимо запретить редактирование всех полей, кроме Qty и Pri -
се. Откройте редактор полей, дважды щелкнув на пиктограмме объекта Query 1. В список
постоянных полей добавьте определения всех полей набора данных. Поля ItemNo и
StoreltemNo в итоговом наборе нам не нужны, поэтому задайте для их свойств Visible зна-
чения false (эти поля понадобятся нам для настройки объектов TUpdateSQL). В редакторе
полей выделите поля Item, Category и Index и задайте для их свойств ReadOnly значение
true — это предохранит их от редактирования. В заключение, для свойств CachedUpdates
и Active задайте значения true, переведя тем самым набор данных в режим кэширования
изменений и открыв его.
Свойство UpdateObject может содержать ссылку только на один объект TUpdateSQL,
а у нас их два. Поэтому пользоваться этим свойством в своем приложении мы не будем.
Вместо этого для подключения объектов TUpdateSQL к набору данных Queryl будем
использовать свойство DataSet. Это свойство компонента TUpdateSQL доступно толь-
ко в режиме выполнения приложения. Создайте обработчик события OnCreate формы
и введите в его тело текст:
UpdateSQLl->DataSet=Queryl;
UpdateSQL2->DataSet=Queryl;
Я, пожалуй, поспешил, сказав, что пользоваться свойством UpdateObject компонен-
та TQuery мы не будем. На самом деле им удобно пользоваться в режиме конструктора
для настройки объектов TUpdateSQL. Итак, задайте для свойства UpdateObject значе-
ние UpdateSQLl и откройте редактор для этого объекта (UpdateSQL Editor) на вкладке
Options. В поле со списком Table Name введите имя таблицы Items и нажмите кнопку Get
Table Fields. В списке Key Fields выберите поле ItemNo, а в списке Update Fields — Price.
Нажмите кнопку Generate SQL и переключитесь на вкладку SQL. Оператор для переклю-
чателя Modify должен выглядеть следующим образом:
Update Items
Set
Price = :Price
Where
ItemNo = :OLD_ItemNo
Операторы SQL для переключателей Insert и Delete нужно удалить, так как предпо-
лагается не давать пользователю возможность добавлять или удалять записи. Но чтобы
реализовать эти ограничения, необходимо воспользоваться событиями Beforelnsert и Befo-
reDelete. В каждый из обработчиков этих событий введите строку:
Abort();
Таким образом вы лишите пользователя возможности добавлять и удалять записи на-
188 Borland C++ Builder 6. Разработка приложений баз данных

бора данных Query 1.


Далее следует настроить объект UpdateSQL2. Укажите его имя в качестве значеш
свойства UpdateObject объекта Query 1 и откройте редактор UpdateSQL2. В поле со cm
ском Table Name укажите таблицу Storage и нажмите кнопку Get Table Fields. В спиа
Key Fields выделите поле StoreltemNo, а в списке Update Fields — поле Qty. Нажми:
кнопку Generate SQL и переключитесь на вкладку SQL. Оператор для модификации з;
писей (переключатель Modify) должен выглядеть так:
Update Storage
Set
Qty = :Qty
Where
StoreltemNo = :OLD_StoreItemNo
Остальные операторы, как и в предыдущем случае, следует удалить. В заключение н
забудьте очистить свойство UpdateObject объекта Query 1. Иначе будет предпринята втора;
попытка обновить таблицу.
Последнее звено описываемой технологии — вызов запросов на обновление из обра
ботчика события OnUpdateRecord объекта Query 1. Для этого можно воспользоваться ил*
методом ExecSQL, или методом Apply. Последний больше подходит для параметризован-
ных запросов, поэтому лучше применить его:
if(Update«ind == ukModify)
{
UpdateSQLl->Apply(UpdateKind);
UpdateSQL2->Apply(UpdateKind);
UpdateAction=uaApplied;
>
Прежде чем обновить таблицы, неплохо было бы проверить, какой из видов обнов-
ления выполняется для текущей записи. В конце обработчика события OnUpdateRecord
нужно присвоить переменной-аргументу UpdateAction значение uaApplied, чтобы принять
обновления.
Вот, в принципе, и вся технология. Остается только добавить на форму объекты
TDBNavigator и две кнопки TButton, настроив их так же, как и в предыдущем примере.
И не забудьте перед закрытием формы проверить значение свойства UpdatesPending — ес-
ли есть несохраненные изменения, нужно запросить у пользователя разрешение сохра-
нить или отменить их:
void fastcall TForml::FormCloseQuery(TObject *Sender, bool &CanClose)
{
if((Queryl->CachedUpdates) && (Queryl->UpdatesPending))
{
switch(Application->MessageBox(
"Остались несохраненные изменения! Сохранить?",
"Внимание!", MB_YESNOCANCEL + MB_ICONQUESTION))
{
case IDYES:
{ Queryl->ApplyUpdates();
Глава 3. Использование механизма ВОЕ (продолжение) 189

Queryl->CommitUpdatesQ ;
break; }
case IDCANCEL:
{ CanClose=false; break; }
case IDNO:
{ Queryl->CancelUpdates(); break; }

Откомпилируйте приложение и попробуйте его в действии. Оно должно работать так,


как мы и запланировали (см: рис. 3.31).

"1-1 '1м . .••••!-


j •.
"* l^j -;-
Gb Fujitsu MNN21 МАТ Винч гетеры 70101 10 98.00S *г^;Г
;
1 3.8") LG SludioWotks 563N Моии торы 60101 56 13.00$"- *':||
;1 3 В") LG StudraWorks 575E Мони торы 60102 : и! 16.0DS ;. Г\':
Semsung 1 51 E Моии торы 60002 : ее 9400$ ;л
Semsurigl 61 8M Мони торы воооч ! 99 12.00$ 1
Samsung 1 51 i.297 Мониторы 8700$iV-:";"!
:
J60001 86 '
Samsung 550E :Мони торы ^ 60105 37. ^0.00$ -"- £
:
Samsung 561 S • Мониторы 601 01 98 : гэоо$ • ;
|
: :
jcmtron 56E Мони торы :60103 7 19.00$ ';
Sony SDM-N50 LCD Мони торы 'BOQOI 35 !61,OQJ X:\*j$.
"" 69'""
16")Senisung753DFX Мони торы 61105
:
шо$"::";;.:
.1 5") Samsung 753S Мониторы 61103 26 6400$ '^i'l

Рис. 3.31. Приложение-пример, позволяющее редактировать значения полей


двух связанных между собой таблиц

Компонент 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. Разработка приложений баз данных

завершить разработку приложения-примера, расположите на форме кнопку (компонеи


TButton), задайте для нее в качестве подписи текст Создать таблицу и введите в тело об
работника события нажатия (OnClick) текст:
Queryl->Close();
BatchMovel->Execute();
DataSourcel->DataSet=Tablel;
Tablel->0pen();
В этом отрывке кода вначале закрывается набор данных Query 1. Затем выполня
ются действия, запланированные при помощи объекта BatchMovel. В конце источни]
данных DataSourcel переключается на объект Tablel и соответствующий набор дан
ных открывается.
Запустите получившееся приложение на выполнение. Попробуйте поэксперименти
ровать, нажимая кнопку Создать таблицу. Можете убедиться, что объект TBatchMove ш
дружит с русским языком (см. рис. 3.32).

Hew' 'Category |atj/V :4Bta. |a»l


> HI MMMM 51 189 i. (
^ Samsung ML-1 210 tttwn 88 203 DOS 17.8Б400»
I Epson EPL4900L ItttttH I3J 239001 3.107001
^ Canon LBP-810 tttttat 0: 21 5 OOt 0001
^ Brother HL-1240 Mono Laser Wttttt 2; 2Б6 OOt 532.001
Epson AcuLaser ClOOOw HfffW 26; 1.349.001 35.074001
Срьоп AcuLeser C2000 twtttt 98: 2,156 OOt 2ir28800t
^ HP LaserJet ICOOw «/«*« 30; 224001 6.72COOt
; HP LaserJet 1200 tttHftt : 24; 332001 7.966001
sHPLeserJetl220 панн 2; 440001 080 OOt
I HP LaserJet 3200 tttttttt 86: 637.00t 61.152001
mum
IHP LaserJet 2200 86 761.001 66.968 OOt
"j
Рис. 3.32. При использовании компонента TBatchMove
все символы русского языка заменяются на #

Клиентские наборы данных


Клиентские наборы данных, кроме всего прочего, реализуют «портфельную» техно-
логию работы с данными, которая также называется MyBase. Это красноречивое название
полностью отражает сущность технологии. При ее использовании данные из таблицы
локальной или удаленной БД копируются на локальный компьютер. Все изменения на-
бора данных не отправляются сразу в исходную базу данных, а сохраняются в файле на
локальном компьютере. Далее все изменения можно или отправить в исходную БД, или
отменить. Таким образом, клиентские наборы данных автоматически работают в режиме
кэширования изменений.
Такой подход позволяет, один раз получив данные от БД, редакторовать их, не поддер-
живая свяь с исходной БД. Благодаря этому можно снизить нагрузку на сеть, и, кроме того,
набор данных, сохраненный в формате xml ник cds, можно скопировать на свой ноутбук
и взять его с собой в командировку. Потом можно снова подключиться к базе данных и за
одну транзакцию внести все изменения в таблицы.
Следует учесть, что у технологии MyBase есть и недостатки. Они, как это часто быва-
Глава 3. Использование механизма ВОЕ (продолжение)

ет, — продолжение ее достоинств. При длительном отсутствии связи с исходной БД все


данные, с которыми вы работали, могут стать неактуальными, а также может измениться
структура таблиц. Не исключены и другие подобные неприятности. Кроме того, все сде-
ланные вами изменения не будут доступны для других пользователей до тех пор, пока вы
не сохраните их в исходных таблицах.
Для реализации технологии MyBase в BDE предназначены компоненты TClientDataSet
и TBDEClientDataSet. В их использовании есть существенные различия. Подробнее об
этом можно узнать из справочной системы C++ Builder, а в этом разделе будут описаны
простейшие приемы использования TClientDataSet и TBDEClientDataSet.
Чтобы подключить объект TClientDataSet к набору данных, нужно указать в его свой-
стве ProviderName имя одного из объектов типа TProviderName, который, в свою очередь,
следует подключить к нужному набору данных (таблице, запросу, клиентскому набору
данных). Но проще воспользоваться пунктом Assign Local Data контекстного меню объек-
та TClientDataSet. В этом случае не нужно размещать на форме или модуле данных объект
TProviderName. В появившемся диалоговом окне нужно выбрать имя одного из имеющих-
ся объектов — наборов данных. После этого можно подключать к объекту TClientDataSet
объект TDataSource и затем — любые элементы управления данными. При помощи пунк-
та Load from MyBase table можно загрузить данные из предварительно сохраненного на
диске файла.
Как только данные загружены, в контекстном меню объекта TClientDataSet появятся
новые пункты. При помощи пункта Clear Data можно отключиться от источника данных.
Пункты меню Save to MyBase Xml table, Save to MyBase Xml UTF8 table и Save to binary
MyBase file позволяют сохранить набор данных в файле. Первые два пункта сохраняют
данные в формате xml, а с помощью третьего можно сохранить набор данных в двоичном
файле с расширением cds.
При выполнении приложения записать все сделанные изменения в исходную БД
можно при помощи вызова метода ApplyUpdates. Для каждой сохраняемой записи будет
вызываться обработчик события BeforeApplyUpdates (конечно же, если он определен).
Отменить все сделанные изменения можно при помощи метода CancelUpdates.
Объект типа TBDEClientDataSet к нужному набору данных можно подключить при по-
мощи свойств DBConnection и CommandText. В свойстве DBConnection нужно указать имя
одного из имеющихся объектов типа TDatabase. Свойство CommandText может содержать
строку оператора SQL. Тогда объект TBDEClientDataSet будет использовать возвращаемый
этим запросом набор данных. В свойстве FileName можно указать имя файла в формате
xml или cds — в этом случае свойство CommandText игнорируется, а набор данных полу-
чается прямо из указанного файла.
Более подробно об использовании портфельной модели данных будет рассказано
в конце следующей главы.

Резюме
В этой главе было продолжено изучение основ использования BDE. Внимание
уделялось главным образом элементам визуализации и управления данными, а также
приемам использования компонента TQuery. Подробно был рассмотрен компонент
TDBGrid (сетка) — чаще всего используемый в приложениях БД элемент управления.

7 Зак. 319
194 Borland C++ Builder 6. Разработка приложений баз данных

При описании компонента TQuery приводилась подробная информация о локально]


SQL. Операторы, написанные на языке SQL, используются в качестве значений свой
ства SQL компонента TQuery.
Кроме того, были рассмотрены приемы управления сеансами связи (компонен
TSession) и отдельными соединениями с БД (компонент TDatabase). В заключение объяс
няется, как пользоваться объектами типа TUpdateSQL (обновление наборов данных тольк
для чтения), TBatchMove (групповые операции с наборами данных), а также клиентским
наборами данных (компоненты TClientDataSet и TBDEDataSei).
Клиент-серверные базы данных
ВЭТОЙШШЕ
- Утилита IBConsole
Утилита ISQL
Типы данных Interbase
Встроенный (Embedded) SQL
Работа с основными объектами Interbase
Планирование системы безопасности в Interbase
Подключение к базам данных при помощи механизмов доступа dbExpress,
Interbase Express и ADO.
Однонаправленные (unidirectional) наборы данных dbExpress
Обновление необновляемых наборов данных
Клиентский набор данных TSQLClientDataSet
Компонент TSQLMonitor
Резюме

П
ри клиент-серверной организации приложений база данных управляется специ-
альной программой (называемой СУБД, или сервером БД), в то время как клиент-
ское приложение может воздействовать на БД и содержащиеся в ней объекты или
информацию, посылая серверу БД запросы. При помощи этих запросов можно создать
базу данных, таблицы, индексы, а также другие объекты, допускаемые форматом кон-
кретной БД. Кроме того, можно получать необходимые данные из БД и воздействовать на
данные, хранящиеся в таблицах. Сервер БД вместе с базой данных обычно расположены
на удаленном сервере, однако они могут находиться и на сетевом или локальном ком-
пьютере. В зависимости от этого клиентское приложение может использовать для связи
с сервером БД локальную сеть или удаленное подключение.
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 поставляется несколько полезных утилит, однако особого


раздела достойна только одна из них — IBConsole.
Глава 4. Клиент-серверные базы данных 197
Консоль Interbase '$ IBConeole

(IBConsole) !.,<»„,>»,. 1м/

Консоль Interbase, на мой взгляд,


- это не очень удачный интерфейс не-
скольких полезных утилит, которые ранее
могли использоваться как отдельные при-
ложения (в частности, Windows ISQL).
Доступ к консоли Interbase можно полу-
чить при помощи меню Пуск \ Программы
| «Название группы Interbase». Другой
вариант — запустить на выполнение Рис. 4.1. Окно приложения IBConsole
программу ibconsole.exe', она должна сразу после его открытия
находиться в подкаталоге Bin каталога,
в который был инсталлирован Interbase. В результате на экране будет отображено окно
консоли, разделенное вертикальным сплиттером (перемещаемой границей) на две части
(см. рис. 4.1). В левой части, называемой деревом соединений (Connections Tree), в иера-
рхическом виде представлены различные объекты (серверы, базы данных, таблицы, поля
и т.д.). В правой части окна перечислены все свойства выбранного на панели Connection
Tree объекта, или действия, которые можно выполнить с объектом. Например, для базы
данных на правой панели будет отображен список,действий, которые можно с ней про-
делать: получить список пользователей, получить статистику для БД и значения всех ее
свойств, закрыть соединение с БД, и т.д.
Сразу после запуска IBConsole дерево соединений будет содержать только один узел
верхнего уровня — InterBase Servers. Этот узел должен содержать список всех зареги-
стрированных серверов. Прежде чем что- I Register Server and i
то делать, необходимо зарегистрировать
хотя бы один сервер. Для этого можно г Server Information —
воспользоваться пунктом Register... меню
I* Local.Server Demote Server;
Server или дважды щелкнуть мышкой

г
на имени узла верхнего уровня (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

Server Information - Server: Local Setver

Г |.ocal Server Bemcrte Server - Database^

Server Name: £Jetwork Protocol: D:\Program Files\Common Files\Borlan

|lB6Server (TCP/IP file.


employee.gdb
Alias Name:
ANas Name:
;B6
F Save Alias Information
Qescription:
(Локальный сервер
:
P Save Alias Information

Рис. 4.З. Регистрация удаленного сервера Рис. 4.4. Регистрация и подключение


к базе данных
Чтобы закончить регистрацию удаленного сервера, нужно указать имя пользователя
и пароль (соответственно, SYSDBA и masterkey), после чего нажать кнопку ОК. Сервер
будет зарегистрирован и будет произведена попытка подключиться к нему с использова-
нием выбранного протокола.
Если регистрация и подключение к серверу прошли успешно, узел, соответствующий
серверу (Local Server — в случае локального сервера), будет содержать перечень объектов,
имеющих отношение к серверу. К этим объектам относятся базы данных (Databases), сер-
тификаты (Sertiftcates) и пользователи (Users). Кроме того, список объектов узла сервера
содержит позиции Backup и Server Log. При помощи первой из них можно заархивировать
базу данных (Backup) или восстановить ее из архива (Restore). При помощи объекта Server
Log можно просмотреть Log-файл сервера.
Посредством пункта Logout меню Server можно прервать сеанс связи, оставив сервер
зарегистрированным. Это позволит не поддерживать удаленное соединение, когда оно
не нужно. Снова подключиться к серверу можно при помощи пункта Login меню Server,
а отменить регистрацию сервера — пункта Un-Register того же меню. Тогда выбранный
Глава 4. Клиент-серверные базы данных 199
сервер исчезнет из списка узла InterBase Servers, а при следующем запуске IBConsole при-
дется регистрировать сервер заново. Если регистрация отменена не была, то после переза-
пуска IBConsole нужно только активизировать подключение (пункт Login).
Примерно такая же схема используется и при работе с базами данных. Как только сер-
вер зарегистрирован и установлено подключение, можно воспользоваться контекстным
меню объекта Databases (или пунктами меню Database). С его помощью можно создать
новую базу данных (пункт Create Database) или подключиться к существующей БД (пункт
Register...) при помощи диалогового окна, изображенного на рис. 4.4. При выборе пункта
Create Database на экране будет отображено окно, показанное на рис. 4.5.
J Create Database

Server: Local Server


F:le(s):
FilenamefeL
p:\Data\Test.gdb

Page 'Size
Default Character Set listens
SQL Dialect

JTest

| OK I Cancel
I " i '""• ' •

Рис. 4.5. С помощью диалогового окна Create Database


можно создать новую базу данных и зарегистрировать ее
В верхней части этого окна размещена сетка File(s) (обычная таблица), состоящая
из двух столбцов. В первом столбце следует указать имя файла (и путь к нему) для
хранения новой базы данных. Можно указать несколько файлов БД, объединенных
под одним псевдонимом. Имя псевдонима нужно указать в элементе редактирования
Alias (псевдоним). Под этим именем база данных будет фигурировать в дереве соеди-
нений (левая панель окна консоли Interbase). Кроме имени файла и пути в таблице
File(s) можно указать размер страницы для каждого файла БД (если этих файлов не-
сколько, то указать размер страницы нужно обязательно). От этого значения зависит,
сколько записей будет размещено в пределах одной страницы. Именно столько за-
писей будет заблокировано при одновременном доступе нескольких пользователей
к одной записи. Чуть ниже расположена таблица, при помощи которой можно задать
200 Borland C++ Builder 6. Разработка приложений баз данных

индивидуальные характеристики каждого из файлов (размер страницы, набор


волов по умолчанию, диалект SQL). Более подробно об этих характеристиках буде
сказано чуть далее.
Если флажок Register database установлен, то после нажатия кнопки ОК база данны
будет создана в указанном каталоге (каталог уже должен существовать к моменту создани
БД), а также зарегистрирована, и с ней будет установлена связь. Для прекращения сеанс
связи и отмены регистрации БД служат пункты контекстного меню Disconnect и Unregis
ег соответственно. Зарегистрировать БД можно при помощи пункта Register (см. рис. 4.4
а подключиться к БД — при помощи команд Connect и Connect As. Последняя команд
служит для подключения к БД под новым именем. Обратите внимание на правую панел
окна консоли Interbase. Если на панели дерева подключений (левая панель) выбрана баз
данных, то на правой панели будет отображен список действий (Action), которые можн
выполнить в данный момент. Чтобы выполнить какое-то действие, нужно просто дважд]
щелкнуть мышкой на его наименовании.
Как только база данных зарегистрирована и открыта (т.е. установлено подключена
к БД), станет доступен список всех ее объектов (см. рис. 4.6). К этим объектам относите
таблицы (Tables), индексы (Indexes), представления (Views), домены (Domains), храни
мые процедуры (Stored Procedures), внешние функции (External Functions), генераторь
(Generators), исключения (Exceptions), Blob-фильтры (Blob Filters) и роли (Roles). Cnncoi
этих объектов дает первое представление о том, насколько отличаются локальные базь
данных от БД в формате SQL.

^Console View Sewer {Database loots

Ш :*§ ЩШ-:^ '

RI-§0 InterBase Servers


*-
Р
'

.
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.

• Metadata for ITEMS


Edit

: /* Table: ITEMS, Owner: SYSDBA V

! CREATE TABLE ITEHS


j(
ITEM_ID INTEGER HOT HULL,
SPEC_ID_REF INTEGER,
CURRENCY_ID_REF IHTEGER,
ITEH OARCHAR(IOO) ,
PRICE NUMERIC (9, 2) ,
DISCOUNT NUMERICH, 2),
| PRIMARY KEY (ITEM_ID)
I);
I SET TERH Л ;

•i/* Triggers only will work for SQL triggers */


i
JCREATE TRIGGER CASCADE_ITEH FOR ITEHS
(ACTIVE BEFORE INSERT POSITION 0

.1 * (Modified i

Рис. 4.7. Метаданные для таблицы Items базы данных employee.gdb


По умолчанию сервер Interbase имеет одного зарегистрированного пользователя. Имя
этого пользователя — SYSDBA, пароль — masterkey. Из соображений безопасности сразу
после инсталляции сервера Interbase следует изменить пароль для пользователя SYSDBA.
Для этого можно воспользоваться контекстным меню объекта Users выбранного сервера.
Если выделить объект Users, на правой панели окна консоли Interbase будет отображен
список имеющихся пользователей. Выберите пользователя SYSDBA и затем из контекстно-
го меню объекта Users — пункт Modify User. Введите в элемент редактирования Password
пароль и продублируйте его в элементе Confirm Password. Можно указать и дополните-
льную информацию — имя, отчество и фамилию.
В контекстном меню объекта Users есть еще два пункта. При помощи пункта Add User
можно добавить нового пользователя, а пункт Delete User предназначен для удаления
пользователя из списка. Обратите внимание на то, что этот пункт меню недоступен для
пользователя SYSDBA. Удалить его нельзя, потому что некоторые операции с базой дан-
ных доступны только для него.
202 Borland C++ Builder 6. Разработка приложений баз данных

И в заключение краткого обзора возможностей консоли Interbase следует сказать №


сколько слов о сертификатах.
Сертификат задает уровень полномочий и возможностей (например, количество юн
ентов), предоставляемых пользователю данного сервера Interbase. Вы можете расширю
эти возможности, приобретя соответствующую лицензию и добавив новый сертифика
Добавить новый сертификат можно при помощи пункта Add Certificate контекстного меш
объекта Certificates. В открывшемся диалоговом окне нужно указать ID сертификата и ег
ключ в соответствии с лицензионным соглашением.

Утилита 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

"jGlient dialect 1 (transaction is ACTIVE. jAutoDDLON

Ran j Statistics)

Рис. 4.8. Утилита ISQL в действии

в файле с расширением 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. Разработка приложений баз данных

Базы данных Interbase


Базы данных Interbase, как, впрочем, и остальные БД в формате SQL, очень сильнс
отличаются от рассматривавшихся в предыдущих главах локальных БД. Первое, чтс
бросается в глаза: база данных Interbase хранится в одном файле, в котором содержатся
все таблицы, индексы и другие объекты. Рекомендуется, хотя и не обязательно, исполь
зовать для файлов БД Interbase расширение gdb. Среди объектов, которые могут содер
жаться в БД Interbase, пользователям локальных БД знакомы только таблицы и индексы
Отличаются также и типы данных, поддерживаемые сервером Interbase. Следует такж«
учесть, что к клиент-серверным БД одновременно может быть подключено много кли-
ентов, использующих локальную сеть или удаленную связь. Из-за всего этого к прило-
жениям БД в формате SQL предъявляются особые требования, отличные от требований
к приложениям локальных БД.
Итак, начнем изучение всех этих особенностей с типов данных, поддерживаемых
Interbase.

Типы данных, поддерживаемые Interbase


Прежде чем создавать и использовать таблицы, индексы и другие объекты Interbase, необ-
ходимо разобраться, какие типы данных есть в вашем распоряжении. Interbase поддерживает
десять типов данных. Ниже приведены их краткие характеристики и описание.
О Blob. Сокращение от Binary Large Object (большой двоичный объект). Этот тип предна-
значен для хранения больших объемов текстовой информации, графических данных,
видеоклипов, оцифрованного звука и тому подобных данных. Какие именно данные
хранятся в объекте Blob, можно узнать, проверив его подтип. Подтип объекта типа
Blob может иметь одно из шести значений: 0 (неструктурированные данные или дан-
ные неопределенного типа), 1 (текст), 2 (двоичное языковое представление), 3 (список
управления доступом), 4 (зарезервировано), 5 (закодированное описание метаданных
таблицы), 6 (описание транзакций, в которых используется обращение к нескольким
БД, и которые закончились неудачей). Подтипы от 2 до 6 — экзотические, обычно ис-
пользуются только подтипы 0 и 1. Можно определить и свои собственные подтипы.
В этом случае подтип должен иметь отрицательное значение (в пределах от -32 678 до
-1). Все положительные значения подтипа зарезервированы для Interbase.
Данные типа Blob имеют переменный размер, но не более 64 Кб, так как они хранятся
в сегментах. В самой таблице в соответствующем поле хранится 64-битовое значение,
ссылающееся на конкретное значение типа Blob (Blob ID). Кроме того, Blob ID может
ссылаться на таблицу указателей, каждый из которых в свою очередь ссылается на зна-
чение типа Blob.
О CHAR(n) или CHARACTER(n). Строка фиксированной длины в п символов. Длина
строки может быть от 1 до 32 767 байтов.
Q DATE. Может содержать значение даты в диапазоне от 1 января 100 года нашей эры до
29 февраля 32 768 года. Размер — 32 бита.
Q ОЕС1МАЬ(точностъ, размер). Число с десятичной точкой. Общее количество цифр
числа задается значением аргумента точность (десятичная точка в этом значении не
учитывается), а количество цифр после десятичной точки указывается аргументом раз-
мер. Например, объявление DECIMAL(5,2) задает значение в формате ddd.dd. В базе
Глава 4. Клиент-серверные базы данных 205

данных сохраняется как минимум количество знаков, указанных аргументом точ-


ность. Если значение имеет меньшее количество знаков, сохраняется столько знаков,
сколько указано. Аргумент точность может принимать значение от 1 до 18, в то время
как аргумент размер может принимать значение от 0 до 18. Понятно, что значение
аргумента размер должно быть меньше значения аргумента точность. В зависимости
от конкретных значений объекты этого типа могут занимать в памяти 16, 32 или 64
бита. Я вынужден извиниться за неудачность названий аргументов, однако именно так
звучит перевод терминов, которые используются в документации по Interbase.
О DOUBLE PRECISION. Число с плавающей точкой двойной точности (стандарт IEEE),
состоящее из 15 цифр. Диапазон возможных значений (по модулю): от 2.225 х 10-3°8 до
1.797 х 10308. размер — 64 бита.
Q FLOAT. Число с плавающей точкой (число IEEE), состоящее из 7 цифр. Диапазон воз-
можных значений (по модулю): от 1.175 х Ю-38 до 3.402 х Ю3^. Размер — 32 бита.
Q INTEGER. Целое со знаком, занимающее в памяти 32 бита (длинное целое). Диапазон
представляемых значений: от -2 147 483 648 до 2 147 483 647.
G NUMERIC (точность, размер). Соответствует типу DECIMAL, за исключением то-
го, что в базе данных будет храниться столько цифр, сколько указано аргументом
точность.
Q SMALLINT. Целое со знаком, занимающее в памяти 16 бит. Диапазон представляемых
значений: от -32 768 до 32 767.
О TIME. Значения этого типа представляют время от 00:00:00.0000 до 23:59:59.9999.
Размер значений этого типа — 32 бита.
Q TIMESTAMP. Достаточно распространенный тип данных в БД разных типов, пред-
ставляющий одновременно и дату, и время. Размер — 64 бита. Диапазоны значений
времени и даты соответствуют типам DATE и TIME, описанным ранее.
Q VARCHAR(n), или CHAR VARYING(n), или CHARACTER VARYING(n). Строка пере-
менной длины. В памяти будет сохранено ровно столько символов, сколько содержит
текущее значение. Аргумент п может принимать значения от 1 до 32 765.

Несколько слов о языке SQL


Так как Interbase — это сервер SQL, то понятно, что создавать, удалять и моди-
фицировать как саму БД, так и ее объекты следует при помощи запросов SQL. Язык
запросов SQL, поддерживаемый сервером Interbase, значительно отличается от рас-
сматривавшегося в предыдущей главе локального SQL. Связано это в первую очередь
с необходимостью поддерживать новые (по сравнению с локальными БД) объекты.
Еще одна особенность: Interbase поддерживает три варианта SQL (встроенный SQL,
интерактивный и динамический) и три его диалекта (1, 2 и 3). Ко встроенному SQL
(Embedded SQL) относятся обычные операторы SQL, позволяющие манипулировать
данными и определять объекты базы данных. Динамический SQL (Dynamic SQL,
или DSQL) кроме этого имеет возможности для формирования и выполнения запро-
са при работе программы. DSQL позволяет напрямую взаимодействовать с сервером
Interbase, минуя механизмы доступа. Интерактивный SQL поддерживает возможно-
сти, необходимые для нормальной работы утилиты 1SQL. В дальнейшем речь пойдет
в основном о встроенном SQL, который мы будем именовать просто SQL.
206 Borland C++ Builder 6. Разработка приложений баз данных

Что касается поддержки трех различных диалектов, то диалект 1 совместим с вариан


том языка SQL, поддерживаемым Interbase 5.6 и более ранними версиями. Диалект 3 со
вместим со стандартом SQL-92 и обеспечивает ряд новых возможностей. Диалект 2 — от
ладочный: при его использовании выдаются предупреждающие сообщения, если встреча
ются возможности, характерные только для диалекта 3. В дальнейшем будет описыватьс.
диалект 3, причем речь не будет идти о его новых возможностях. За информацией о ни:
следует обратиться к справочной системе Interbase.
Далее при описании операторов SQL будут использоваться следующие соглашения
В квадратные скобки заключаются необязательные элементы. В фигурных скобках пере
числяются несколько элементов, из которых должен использоваться только один. ЭТР
элементы разделяются вертикальной чертой («|»). Идентификаторы, обозначающие ка
кие-либо конструкции, заключаются в угловые скобки (<>). Все перечисленные элемента
описания не должны фигурировать в тексте операторов.

Работа с базами данных


Базу данных можно создать, удалить или модифицировать при помощи запроса SQL
точно так же, как и любой другой объект. Создать базу данных можно при помощи следу-
ющего оператора:
CREATE {DATABASE | SCHEMA} 'СпецификацияФайла'
[USER 'ИмяПользователя' [PASSWORD 'Пароль']]
[PAGE_SIZE [=] РазмерСтраницы]
[LENGTH [=] РазмерФайла [PAGE[S]]]
[DEFAULT CHARACTER SET НаборСимволов]
[<ВторичныйФайл>];
Для создания базы данных можно использовать ключевое слово DATABASE, или
SCHEMA. Следует воспринимать оба ключевых слова как синонимы.
СпецификацияФайла — указанное в одинарных кавычках имя файла базы данных,
включая идентификатор диска и полный путь. Этот элемент задает первичный файл для
хранения БД. Для хранения базы данных кроме первичного файла могут использоваться
один или несколько вторичных файлов. Вторичные файлы можно задать при помощи кон-
струкции <ВторичныйФайл>.
Вслед за ключевыми словами USER и PASSWORD в одинарных кавычках указывают-
ся, соответственно, имя пользователя и пароль. Пользователь с таким именем и паролем
должен быть зарегистрирован на сервере, где будет располагаться БД. Только первые 8
символов пароля — значащие. Ниже приведен простой пример создания БД:
CREATE DATABASE 'D:\Data\MyData.gdb' USER 'SYSDBA' PASSWORD 'masterkey';
При помощи ключевого слова PAGEJSIZE можно задать размер страницы для базы
данных. Сервер БД обрабатывает информацию в постраничном режиме. При чтении
одной записи из базы данных реально считывается ровно столько записей, сколько по-
мещается на одну страницу. Точно так же при сохранении одной записи в базу данных
передается целая страница. С одной стороны, чем меньше страница, тем меньше данных
передается при чтении или сохранении записей, что повышает производительность об-
работки информации. С другой стороны, если некоторые таблицы имеют длинные записи,
не помещающиеся на одной странице, то под одну запись будет отводиться несколько
страниц. В этом случае при операциях чтения/сохранения задействуются все эти страни-
Глава 4. Клиент-серверные базы данных 207

цы, что снижает производительность. В идеальном случае размер страницы должен быть
подобран так, чтобы на ней помещалась самая длинная запись базы данных.
Размер страницы может принимать одно из значений: 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 DOMAIN ITEMNO AS INTEGER
NOT NULL
CHECK(VALUE > 0);
Изменить определение уже существующего домена можно при помощи оператора
ALTER DOMAIN:
ALTER DOMAIN {Имя | СтароеИмя ТО НовоеИмя } {
[SET DEFAULT {константа | NULL | USER}]
| [DROP DEFAULT]
| [ADD [CONSTRAINT] CHECK (<Условие>)]
| [DROP CONSTRAINT]
| TYPE ТипДанных
};
Как видим, по структуре этот оператор сходен с оператором CREATE DOMAIN. При
помощи оператора ALTER DOMAIN можно удалить из определения домена значение по
умолчанию и добавить новое, заменить имя домена, удалить конструкцию CHECK и доба-
вить вместо нее новую, изменить тип данных домена.
Удалить существующий домен из БД можно посредством оператора DROP DOMAIN:
Глава 4. Клиент-серверные базы данных 211

DROP DOMAIN ИмяДомена

Работа с таблицами
Прежде чем приступить к созданию таблицы, следует определить все домены, которые
должны использоваться при объявлении ее полей. Непосредственно создание таблицы БД
осуществляется оператором 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. Разработка приложений баз данных

CREATE DOMAIN LastName AS VARCHAR(25) NOT N U L L ;


CREATE DOMAIN MiddleName AS VARCHAR(20);
'При создании таблицы Employee используется 4 предварительно
'созданных домена и одно вычисляемое поле.
'Для вычисления значения поля Name используется операция
'конкатенации, объединяющая две строки в одну.
'Константа Now содержит текущую системную дату.
CREATE TABLE Employee
(
EmpNo EmpNo,
LastName LastName,
FirstName FirstName,
MiddleName MiddleName,
Name COMPUTED BY (FirstName || ' ' || LastName),
Birthday DATE NOT NULL,
HireDate DATE DEFAULT 'Now' NOT NULL,
FireDate DATE
);
'Сначала нужно создать требуемые домены
CREATE DOMAIN Price AS NUMERIC(8, 2)
DEFAULT 0;
CREATE DOMAIN Qty AS SMALLINT
DEFAULT 0;
'При создании таблицы Storage определяется пять полей.
'Два из них основаны на доменах, одно поле — вычисляемое.
CREATE TABLE Storage
(
StoreNo INTEGER NOT NULL,
Item INTEGER NOT NULL,
Qty Qty,
Price Price,
Cost COMPUTED BY (Qty*Price)
);
Конструкция <ОграничениеДляПоля> позволяет задать ограничения, связанные с
целостностью, допустимостью и непротиворечивостью данных. При помощи этой кон-
струкции можно указать, является ли поле первичным ключом, должны ли его значения
быть уникальными, задать дополнительные ограничения, а также задать ссылку на поле
другой таблицы и действия, которые необходимо выполнять при удалении или обновле-
нии значения связанного поля. Но гораздо удобнее пользоваться упрощенным синтакси-
сом этой конструкции, задавая остальные ограничения на уровне таблицы. Упрощенный
синтаксис конструкции <ОграничениеДляПоля> таков:
<0граничениеДляПоля>=[CONSTRAINT ИмяОграничения]
(UNIQUE | PRIMARY KEY}
Вслед за ключевым полем CONSTRAINT нужно указать имя для создаваемого ограни-
чения. Если конструкцию CONSTRAINT опустить, сервер Interbase присвоит ограничению
служебное имя, нечто вроде INTEG_10. Все ограничения хранятся в служебной таблице
RDBSRELATION CONSTRAINTS.
Глава 4. Клиент-серверные базы данных 213

Далее, вы можете указать или ключевое слово 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. Разработка приложений баз данных

Только использование ключевого слова CASCADE гарантирует соблюдение ссылочно!


целостности.
Конструкция CHECK в этом контексте позволяет задать условия, которым должнь
удовлетворять сразу несколько полей вставленной или модифицированной записи.
Ниже приведен пример создания таблицы, содержащей первичный и внешний ключи
'Домен PKeyField в дальнейшем будет использоваться
'в качестве типа данных для всех первичных ключей,
'а домен FKeyField - для внешних ключей
CREATE DOMAIN PKeyField AS INTEGER
CHECK(VALUE > 0) NOT NULL;
CREATE DOMAIN FKeyField AS INTEGER
CHECK(VALUE > 0) NOT NULL;

'Таблица-справочник 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 DROP CONSTRAINT ИмяОграничения — из структуры таблицы удаляется ограниче-


ние с указанным именем.
Удалить таблицу из БД можно при помощи оператора DROP TABLE:
DROP TABLE ИмяТаблицы
В результате применения этого оператора из базы данных удаляется описание таблицы
(метаданные), ее данные, а также все связанные с таблицей индексы и триггеры.
Замечание
Если вы удаляете БД при помощи оператора DROP DATABASE, следует иметь в виду,
что все внешние таблицы, ассоциированные с этой БД, не будут удалены автоматически.
Удалять их придется самому.

Использование индексов
В предыдущих главах, посвященных локальным базам данных, рассказывалось о том,
зачем нужны индексы, в каких случаях их нужно применять, а в каких — нет. Большей
частью эта информация верна и для клиент-серверных БД. Индексы позволяют значи-
тельно ускорить извлечение информации из таблиц, сортировку, перемещение по набору
данных. С другой стороны, большое количество индексов, определенных для таблицы,
приводит к излишней потере времени при вставке, модификации или удалении записей.
Это связано с необходимостью обновлять большой объем информации об индексах.
Нелишне еще раз напомнить о тех ситуациях, когда использовать индексы вполне
уместно:
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 удалить нельзя.

Генераторы (Generators), триггеры (Triggers)


и хранимые процедуры (Stored Procedures)
В таблицах локальных БД для первичного ключа обычно используется автоинкремент-
ный тип данных. Это гарантирует, что каждая новая запись таблицы будет получать для
своего первичного ключа уникальное значение. Сервер Interbase не поддерживает типы
данных, аналогичные автоинкрементным. Вместо этого, чтобы получить уникальные зна-
чения для поля первичного ключа, можно использовать генераторы.
Генератор (Generator) — это механизм, формирующий уникальное число, после
чего это число вставляется (например, при помощи триггера) в указанное поле при
операциях вставки или обновления записей. Генераторы удобно использовать не толь-
ко для заполнения первичных ключей последовательно возрастающими уникальными
значениями. Например, в БД имеется таблица, в которой хранятся счета предприятия,
и требуется, чтобы номер каждого нового счета был на единицу больше предыдущего.
Если в этом случае использовать для получения значения генератор, указанное усло-
вие будет соблюдено.
Можно, конечно, обойтись и без генератора, вычисляя максимальное значение первич-
ного ключа таблицы в рамках клиентского приложения. Один из вариантов — воспользо-
ваться значением, которое возвратит запрос на выборку следующего вида:
218 Borland C++ Builder 6. Разработка приложений баз данных

SELECT MAX(Cust_No) FROM CUSTOMER;


Затем можно увеличить это значение на 1 и указать его в качестве значения первичного
ключа для новой записи. О таком подходе можно сделать несколько замечаний. Во-пер-
вых, так вы перенесете часть логики обработки данных из серверной части приложения
в его клиентскую часть. В принципе, в этом нет ничего плохого, но если вы внесете в эту
часть приложения какие-то изменения, вам придется обеспечить обновление для всех кли-
ентских приложений. Гораздо проще внести изменения в серверную часть приложения
(в базу данных). Второе, более важное замечание: пока вы получите новое значение для
первичного ключа, в таблице БД уже может появиться запись, имеющая то же значение
ключевого поля. Эту запись может добавить другой клиент, воспользовавшись тем же
механизмом чуть позже вас, но обработав и сохранив информацию раньше вас. В этом
случае при попытке сохранить запись вы получите сообщение об ошибке — о нарушении
уникальности данных. Использование генератора гарантирует, что вы всегда получите
уникальное значение, независимо от количества клиентов и порядка их доступа к БД.
В базе данных Interbase можно создать любое количество генераторов. Сделать это
можно при помощи оператора CREATE GENERATOR:
CREATE GENERATOR ИмяГенератора;
В результате выполнения этого оператора в базе данных будет создан новый генератор
с указанным именем, и его начальное значение будет установлено в 0. Чтобы получить
очередное значение генератора, используется функция GEN_ID, имеющая такой формат:
6ЕМ_Ю(ИмяГенератора, Ш а г ) ;
Функция GEN_ID выполняет следующие действия. Текущее значение генератора,
чье имя передано в качестве первого аргумента, увеличивается на величину Шаг, кото-
рая передается во втором аргументе. Функция возвращает новое значение генератора.
Функцию GEN_ID можно вызывать прямо из приложения, из тела триггера или хранимой
процедуры, а также использовать в операторах INSERT, UPDATE или DELETE. Например,
следующий оператор вставит в таблицу Category новую запись, задав для первичного
ключа сформированное генератором значение:
INSERT INTO Category (CategoryNo, Category, Description)
VALUES (GEN_ID(Category_Gen,l), 'Принтеры',
'Принтеры струйные, матричные, лазерные")
До использования этого оператора вставки должен быть создан генератор CATEGORY^
GEN (например, в ISQL):
CREATE GENERATOR Category_Gen;
Удалить генератор из базы данных не так просто, как другие объекты. Дело в том,
что оператора DROP GENERATOR не существует. Однако удалить генератор из БД все
же можно. Все генераторы хранятся в системной таблице RDB$GENERATORS, поэтому
генератор можно удалить при помощи оператора следующего вида:
DELETE FROM RDBSGENERATORS
WHERE "RDB$GENERATOR_NAME" = 'Category_Gen';
Если вы выполните запрос, основанный на приведенной строке оператора SQL,
в рамках утилиты ISQL, то IBConsole перестанет правильно отображать информацию обо
всех зарегистрированных БД. Вам придется перезапустить эту утилиту.
Глава 4. Клиент-серверные базы данных 219

Изменить текущее значение генератора можно при помощи оператора SET


GENERATOR:
SET GENERATOR ИмяГенератора ТО НачальноеЗначение;
Например:
CREATE GENERATOR Category_Gen;
SET GENERATOR Category_Gen TO 1000;
:
В этом случае при первом обращении к генератору Category _Gen будет возвращено
значение 1001.
Замечание
31 31-
Генератор возвращает 64-битовое значение в диапазоне от -2 х 10 до 2 х10 1.
Поэтому, чтобы избежать неприятностей, связанных с переполнением, необходимо ис-
пользовать для поля, в котором сохраняется значение генератора, соответствующий тип
данных (NUMERIC или DECIMAL). Но если вы уверены, что поле не может принимать
большие значения, то можно использовать и тип INTEGER.

Кроме прочего, генераторы могут применяться и в триггерах. Триггер (Trigger) — это .


специальным образом заданная процедура, связанная с определенной таблицей или пред-
ставлением (View). Триггер выполняет указанные действия с записью— вставляемой,
обновляемой или удаляемой. Триггер никогда не вызывается явно. Вместо этого в указа-
нных при создании триггера ситуациях автоматически выполняются заданные действия.
Создать новый триггер можно посредством оператора CREATE TRIGGER:
CREATE TRIGGER ИмяТриггера FOR {ИмяТаблицы | ИмяПредставления}
[ACTIVE | INACTIVE]
{BEFORE | AFTER} {DELETE | INSERT | UPDATE}
[POSITION Номер]
AS <ТелоТриггера>
Оператор CREATE TRIGGER вводит определение триггера, которое состоит из заго-
ловка и тела триггера. В заголовке указывается имя триггера, имя таблицы (или представ-
ления), с которой связывается триггер, а также информация о том, когда и какие действия
должны выполняться. Тело триггера включает необязательный список объявлений пере-
менных, а также набор операторов на языке триггеров и процедур Interbase, заключенных
в операторные скобки BEGIN... END.
В заголовке триггера можно употребить ключевое слово ACTIVE или INACTIVE.
В первом случае триггер будет работать в указанных в его заголовке ситуациях, во
втором — нет. Значение по умолчанию — A C T I V E . Ключевое слово INACTIVE исполь-
зуется исключительно в отладочных целях. Следующие две группы ключевых слов
задают шесть вариантов событий, при наступлении которых триггер активизируется.
К ним относятся: перед удалением, вставкой, обновлением и после удаления, вставки,
обновления. Например, если в заголовке триггера указать BEFORE INSERT, то опера-
торы, составляющие тело триггера, будут выполняться перед вставкой новой записи
в таблицу (или представление).
С одной и той же таблицей может быть связано несколько триггеров. Для того что-
бы определить очередность их активизации, в заголовке можно указать конструкцию
POSITION. В этой конструкции указывается целое неотрицательное число. Чем это число
меньше, тем раньше выполняется триггер. Если в заголовке триггера указана конструкция
220 Borland C++ Builder 6. Разработка приложений баз данных

POSITION 0, этот триггер активизируется первым; вторым будет активизирован триггер,


в заголовке которого указана конструкция POSITION 1, и т.д.
Оператор CREATE TRIGGER можно выполнять или в рамках динамического SQL
(DSQL), или в рамках утилиты ISQL. Каждый из операторов, составляющих тело триг-
гера, должен заканчиваться символом точки с запятой. Поэтому нужно выбрать другой
символ (терминатор), при помощи которого определение триггера само по себе будет от-
деляться от других операторов, выполняемых в ISQL. Для этого нужно воспользоваться
оператором SET TERM:
Л
SET TERM ;
Л
Этот оператор определяет символ « » вместо точки с запятой в качестве ограничи-
теля для операторов, используемых в дальнейшем в рамках ISQL. Можно использовать
и другие символы. После того как триггер определен, можно вернуться к первоначально-
му положению вещей:
Л
SET TERM ;
Тело триггера начинается с ключевого слова AS, за которым располагается блок опера-
торов, заключенный в операторные скобки BEGIN... END:
AS
BEGIN
[ОбъявленияЛокальныхПеременных]
СписокОператоров
END;'
Локальные переменные объявляются следующим образом:
DECLARE VARIABLE ИмяПеременной ТипДанных;
Операторы тела триггера пишутся на так называемом языке программирования проце-
дур и триггеров Interbase. Этот язык включает операторы манипуляции данными (INSERT,
UPDATE, DELETE и SELECT), операторы и выражения SQL, в том числе определенные
пользователем функции (UDF— User-Defined Functions). Он предоставляет также допол-
нительные возможности, характерные только для триггеров и хранимых процедур:
О Операторы присвоения, используемые для задания значений локальным переменным.
О Условные операторы (IF... THEN) и операторы цикла (WHILE... DO и FOR SELECT... DO).
О Оператор EXECUTE PROCEDURE, предназначенный для вызова хранимой процедуры.
О Операторы исключений, предназначенные для возвращения сообщений об ошибках,
и оператор WHEN, обрабатывающий указанную ошибку.
О Контекстные переменные OLD и NEW, содержащие, соответственно, старое и новое
значение поля. Эти переменные могут использоваться только в триггерах.
Q Генераторы.
Условный оператор в языке триггеров и процедур используется в следующем виде:
IF (<Условие>) THEN <БлокОператоров >
[ELSE <БлокОператоров>]
Условие — это выражение SQL логического типа. Если условие возвращает значение
TRUE, выполняется блок операторов, следующий за ключевым словом THEN. В противном
Глава 4. Клиент-серверные базы данных 221

случае, если указана необязательная конструкция ELSE, выполняется содержащийся в ней


блок операторов. Если конструкция ELSE не указана, управление передается следующему
оператору. Блок операторов — это операторы языка триггеров и процедур, заключенные
в операторные скобки BEGIN и END.
Специфический вид имеет оператор цикла FOR SELECT... DO:
FOR
<Выражение_5Е!_ЕСТ>
DO
<БлокОператоров>;
Выражение SELECT представляет собой текст оператора на выборку из таблицы,
представления или хранимой процедуры, за которым следует (обязательная) конструк-
ция INTO. Оператор на выборку может быть как простым оператором SELECT, так
и оператором на объединение (UNION). Конструкция INTO используется для того,
чтобы присвоить значение одного или нескольких полей текущей записи внутренним
переменным или выходным аргументам (в хранимых процедурах). Имена локальных
переменных или выходных аргументов перечисляются через запятую после ключевого
слова INTO. Количество и тип параметров, указанных в конструкции INTO, должны
соответствовать количеству и типу полей, перечисленных в конструкции SELECT.
Значения этих переменных потом могут быть использованы в блоке операторов, ука-
занном после ключевого слова DO.
Работает оператор FOR SELECT... DO следующим образом. Для каждой записи, воз-
вращаемой оператором SELECT, выполняются присвоения, заданные конструкцией INTO.
Затем для этой же записи выполняются операторы, указанные после ключевого слова DO.
Ниже приведен пример использования оператора FOR SELECT... DO. Значения полей
ItemNo, Item, Qty и Notes каждой записи, возвращаемой запросом на выборку, сохраня-
ются в соответствующих переменных. Затем в блоке операторов конструкции DO про-
веряется значение переменной Qty (количество товара на складе) и в зависимости от него
переменной Notes присваивается соответствующее значение.
FOR
SELECT Storage.ITEM AS ITEMNO,
Items.ITEM, Storage.QTY, Storage.NOTES
FROM ITEMS Items
INNER JOIN STORAGE Storage
ON (Storage.ITEM = Items.ITEMNO)
INTO : ItemNo, :Item, :Qty, -.Notes
DO
BEGIN
Notes=";
IF (Qty 5) THEN Notes='Cpo4HO доставить!';
IF ((Qty >= 5) AND (Qty 10)) THEN Notes='Заказать!';
SUSPEND;
END
Второй оператор цикла имеет следующий синтаксис:
WHILE (<Условие>) DO <БлокОператоров>
Блок операторов, указанный после ключевого слова DO, выполняется до тех пор, пока
выражение логического типа Условие возвращает значение TRUE.
222 Borland C++ Builder 6. Разработка приложений баз данных

Оператор EXECUTE PROCEDURE предназначен для вызова хранимой процедуры и:


триггера или другой хранимой процедуры:
EXECUTE PROCEDURE [TRANSACTION Транзакция)
ИмяПроцедуры [:Параметр [ [INDICATOR] : Индикатор] ]
[, : Параметр [ [INDICATOR] : Индикатор] ...]
[RETURNING_VALUES :Параметр [ [INDICATOR] :Индикатор]
[, : Параметр [ [INDICATOR] : Индикатор] . . . ] ] ;
В простейшем случае указывается имя процедуры, а также список входных (input)
и выходных (output) параметров процедуры. Выходные параметры указываются после
ключевых слов RETURNING VALUE:
EXECUTE PROCEDURE SalesAnalyze :SalesDate
RETURNING_VALUES :MaxSale, :MinSale, :AvgSale, TotSale;
После ключевого слова TRANSACTION можно указать транзакцию, в рамках кото-
рой должна выполняться процедура. Вслед за именем параметра после ключевого слова
Индикатор можно указать переменную целого типа, значение которой служит для про-
верки параметра на значение NULL или неопределенное значение. Если переменная-ин-
дикатор имеет значение, меньшее или равное нулю, то соответствующий параметр имеет
значение NULL или не определен.
Контекстные переменные OLD и NEW можно применять только в триггерах. При по-
мощи этих предопределенных переменных можно получить доступ к старому и новому
значению указанного поля. Используются эти переменные так:
ОЮ.ИмяПоля

Для обработки ошибок в триггерах (и хранимых процедурах) используются операторы


EXCEPTION и WHEN. Оператор EXCEPTION служит для вызова исключения:
EXCEPTION ИмяИсключения,
которое должно быть предварительно создано при помощи оператора CREATE
EXCEPTION:
CREATE EXCEPTION ИмяИсключения ТекстСообщенияВОдинарныхКавычках
Исключение обычно вызывается при помощи оператора WHEN. Происходит это при-
мерно так:
WHEN SQLCODE КодОшибки DO EXCEPTION ИмяИсключения;
В заключение краткого обзора возможностей языка триггеров и процедур следует
упомянуть об операторе SUSPEND. Оператор SUSPEND приостанавливает извлечение
данных из источника, позволяя выполнить некие действия над значениями полей текущей
записи, после чего инициируется извлечение следующей записи (неявный вызов операто-
ра FETCH).
К сожалению, рамки этой книги не позволяют подробно изложить сведения о языке
триггеров и процедур. Более детально об этом можно узнать в справочной системе
Interbase.
Ниже приведены два примера создания триггеров.
Глава 4. Клиент-серверные базы данных 223

'Триггер предназначен для того, чтобы вставлять


'в поле первичного ключа
'новой записи значение, возвращаемое генератором
CREATE TRIGGER Set_Category FOR Category
ACTIVE BEFORE INSERT
AS
BEGIN
NEW.CategoryNo=GEN_ID(Category_Gen,l);
END
'Триггер вставляет в поле Invoice (номер счета) уникальное число,
'предоставляемое генератором Invoice_Gen, которому предшествуют
'символы No. Функция CAST преобразует целое число в текстовую
'форму
CREATE TRIGGER SetlnvoiceNo FOR INVOICES
ACTIVE BEFORE INSERT
AS
BEGIN
NEW.Invoice='No ' || CAST(GEN_ID(Invoice_Gen,1) AS VARCHAR(G));
END
Приведенные выше примеры триггеров достаточно просты и призваны проиллюстри-
ровать основные принципы их использования. Блок операторов, размещенный между
операторными скобками BEGIN и END, может быть достаточно сложным и изощренным.
Следует, однако, помнить о том, что триггер может вызываться достаточно часто, особен-
но если таблица постоянно модифицируется большим количеством подключенных к БД
клиентов. Поэтому не стоит перегружать тело триггера излишней функциональностью.
Если вы пользуетесь для исследования структуры БД утилитой IBConsole, вы не уви-
дите перечня имеющихся триггеров, как список других объектов БД (триггеров, доменов
и т.д.). Так как триггер при создании ассоциируется с таблицей, то и получить информа-
цию о нем можно, извлекая метаданные для этой таблицы (пункт Properties контекстного
меню таблицы, вкладка Metadata).
Изменить определение триггера можно при помощи оператора ALTER TRIGGER.
Синтаксис этого оператора аналогичен синтаксису оператора CREATE TRIGGER. Для
удаления триггера служит оператор DROP TRIGGER ИмяТриггера.
Гораздо большие возможности имеет другой объект базы данных, использующий язык
триггеров и процедур, — хранимая процедура (Stored Procedure). В отличие от триггеров,
хранимые процедуры должны явно вызываться из клиентского приложения, другой про-
цедуры или триггера. Если хранимая процедура возвращает набор данных, ее имя мо-
жет использоваться вместо имени таблицы или представления в операторах на выборку
SELECT.
Для создания хранимой процедуры используется оператор CREATE PROCEDURE, ко-
торый, как и остальные операторы этого типа, доступен в DSQL и ISQL:
CREATE PROCEDURE ИмяПроцедуры
[(Аргумент ТипДанных [, Аргумент ТипДанных . . . ] ) ]
[RETURNS (Аргумент ТипДанных [, Аргумент ТипДанных . . . ] ) ]
AS
<ТелоПроцедуры>;
224 Borland C++ Builder 6. Разработка приложений баз данных

Хранимая процедура может использовать входные (input) и выходные (output) аргу-


менты. Входные аргументы указываются через запятую в круглых скобках вслед за име-
нем процедуры. Список выходных аргументов процедуры указывается после ключевого
слова RETURNS. Для каждого аргумента нужно указать также его тип. Чтобы отличить
имена аргументов обоих типов от имен полей в теле процедуры, перед именами аргумен-
тов (как, впрочем, и перед именами локальных переменных) ставится символ двоеточия,
Например, если объявлен входной аргумент SaleDate, то в теле процедуры его следуе!
использовать в виде :SaleDate.
Операторы, составляющие тело процедуры, как и операторы тела триггера, пишутся
на языке триггеров и процедур Interbase. Единственное отличие: в теле процедуры нельзя
использовать контекстные переменные OLD и NEW. Хранимые процедуры, как и обы-
чные запросы, в зависимости от назначения могут быть двух типов: процедуры выборки
(SELECTProcedures) и выполнимые процедуры (Executable Procedures).
Процедуры выборки должны возвращать одно или несколько значений, но
в большинстве случаев процедуры этого типа программируются на возвращение на-
бора данных, состоящего из большого количества строк. Все данные возвращаются
процедурой через ее выходные аргументы, поэтому следует позаботиться об их пра-
вильном объявлении. Имя процедуры на выборку может затем использоваться вме-
сто имени таблицы или представления в запросах SELECT. Так как язык триггеров
и процедур обладает большими возможностями по сравнению с языком SQL, исполь-
зование хранимых процедур выборки облегчает решение ряда сложных задач, связан-
ных с выборкой и представлением данных.
Ниже приведен пример определения хранимой процедуры на выборку, который можно
использовать в ISQL:
л
SET TERM ;

CREATE PROCEDURE GetStorage


RETURNS
(
ItemNo INTEGER,
Item VARCHAR(50),
Qty SMALLINT,
Notes VARCHAR(20)
)
AS
BEGIN
FOR
SELECT Storage.Item AS ItemNo, Items.Item, Storage.Qty,
Storage.Notes
FROM Items
INNER JOIN Storage
ON (Storage.Item = Items.ItemNo)
INTO :ItemNo, :Item, :Qty, :Notes
DO
BEGIN
Notes='';
IF (Qty 5) THEN Notes='Cp04HO доставить!';
IF ((Qty >= 5) AND (Qty 10» THEN Notes='Заказать!';
SUSPEND;

'
Глава 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 б. Разработка приложений баз данных

В отличие от триггера, приведенную выше процедуру придется вызывать из кли


ентского приложения самостоятельно. Для этого можно воспользоваться или одним и:
компонентов типа TQuery (BDE) или TSQLQuery (dbExpress), или одним из специализи
рованных компонентов: TStoredProc (BDE) или TSQLStoredProc (dbExpress). В последнел
случае в текст процедуры обработки события BeforePost набора данных необходимо до
бавить строку:
GetEmpGen->ExexProc() ;
EmpNoField->AsInteger=GetEmpGen->ParamByName("NumCode") ->As Integer;
Ниже приведен пример хранимой процедуры, предназначенной для вставки новой за-
писи в таблицу Employee. Значения полей для новой записи передаются процедуре в каче-
стве входных аргументов.
CREATE PROCEDURE Add_Employee
(
LName LastName,
MName MiddleName,
FName FirstName,
HireDate DATE,
FireDate DATE,
Birthday DATE
)
AS
BEGIN
INSERT INTO Add_Employee
(EmployeeNo, LName, MName, FName, HireDate, FireDate, Birthday)
VALUES
(GEN_ID(Emp_GEN,l) , :LName, :MName, :FName, :HireDate,
:FireDate, :Birthday);
EXIT;
END
Изменить определение существующей процедуры можно при помощи оператора ALTER
PROCEDURE, по структуре аналогичного оператору CREATE PROCEDURE. Удалить про-
цедуру из БД можно при помощи оператора DROP PROCEDURE ИмяПроцедуры.

Представления (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.
Замечание
В определении представления нельзя использовать в качестве источника данных н;
бор строк, возвращаемых хранимой процедурой.

В конце оператора определения представления можно указать необязательную KOI


струкцию WITH CHECK OPTION.-Еспи она указана, клиенту не удастся вставить или of
новить запись таким образом, что она не будет входить в диапазон, указанный в определи
нии представления. Имеется в виду, конечно, что представление обновляемо. Наприме}
обновляемое представление возвращает список всех счетов за текущий месяц. Если пр
определении этого представления была указана конструкция WITH CHECK OPTION, вш
не удастся задать для даты счета какое-либо другое значение месяца.
Ниже приведено несколько примеров создания представлений.
'Представление возвращает все записи таблицы Employee

CREATE VIEW ViewEmp ( EmployeeNo, LName, MName, FName,


HireDate, FireDate, Birthday, Description)
AS
SELECT * FROM Employee;
'Представление StoreView может использоваться в качестве
'базового набора данных для различных параметризованных
'и итоговых запросов
CREATE VIEW StoreView (ItemNo, Item, CategoryNo, Category,
Description, Itemlndex, Price, Qty, Cost, Notes)
AS
SELECT Items.ItemNo, Items.Item, Category.CategoryNo,
Category.Category, I terns.Description,
Items.Itemlndex, Items.Price, Storage.Qty,
Storage.Qty*I terns.Price, Storage.Notes
FROM Category
INNER JOIN Items
ON (Items.Category = Category.CategoryNo)
INNER JOIN Storage
ON (Storage.Item = Items.ItemNo);
Последний пример иллюстрирует основную область применения представлений.
Представление, которое хранится на стороне сервера, скрывает от пользователя достаточ-
но сложную внутреннюю реализацию, предоставляя максимально широкий набор данных,
объединяющий информацию из нескольких связанных между собой таблиц. Клиентское
приложение может затем получать информацию, имеющую сложную структуру, при по-
мощи простых запросов на выборку данных из этого представления. Особенно полезны
простые параметризованные запросы на выборку и запросы, возвращающие различные
итоговые значения.
Ранее уже упоминалось, что представления могут использоваться не только для вы-
борки данных, но и для вставки и модификации записей в таблицы, на основе которых это
представление создано. Для этого требуется выполнение трех условий. Во-первых, пред-
ставление должно быть обновляемым, т.е. в его основе должен лежать обновляемый за-
Глава 4. Клиент-серверные базы данных 229

прос на выборку. (Вопрос обновляемости запросов рассматривался в предыдущей главе.)


Во-вторых, клиент должен иметь соответствующие права на вставку и обновление записи.
И последнее: представление должно быть создано с указанием WITH CHECK OPTION.
Сервер Interbase не поддерживает оператор ALTER VIEW, поэтому чтобы изменить
определение представления в БД, необходимо его сначала удалить (оператор DROP VIEW
ИмяПредставления), а затем снова создать (оператор CREATE VIEW).

Исключения (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— предназначена для обработки ошибок одного из трех
вышеперечисленных типов.

Планирование системы безопасности


Планирование системы безопасности, поддерживаемой в Interbase, заключается
в создании пользователей и групп пользователей, а также в предоставлении им прав до-
ступа различного уровня к объектам БД. Кроме того, различные права доступа могут быть
предоставлены хранимым процедурам, триггерам и представлениям. Первоначально до-
ступ к таблицам и представлениям может получить только их создатель (владелец) или
пользователь SYSDBA. Создатель (Creator) или владелец (Owner)— это пользователь,
создавший соответствующий объект. Пользователь SYSDBA имеет неограниченный до-
ступ ко всем объектам БД, а также исключительные полномочия на предоставление прав
доступа другим пользователям.
Для предоставления прав доступа отдельному пользователю применяется оператор
GRANT, имеющий в этом случае следующий синтаксис:
GRANT {ALL [PRIVILEGES] | <СписокПрав>}
ON [TABLE] {ИмяТаблицы | ИмяПредставления}
ТО {<0бъект> | <СписокПользователей> | GROUP TpynnaUNIX};
Элемент СписокПрав может содержать перечень следующих прав, указываемых через
запятую:
Q SELECT— право на чтение данных;
Q DELETE — на удаление записей;
Q INSERT— на добавление новых записей;
Q UPDATE [(Поле [, Поле...])] — на обновление заданных полей;
О REFERENCES [(Поле [, Поле ...])] — на создание связи с использованием заданных
полей.
Если необходимо присвоить пользователю все перечисленные права, следует приме-
нить ключевое слово ALL.
Глава 4. Клиент-серверные базы данных

После ключевых слов 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. Разработка приложений баз данных

CONNECT 'БазаДанных' USER 'ИмяПользователя' PASSWORD 'Пароль'


ROLE 'ИмяРоли';
Создать роль можно при помощи оператора CREATE, а удалить — при помощи опере
тора DROP:
CREATE ROLE ИмяРоли; //Создание роли
DROP ROLE ИмяРоли; //Уничтожение роли
После того как роль создана, ей необходимо делегировать необходимые права достуге
Делается это при помощи оператора GRANT, который в данном случае имеет такую форму:
GRANT <СписокПрав> ON [TABLE] {ИмяТаблицы | ИмяПредставления}
ТО ИмяРоли;
Элемент Список/Трое может содержать перечень предоставляемых прав (SELEC1
INSERT, UPDATE, DELETE, REFERENCES) или ключевое слово ANY. В последнем случа(
роли присваиваются все права на доступ к таблице или представлению. Права на достуг
к хранимой процедуре для роли можно предоставить так:
GRANT EXECUTE ON PROCEDURE ИмяПроцедуры ТО ИмяРоли;
После того как роль полностью настроена (т.е. ей передан необходимый набор прав до-
ступа), нужно ассоциировать с ней пользователей. Для этого также применяется оператор
GRANT:
GRANT {ИмяРоли [, ИмяРоли ...]} ТО (PUBLIC)
{[USER] ИмяПользователя [, [USER] ИмяПользователя ...]} }
[WITH ADMIN OPTION];
После ключевого слова GRANT можно указать наименование одной или нескольких
существующих в БД ролей. Конструкция ТО может содержать список пользователей,
с которыми указанные роли должны быть ассоциированы. Вместо списка пользователей
можно указать ключевое слово PUBLIC. В этом случае роль (или роли) будут связаны со
всеми без исключения пользователями базы данных.
Если указана необязательная конструкция WITH ADMIN OPTION, то все пользователи,
ассоциированные с ролью (ролями), получают возможность предоставлять все права до-
ступа, предусмотренные для роли, другим пользователям (не связанным с данной ролью
(ролями)).
Решить обратную задачу — отменить права на доступ к объектам БД, предоставлен-
ные ранее пользователям или ролям при помощи оператора GRANT, можно при помощи
оператора REVOKE:
REVOKE <СписокПрав> ON [TABLE] {ИмяТаблицы | ИмяПредставления}
FROM {<0бъект> | <СписокПользователей> | GROUP TpynnaUNIX};
Все конструкции, используемые с оператором REVOKE, уже обсуждались ранее при
описании вариантов применения оператора GRANT.

Создание клиентских приложений


Вооружившись сведениями о структуре и методах использования БД в формате SQL
(в частности, InterBase), можно приступать к вопросам проектирования клиентских при-
ложений. Прежде чем производить какие-либо действия по отношению к БД, клиентское
Глава 4. Клиент-серверные базы данных 233

приложение должно к ней подключиться. С вариантов использования для подключения


к БД различных механизмов доступа мы и начнем.

Несколько слов о механизмах доступа к данным


В этом и последующих разделах будет в основном рассматриваться механизм
dbExpress (новый для C++ Builder 6); будут приводиться также некоторые сведения об
использовании механизмов ADOExpress и IBExpress. Использование старейшины всех
механизмов доступа — BDE — рассматривалось в двух предыдущих главах.
Механизм dbExpress представляет собой набор драйверов (в виде библиотек dll), специ-
ально разработанных для прямого и быстрого доступа к базам данных в формате SQL.
Каждому зарегистрированному типу БД соответствует пара файлов— драйвер dbExpress
и библиотека производителя (Vendor Library). Файл драйвера dbExpress имеет имя, которое на-
чинается с символов dbexp и заканчивается обозначением сервера БД. Например: dbexpdb2.dll
(драйвер для DB2), dbexpint.dll (драйвер для Interbase), dbexpora.dll (драйвер для Oracle).
Библиотека производителя предназначена для поддержки клиентских приложений
и инсталлируется при установке сервера БД. Кроме того, при инсталляции Borland C++
Builder (Enterprise Edition) устанавливается набор библиотек производителя наиболее
часто используемых серверов БД (DB2, Informix, Interbase, MySQL, Oracle). Например,
для DB2 файл библиотеки производителя— db2cli.dll, для Interbase— gds32.dll, для
Oracle — ocldll. Информация о файлах драйверов dbExpress (LibraryName) и библиотек
производителя (VendorLib) хранится в файле dbxdrivers.ini.
Если вы используете механизм dbExpress, то при распространении клиентских приложе-
ний вам достаточно включить в пакет поставки сравнительно небольшую по размеру сШ-би-
блитеку, соответствующую используемому серверу БД. Кроме того, такие приложения можно
распространять в виде отдельного выполняемого (ехе) файла (это не относится к БД Informix).
Механизм dbExpress можно также использовать и на платформе Linux, т.е. его можно приме-
нять и для создания кросс-платформенных приложений (приложений CLX).
Начиная с версии 5, C++ Builder включает механизм доступа к данным Interbase
Express (IBExpress). Этот механизм предназначен для прямого доступа к базам данных
в формате Interbase. Кроме компонентов для подключения к БД Interbase и манипуляций
ее данными (различные наборы данных, компоненты, управляющие транзакциями, за-
просы на обновление и т.д. — все они расположены на вкладке IBExpress), C++ Builder
6 включает также набор компонентов для администрирования БД Interbase (размещены
на вкладке Interbase Admin): для инсталляции, деинсталляции, службы подключения,
безопасности и т.д. Воспользоваться компонентами с вкладки Interbase Admin можно,
только если установлен Interbase версии 6 и новее. Посредством компонентов с этих двух
вкладок, можно воспользоваться почти всеми особенностями, присущими базам данных
в формате Interbase.
C++ Builder поддерживает также механизм ADO (ActiveX Data Objects), разрабо-
танный в Microsoft. ADO использует для с связи с данными провайдер OLE DB, позво-
ляющий работать как с реляционными источниками данных, так и с нереляционными.
В предыдущей версии C++ Builder набор компонентов, расположенных на вкладке ADO,
назывался ADOExpress, в шестой версии — dbGo. Несмотря на большое количество типов
поддерживаемых механизмом ADO источников данных, его удобнее всего применять для
подключения к БД Microsoft Access и Microsoft SQL Server (а также для работы с другими
продуктами Microsoft).
234 Borland C++ Builder 6. Разработка приложений баз данных

Подключение к базам данных с помощью dbExpress


Для подключения к базе данных в рамках механизма dbExpress предназначен ко
понент TSQLConnection. Этот компонент — единственная возможность подключен;
к БД для всех компонентов с вкладки dbExpress (пожалуй, за исключением клиен
ского набора данных TSQLClientDataSet). Для того чтобы подключиться к одной j
БД в формате SQL, необходимо настроить пять свойств объекта TSQLConnectio
ConnectionName, DriverName, GetDriverFunc, LibraryName и VendorLib. Ho npi
ще воспользоваться пунктом Edit Connection Properties контекстного меню объею
TSQLConnection. В результате на экране будет отображено диалоговое окно dbExpre.
Connections (рис. 4.9).
f;
% , dbExpress Connections: D:\Program Files\Coi

Driver Name Connection Settings


lnterbase Key _ .Lvalue.
Drive rName ilnterbase
Connection Name
Database : d:\data\MylBData.gdb
JIBConnection
RpleNarne
yser_Name sysdba
Password masterkey
SeryerCriarSet None
SQLDialect 3"
BlobSize -1
CommitRetain False
IWaitOnLocks True
ErrqrResqurceFil6:D:\MylBData\MylBData.err
LocaleCode iOOOp
Interbase TranslstReadCommited

Cancel Hep

Рис. 4.9. Окно dbExpress Connections, при помощи которого можно


настроить подключение к базе данных
После первого открытия окна dbExpress Connections список Connection Name будет
содержать перечень соединений, созданных по умолчанию. Эти соединения создаются ав-
томатически, по одному для каждого драйвера БД, на основании информации, хранящей-
ся в файле dbxdrivers.ini. Информация о самих соединениях хранится в файле dbxconnec-
tions.ini (в каталоге Common Files/Borland Shared/DBExpress). Выбрав в списке Connection
Name какое-нибудь соединение, можно отредактировать значения его свойств в редакторе
списка значений Connection Settings. Чаще всего приходится редактировать свойства, свя-
занные с именем базы данных, именем пользователя и паролем.
Вместо того чтобы постоянно редактировать соединение, переключаясь с одной БД
на другую, можно создать необходимое количество новых соединений. Чтобы создать
новое соединение, нужно нажать кнопку Add Connection (с изображением знака +).
Глава 4. Клиент-серверные базы данных 235
В появившемся диалоговом окне New Connection (рис. 4.10) следует выбрать имя драйве-
ра БД и указать имя нового соединения. Как только вы нажмете кнопку ОК, информация
о созданном соединении будет сохранена в файле dbxconnections.ini и будет доступна
и в последующих сеансах работы с C++ Builder. В комбинированном списке Driver Name
можно выбрать имя нужного драйвера, тогда список Connection Name будет содержать
только соединения, соответствующие этому драйверу.

New Connection

Qriver Name
jlnterbase

Connection Name
MylBConnect

OK Cancel htelp

Рис. 4.10. Окно для создания нового соединения


Кроме кнопки Add Connection панель инструментов окна dbExpress Connections со-
держит еще четыре кнопки — Delete Connection (удалить соединение), Rename Connection
(переименовать соединение), Test Connection (проверить соединение) и View Driver set-
tings (информация о паре файлов: драйвер dbExpress/библиотека производителя {Library
Name/Vendor Library)).
После того как нужное соединение выбрано, нажмите кнопку ОК. В результате
свойства ConnectionName, DriverName, GetDriverFunc, LibraryName и VendorLib объекта
TSQLConnection получат необходимые значения. Остается только задать для свойства
Connected значение true и, если соединение было настроено правильно, подключение
к базе данных будет установлено.
Настроить соединение можно и при помощи свойства Params, представляющего собой
список строк. Свойство Params снабжено кнопкой построителя, нажатие которой вызы-
вает отображение на экране редактора списка значений (Value List editor). Пользуясь его
услугами, можно задать необходимые значения свойств соединения.
Компонент TSQLConnection имеет еще несколько полезных свойств. Свойство
KeepConnection аналогично одноименному свойству компонента TDatabase из набора
BDE. Если это свойство имеет значение false, соединение с базой данных не будет под-
держиваться, если нет ни одного активного набора данных, использующего данное соеди-
нение (объект TSQLConnection). Если свойство LoginPrompt имеет значение false, то при
подключении к БД не будет отображаться окно для ввода имени пользователя и пароля.
В этом случае вы должны позаботиться о том, чтобы эта информация была указана в наст-
ройках соединения (свойство Params).
Еще одно свойство логического типа— LoadParamsOnConnect. Значение true этого
свойства подразумевает, что сведения о конфигурации используемого соединения (свой-
ства DriverName и Params) в режиме выполнения приложения будут загружаться из файла
dbxconnections.ini в компонент TSQLConnection непосредственно перед установкой связи
с БД. Это может понадобиться для синхронизации настроек соединения с информацией
236 Borland C++ Builder 6. Разработка приложений баз данных

из файла dbxconnections.ini (например, если администратор БД внес в него какие-либ(


изменения). Кроме того, загрузить параметры соединения можно при помощи метод*
LoadParamsFromlniFile:
void _ fastcall LoadParamsFromIniFile(AnsiString AFileName = "");
Строка AFileName должна содержать имя и путь к файлу, который должен иметь ту же
структуру, что и файл dbxconnections.ini. Из этого же файла загружается конфигурация со-
единения, если аргумент AFileName не указан.
Компонент TSQLConnection совмещает функции, характерные как для компонен-
та TDatabase, так и для TSession из набора BDE. В частности, так же как компонент
TSession, TSQLConnection имеет целый ряд методов, при помощи которых можно по-
лучить информацию о структуре базы данных (список таблиц, хранимых процедур,
представлений, полей и т.д.). Все эти методы начинаются с префикса Get GetTableName,
GetProcedureParams, GetProcedureNames , GetlndexNames, GetFieldNames. Последние
четыре метода аналогичны одноименным методам компонента TSession (BDE). Метод
GetTableName имеет следующий формат:
void _ fastcall GetTableNames(Classes: :TStrings* List,
bool SystemTables = false) ;
Этот метод заполняет список строк List наименованиями всех таблиц базы данных.
Результат зависит от нескольких факторов. Если аргумент SystemTables имеет значе-
ние true, то в список List будут добавлены наименования только системных таблиц БД.
В противном случае все будет зависеть от свойства TableScope объекта TSQLConnection.
Это свойство-множество, которое может содержать набор из следующих значений: tsTable
(в список помещаются таблицы БД), tsSysTable (системные таблицы), tsSynonym (синони-
мы), tsView (представления).
Компонент TSQLConnection имеет всего пять событий, и все они связаны с под-
ключением, отключением и регистрацией: BeforeConnect, AfterConnect, BeforeDisconnect,
AfterDisconnect и OnLogin. Например, событие BeforeConnect можно использовать для
того, чтобы скорректировать параметры соединения непосредственно перед подключени-
ем к БД. В частности, можно запросить у пользователя ввод имени и пароля и в соответ-
ствии с этим вводом обновить параметры. Ниже приведен код обработчика события
BeforeConnect, выполняющий эту задачу.
void _ fastcall TForml: : SQLConnectionlBeforeConnect (TObject *Sender)
{
if (SQLConnectionl->LoginPrompt == true)
{
SQLConnectionl->LoginPrompt = false;
SQLConnectionl->Params->Values ["User_Name"] =
Edi tUserName->Text ;
SQLConnectionl->Params->Values ["Password"] =
Edi t Pas sword ->Text;

Настроенный необходимым образом объект TSQLConnection затем применяется в каче-


стве значения свойства DBConnection остальными компонентами с вкладки dbExpress.
Глава 4. Клиент-серверные базы данных 237
Использование IBExpress для подключения к БД
Если вам приходится иметь дело исключительно с базами данных Interbase, есть смысл
применять механизм Interbase Express. Прежде чем воспользоваться одним из компонен-
тов, расположенных на вкладке InterBase, следует настроить подключение к БД. Для этого
предназначен компонент TIBDatabase. Для удобства настройки он снабжен своим редак-
тором, доступ к которому можно получить, выбрав пункт Database Editor из его контекст-
ного меню (рис. 4.11).
Database Component Editor
rConnection-
Я '.. <

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

OK ['• I Cancel lest iietp

Рис. 4.11. Редактор компонента TIBDatabase

Редактор компонента TIBDatabase предоставляет практически те же возможности, что


и окно Register Server and Connect приложения IBConsole (см. рис. 4.3). Здесь можно вы-
брать подключение к локальному или удаленному серверу, при необходимости — указать
имя сервера и протокол для удаленной связи. Кроме того, можно ввести имя пользова-
теля, пароль и имя роли, в рамках которой вы хотите подключиться к базе данных. Имя
пользователя и пароль мгновенно отобразятся в списке Settings, Здесь же можно указать
и другие параметры подключения, но можно это сделать и позже, воспользовавшись свой-
ством Params. Из комбинированного списка Character Set можно выбрать используемый
для БД набор символов, а также задать значение для свойства LoginPrompt. В заключение,
нажав кнопку Test, можно протестировать соединение. Если все нормально, вы получите
сообщение об успешном подключении (Successful Connection).
Перед тем как установить соединение (т.е. перед тем как задать для свойства Connected
значение true}, можно воспользоваться несколькими свойствами компонента TIBDatabase
238 Borland C++ Builder 6. Разработка приложений баз данных

для дополнительной настройки соединения. Задать путь и имя файла БД можно при помс
щи свойства 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

Ref No. Jime Stamp SQL Statement


106 21:36:29 SQL Data Out: INTRBASE - Column = 12, Name = RDB$EXTERNAL_FILE, Type;
107 21:36:29 SQL Data Out: INTRBASE - Column = 15, Name = RDB$DEFAULT_CLASS, Тур
108 21:36:29 SQL Data Out: INTRBASE - Column = 16, Name = RDBSFLAGS, Type = fldlNTI t
109 21:36:29 SQL Stmt: INTRBASE - Fetch
110 21:36:29 SQL Vendor: INTRBASE - isc_dsqljetch
111 21:36:29 SQL Stmt: INTRBASE - EOF
112 21:36:29 SQL Stmt: INTRBASE - Reset
113 21:36:29 SQL Vendor: INTRBASE - isc_dsql_free_statement
21:36:29 SQL Transect: INTRBASE -XACT Commit
21:36:29 SQL Vendor: INTRBASE - isc_comrnit_transaction
21:36.29 SQL Stmt: INTRBASE - Close

SQLVendor: INTRBASE - isc_dsqljree_statement

Рис. 4.12. Утилита SQL Monitor предназначена


для отслеживания операций с БД в формате SQL

G tfService — служебные операции;


Q tfMisc — все операции, не подпадающие ни под одну из вышеперечисленных категорий.
Если значение типа TTraceFlag включено в свойство-множество TraceFlags, то опера-
ции соответствующего типа будут доступны для отслеживания при помощи утилиты SQL
Monitor. По умолчанию свойство TraceFlags пусто.
Замечание
Свойство TraceFlags есть также у компонентов TDatabase и TSession из набора ВОЕ.
Однако оно доступно только во время выполнения приложения. Для компонента
T/BDatabase существует возможность настроить это свойство и в режиме конструктора.

После того как соединение с базой данных при помощи объекта 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.

Подключение к БД при помощи механизма dbGo (ADO)


Набор компонентов dbGo по составу напоминает набор dbExpress. В частности, для
подключения к базе данных используется компонент TADOConnection, похожий по своим
функциям и назначению на упоминавшийся ранее компонент TSQLConnection. Уже гово-
рилось о том, что механизм dbGo (точнее, компонент TADOConnection) более всего при-
способлен для работы с базами данных Microsoft Access и Microsoft SQL Server, однако
его можно с успехом использовать и для подключения к базам данных других форматов.
В качестве примера рассмотрим настройку объекта TSQLConnection для подключения
к базе данных Interbase.
Однако прежде чем приступать к решению этой задачи, следует убедиться, что в списке
драйверов ODBC есть драйвер и для Interbase. Для этого можно воспользоваться прило-
жением Администратор источников данных ODBC, доступным через панель управления.
На вкладке Драйверы приведен список всех доступных в системе драйверов ODBC. Если
в этом списке нет драйвера для Interbase (скорее всего, его там нет, если вы его специаль-
но не инсталлировали), следует позаботиться о его приобретении и инсталляции. Мне,
например, попалась пробная версия драйвера ODBC для Interbase от фирмы EasySoft,
снабженная удобным инсталлятором.
Самый простой путь настроить соединение ADO — выбрать пункт Edit ConnectionString
из контекстного меню объекта TADOConnection. Не следует забывать, что ADO — детище
Microsoft, поэтому в результате будет запущен соответствующий мастер, а не обычный ре-
дактор компонента. В первом окне мастера (рис. 4.13) предлагается выбрать источник све-
дений, необходимых для соединения ADO. Если есть подходящий файл dsn (Data Source
Name) или udl (Microsoft Data Link), следует выбрать переключатель Use Data Link File.
Нажав кнопку Browse, можно выбрать нужный файл. Файлы udl хранятся обычно в ката-
логе /Common Files/System/ole db/Data Links, а файлы dsn — в каталоге /Common Files/
odbc/Data Sources. Далее нужно нажать кнопку ОК, и соединение ADO будет готово.
Если подходящих файлов dsn и udl нет (обычно так и бывает), следует выбрать
переключатель Use Connection String и нажать кнопку Build. На экране появится окно
Свойства связи с данными, открытое на вкладке Поставщик данных (рис. 4.14). Нужно
выбрать одного из перечисленных здесь поставщиков. При подключении к базе дан-
ных Interbase (как и в большинстве других случаев) следует выбрать Microsoft OLE DB
Глава 4. Клиент-серверные базы данных 241

Formb>ADOConnection1 ConnectionString
Source of Connection

se Data Link File

j <• Use Qonnection String


Build..

OK 1 Cancel fctelp

Рис. 4.13. Окно для задания источника сведений для соединения ADO

Provider for ODBC Drivers. После этого нужно перейти на вкладку Подключение или на-
жать кнопку Далее (что одно и то же).

войства связи с данными L*i

Поставщик данных | Подключение | Дополз

Выберите подключаемые данные:

Поставщики OLE DB _ ^'-_j._./^.^:,,_,Ц_^ :^_


MediaCatalogDB OLE DB Provider
MediaCatalogMergedDB OLE DB Provider
MediaCatalogWebDB OLE DB Provider
Microsoft Jet 4.0 OLE DB Provider
Microsoft OLE DB Provider For Data Mining Services
Microsoft OLE DB Providerjpr Micrgsgfj Office Search
||Р|Д||д|;|'1)|^'ДДД|Д|Д
Microsoft OLE DB Provider for OLAP Services 8.0
Microsoft OLE DB Provider for Oracle
Microsoft OLE DB Provider for SQL Server
Microsoft OLE DB Simple Provider
MSDataShape

Отмена ; Справка

Рис. 4.14. На вкладке Поставщик данных нужно выбрать подходящий провайдер OLE DB
242 Borland C++ Builder 6. Разработка приложений баз данных

На этой вкладке можно выбрать имя источника данных (они перечислены на вклад»
Пользовательский DSN приложения Администратор источников данных ODBC) или дви
гаться далее по пути построения строки подключения. Последний вариант нам подходит
Выберите переключатель Использовать строку подключения и нажмите кнопку Сборке
(см. рис 4.15). На экране будет отображено окно Выбор источника данных (см. рис. 4.16)
Здесь снова предлагается выбрать подходящий dsn-файл, но так как его все еще нет, нужнс
нажать кнопку Создать. В следующем окне предлагается список всех доступных драйве-
ров ODBC (рис. 4.17). Сделайте свой выбор и нажмите кнопку Далее. В следующем окне
укажите имя для dsn-файла и снова нажмите кнопку Далее, а в очередном окне — кнопку
Готово.
Цр Свойства связи с данными

Поставщик данных Подключение j Дополнительно | Все |

.Для подключения данных ОШСукажите следующее: :


1. Источник данных:
(~ Использовать имя источника данных

Йспо льэрватьстроку^подключения]
Строка подключения:
Сборка...;
••; • . ..;.' ' ' •

2. Для входа в сервер использовать

Пародь:
Г* Пустой пароль Г" Разрешить сохранение пароля

3. Введите начальный каталог:


Г ™5
HpogepnTb подключение

ок Отмена Сп,

Рис. 4.15. Вкладка Подключение окна Свойства связи сданными


предназначена для сборки строки подключения
Глава 4. Клиент-серверные базы данных 243

Выбор источника данных


~~~

Файловый источник данных I Источник данных компьютера |

(Data Sources
'

1 Выберите файловый источник данных описывающий драйвер, с которым


:
нужно установить связь. Допускается использовать любой источник
! данных, который ссылается на драйвер ODBC, установленный на данном

ОК Отмена Справка

Рис. 4.16. Нажатие кнопки Создать в окне Выбор источника данных


инициирует создание нового dsn-файла
На экране появится окно для окончательной настройки файла dsn. Вид этого окна зави-
сит от используемого драйвера. При использовании драйвера ODBC от EasySoft оно будет
выглядеть так, как изображено на рис. 4.18. В этом окне можно указать нужную базу дан-
ных, имя пользователя и пароль, имя роли и другие сведения о создаваемом соединении.
При помощи кнопки Test можно протестировать подключение к БД. Нажав кнопку ОК, вы
снова перейдете к окну Выбор источника данных (см. рис. 4.16), однако в нем уже будет
содержаться только что созданный dsn-файл. Выберите его и нажмите ОК. Возможно, по-
сле этого вам придется еще раз ввести пароль (в окне EasySoft InterBase DSN Setup), после
чего вы вернетесь к окну Свойства связи с данными, вкладка Подключение которого будет
теперь содержать полностью сформированную строку подключения. Нажимая кнопку ОК,
закройте вначале это окно, а затем и самое первое окно мастера. Согласен, слишком много
окон — можно запутаться. Зато, если все сделано правильно, чтобы активизировать со-
единение, достаточно задать свойству Connected объекта TADOConnection значение true.
Можно, конечно, напрямую задать строку подключения для TADOConnection, исполь-
зуя его свойство ConnectionString. Однако, думаю, вид строки подключения, сформиро-
ванной мастером, убедит вас в том, что такой подход излишне сложен:
Provider=MSDASQL.l;Persist Security Info=False;
User ID=5YSDBA;Extended Properties="DRIVER={Easysoft IB6 ODBC};
UID=SYSDBA;DB=d:\data\MyIBData.gdb;
FILEDSN=C:\Program FilesXCommon Files\ODBC\Data SourcesXEmpl.dsn;"
244 Borland C++ Builder 6. Разработка приложений баз данных

Создание нового источника данных

выберите драйвер, для i


Имя Вереи
Driver para о Microsoft Visual FoxPro 6.01.86
1.00.01 :
Mi его soft Access Driverf.mdb)
MicrDsoftAccess-Treiberf.mdb) 4.00.44
Microsoft dBase Driver f.dbf) 4.00.44
Microsoft dBase VFP Driver f.dbt) 6.01 .ВВ
Microsoft dBase-Treiber f. dbf) 4.00.44
Microsoft Excel Driver (*xls) 4.00.44
Microsoft Excel-Treiber f.xls) 4.00.44
Microsoft FoxPro VFP Driver P.dbfl Б.01.86
<l
Дополнительно..

Далее > Стмена

Рис. 4.17. Список всех доступных драйверов ODBC

В Easysoft InterBase DSN Setup

Database: | D .\Deta\Myl BData.gdb

User Name: (SYSDBA

: Password;

::Role; Г"

With Schema: Г" No Wait: Г

Dialect: |3 jj Pre IB6 MetaData: Г

Execute Proc Г" Leave Quotes: Г


: UID Override: Г Read Only: Г
Character Set: j

Commit Select: Г"

Test I OK Cancel Help

Рис. 4.18. Вид окна настройки соединения с использованием драйвера ODBC


зависит от самого драйвера
Глава 4. Клиент-серверные базы данных 245

Впрочем, свойство ConnectionString может быть полезно, если строку подключения


нужно только слегка подправить.
У объекта TADOConnection есть еще несколько свойств, при помощи которых можно
более гибко управлять настройкой соединения. Некоторые из них аналогичны соответ-
ствующим свойствам компонента TSQLConnection, другие отличаются. Читателю пред-
лагается самостоятельно разобраться в этом.
Замечание
Если механизм dbGo (ADO) применяется для создания многоярусных (multi-tired)
приложений с использованием технологии, ранее называвшейся MIDAS, то вместо ком-
понента TADOConnection следует использовать компонент TRDSConnect/on, также рас-
положенный на вкладке ADO.

Соответствующим образом настроенный объект TADOConnection можно указать


в качестве значения свойства Connection для всех остальных компонентов из набора dbGo
(разумеется, за исключением компонента TRDSConnection). Однако это не единствен-
ный путь подключения компонентов — наборов данных с вкладки ADO к базе данных.
Каждый из этих компонентов имеет собственное свойство ConnectionString. К этому свой-
ству в инспекторе объектов прикреплена кнопка построителя. Щелчок на ней инициирует
то же построение строки соединения, описанное чуть ранее.

Однонаправленные наборы данных dbExpress


• Из семи компонентов, входящих в набор dbExpress, пять представляют собой наборы
данных, причем четыре из них — однонаправленные. Однонаправленные наборы данных
(Unidirectional Datasets) предназначены для быстрого и простого доступа к информации
с минимальной затратой ресурсов. Поэтому однонаправленные наборы данных не исполь-
зуют буферизацию возвращаемых записей, благодаря чему они работают намного эффек-
тивнее. Но, как всегда, любое улучшение одних показателей ведет к ухудшению других.
Однонаправленные наборы данных не поддерживают операции, для выполнения которых
требуется буферизация. Такие операции (точнее, методы и объекты), объявляемые обычно
в классе-предке TDataSet, в однонаправленных наборах данных или не переобъявляются,
или не выполняют никаких действий, или их применение вызывает исключение.
Вот основные возможности, недоступные при применении однонаправленных набо-
ров данных:
Q Как следует из названия, перемещаться по однонаправленному набору данных можно
только в одном направлении: от первой записи к последней. Из методов навигации
можно пользоваться только методами Next и First, применение остальных вызовет ис-
ключение.
О Нельзя использовать закладки. Точнее, методы, осуществляющие переход по закладке,
на самом деле не выполняют никаких действий.
Q Нельзя редактировать данные, так как для этого необходимо поместить редактируе-
мую запись в буфер. Попытка перевести однонаправленный набор данных в режим
редактирования при помощи метода Edit вызовет генерацию исключения. Однако
ничто не мешает организовать собственную процедуру редактирования информации
на основе однонаправленного набора данных (например, с использованием оператора
SQL Update).
246 Borland C++ Builder 6. Разработка приложений баз данных

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
Модули ___..
памяти

Модули памяти DIMM, DIMM DDR и RIMM

Рис. 4.19. Панель навигации, подключенная к однонаправленному набору данных,


имеет только четыре доступные кнопки,причем двумя из них
(Prior и Last) пользоваться нельзя
248 Borland C++ Builder 6. Разработка приложений баз данных

Первое, что бросается в глаза, — заблокированы все кнопки панели навигации, связан-
ные с модификацией набора данных. Нельзя добавлять или удалять записи, нельзя переве-
сти набор данных в режим редактирования и т.д. Кнопки навигации доступны все, но по-
пытка воспользоваться кнопкой 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

Можно обойтись и без компонентов TClientDataSet и TDataSetProvider, организовав


собственную подсистему управления модификацией записей и навигацией по набору
данных. Проиллюстрируем такой подход несложным приложением. В качестве прототипа
для такого приложения воспользуемся тем же примером, что и ранее (см. рис. 4.19).
Прежде всего займемся панелью навигации. Нам понадобятся только первые четы-
ре кнопки панели, связанные с перемещением по записям набора данных. Щелкнув на
элементе развертывания свойства-множества VisibleButtons, можно получить доступ
ко всем его элементам, управляющим видимостью кнопок на панели. Для всех элемен-
тов, кроме первых четырех, задайте значение false. В результате на панели навигации
останутся только кнопки nbFirst, nbPrior, nbNext и nbLast. Все четыре кнопки в режи-
ме функционирования приложения будут доступны, но использование кнопок nbPrior
и nbLast вызовет ошибку.
Чтобы избежать ошибок, можно запретить также и кнопки nbPrior и nbLast. А можно
обеспечить выполнение действий, ожидаемых от нажатия этих кнопок. Именно так мы
и поступим. В первую очередь необходимо отключить действия, автоматически выполня-
емые при нажатии кнопок nbPrior и nbLast (иначе мы будем получать сообщение об ошиб-
ке). Для этого следует воспользоваться событием BeforeAction компонента TDBNavigator.
Обработчик этого события объявлен следующим образом:
typedef void fastcall ( closure *ENavClick)(System::TObject* Sender,
TNavigateBtn Button);
При помощи аргумента Button, передающегося в тело обработчика события, можно
определить, какая из кнопок панели навигации была нажата. Набор констант, принад-
лежащих типу TNavigateBtn, в точности соответствует значениям свойства VisibleButtons.
Событие BeforeAction наступает сразу же после нажатия одной из кнопок панели на-
вигации, непосредственно перед тем как будет выполнено соответствующее действие.
Определив, какая кнопка панели была нажата, можно отменить запрограммированное для
нее действие. Сделать это можно при помощи функции AbortQ. Первый вариант обработ-
чика события BeforeAction достаточно прост:
void fastcall TForml::DBNavigatorlBeforeAction(TObject *Sender,
TNavigateBtn Button)
{
if((Button == nbPrior) || (Button == nbLast))
{
AbortQ;
\
}
Теперь при нажатии «запрещенных» кнопок панели навигации не будет производиться
никаких действий и, как следствие, не будут возбуждаться исключения.
Следующее действие: необходимо заменить все объекты типа TDBEdit на объекты
TEdit, задав для них имена ECatNo (CategoryNo), ECategory (Category) и EDescription
(Description). Это следует сделать, так как редактировать информацию при помощи
объектов TDBEdit не удастся. Для всех трех элементов управления установите свой-
ство ReadOnly в значение true, запретив тем самым редактировать содержащийся
в них текст.
При перемещении по записям набора данных элементы TEdit, в отличие от элементов
управления TDBEdit, не будут автоматически отображать содержимое полей набора дан-
250 Borland C++ Builder 6. Разработка приложений баз данных

ных. Об этом придется позаботиться самому. Ниже приведен код обработчика события
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;

Прежде чем происходит присвоение текущих значений полей таблицы Category,


в коде обработчика AfterScroll проверяется поле первичного ключа CategoryNo на нали-
чие значения. Это кажется бессмысленным, ведь поле CategoryNo не может не содержать
значение, иначе будет нарушена ссылочная целостность. Однако на самом деле такая си-
туация возможна (конечно, не в самом наборе данных (таблице)). При переходе за послед-
нюю запись набора данных или когда набор данных пуст, не существует текущей записи.
Поэтому при обращении вида Р1е1с1ВуШте("\ЛмяПо!\я ") возвращается значение NULL.
Чтобы избежать неприятностей, связанных с выходом за пределы набора данных, и испо-
льзуется упомянутая проверка.
Теперь можно перемещаться по набору данных на первую или следующую запись
набора данных. Однако перемещаться к предыдущей или последней записи пока нельзя,
хотя нажатие на соответствующие кнопки ошибок уже не вызывает. Для осуществления
этих возможностей необходимо знать номер по порядку текущей записи в наборе данных.
Воспользоваться свойством RecNo, как это можно было сделать для баз данных Paradox,
в нашем случае не удастся, так как это свойство для большинства форматов БД всегда
возвращает значение - 1 . Поэтому отслеживать номер текущей записи придется самостоя-
тельно, благо, в данной ситуации это не сложно.
Номер текущей записи будем хранить в переменной NRec. В секции private объявления
класса TForml (в файле Unitl.h) введите следующее объявление:
long NRec;
В коде обработчика события OnCreate формы следует инициализировать переменную
NRec единицей.
Ниже приведен окончательный вариант обработчика события BeforeAction панели на-
вигации, обеспечивающий перемещение по набору данных к предыдущей и к последней
записи, а также отслеживающий номер текущей записи:
v o i d _ fastcall TForrtil : :DBNavigatorlBeforeAction(TObject *Sender,
TNavigateBtn Button)
{
swi tch(Button)
{
case nbFirst: { NRec=l ; break;}
case n b P r i o r : { NRec-=l; break;}
Глава 4. Клиент-серверные базы данных 251

case nbNext: { NRec+=l; break;}


case nbLast: { NRec=SQLTablel->RecordCount ; break;}
}
if ((Button == nbPrior) || (Button == nbLast))
{
try
{
AbortO ;
}
_ finally
{
MoveToRecQ ;

В начале кода обработчика проверяется, какая из кнопок панели навигации была


нажата. В зависимости от этого переменной NRec задается соответствующее значение.
В частности, при переходе к последней записи переменной NRec присваивается общее
количество записей набора данных (свойство RecordCount). Далее, если нажата одна из
кнопок nbPrior или nbLast, отменяется стандартное действие, ассоциированное с кноп-
кой (функция Abort), и выполняется необходимое перемещение по набору данных (метод
MoveToRec).
Функция Abort возбуждает так называемое «молчаливое» исключение (EAbori). Оно
работает как обычное исключение, но пользователю не выдается сообщение об ошибке.
В результате вызова этой функции управление передается на последний блок _ finally,
а если его нет — в конец текущей функции. Таким образом, если не используется блок
операторов try... _ finally, любой оператор, размещенный после Abort, выполняться не
будет. В приведенном выше тексте обработчика события BeforeAction после выполнения
функции Abort управление будет передано методу MoveToRec, так как его вызов располо-
жен в блоке оператора _ finally.
Метод MoveToRec объявлен в секции private класса TForml. Этот метод выполняет
переход к записи набора данных, которая задается переменной NRec. Ниже приведено
определение (реализация) метода MoveToRec:
void _ fastcall TForml: :MoveToRec(void)
{
Process=true;
SQLTablel->First();
for(long i=l; KNRec; i++) SQLTablel->Next() ;
if (NRec == SQLTablel->RecordCount) SQLTablel->Next() ;
Process=false;
}
Перемещаться по однонаправленному набору данных можно только при помощи
методов First и Next. Поэтому в начале метода MoveToRec осуществляется переход на
первую запись, а затем NRec — / раз вызывается метод Next. Чтобы полностью ими-
тировать нажатие кнопки Last, метод Next вызывается еще раз. Этот дополнительный
вызов метода Next выполняет переход за пределы набора данных. (Именно для этого
случая в обработчике события AfterScroll предусмотрена проверка поля первичного
252 Borland C++ Builder 6. Разработка приложений баз данных

ключа на значение 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

: (Видеокарты от различных производителей

Рис. 4.20. Тестовое приложение, позволяющее модифицировать однонаправленный


набор данных, а также перемещаться по его записям в любом направлении

Нажатие кнопок 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;

SQLConnectionl->Execute(SQLText, NULL, NULL)


ECategory->ReadOnly=true;
EDescription->ReadOnly=true;
if (KindOfState == koslnsert) MoveToRecQ;
if (KindOfState == kosDelete)
{
if (NRec > 1) MoveToRecO;
if(NRec == 0)
{
ECatNo->Clear() ;
ECategory->Clear() ;
EDescription->Clear() ;

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 };

class TForml : public TForm


published: // IDE-managed Components
TSQLTable *SQLTablel;
TSQLConnection *5QLConnectionl;
TDataSource *DataSourcel;
256 Borland C++ Builder 6. Разработка приложений баз данных

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);

extern PACKAGE TForml *Forml;


// ----- ..... - .............. -
#endif

Листинг 4.2. Главный модуль приложения SQLTableDemo2

^include <vcl.h>
#pragma hdrstop
#include "Unitl.h"

#pragma package(smart_ini t)
#pragma resource "*.dfm"
Глава 4. Клиент-серверные базы данных 257

TForml *Forml;

_fastcall TForml: :TForml(TComponent* Owner)


: TForm(Owner)

void _ fastcall TForml: :SQLTablelAfterScroU(TDataSet *DataSet)


/
if(!SQLTablel->FieldByName("CategoryNo")->IsNull)
{
ECatNo->Text=SQLTablel->
FieldBy Name ("Category No") ->As String;
ECategory->Text=SQLTablel->
FieldByName("Category")->As5tring;
EDescription->Text=SQLTablel->
FieldByName("Description")->AsString;

void fastcall TForml::DBNavigatorlBeforeAction(TObject «Sender,


TNavigateBtn Button)

switch(Button)

case nbFirst: { NRec=l; break;}


case nbPrior: { NRec-=l; break;}
case nbNext: { NRec+=l; break;}
case nbLast: {
NRec=SQLTablel->RecordCount; break;}
\
if((Button == nbPrior) || (Button == nbLast))
try

AbortO;
finally
MoveToRecO ;

void _ fastcall TForml: :FormCreate(TObject *Sender)


{
NRec=l;
KindOfState=kosBrowse;
Process=false;

9 Зак.319
258 Borland C++ Builder 6. Разработка приложений баз данных

void _fastcall TForml::BEditClick(TObject *Sender)

ECategory->ReadOnly=false;
EDescription->ReadOnly=false;
ECategory->SetFocus();
KindOfState=kosEdit;

void _ fastcall TForml : :Posting(void)


{
if (KindOf State == kosBrowse) return;
switch(KindOfState)
{
case kosEdit:
{
SQLText="Update Category Set Category='" +
ECategory->Text +
" ' , Description^" + EDescn'ption->Text +
'" Where CategoryNo=" + StrToInt(ECatNo->Text) + ";";
break;
}
case koslnsert:
{
SQLText="INSERT INTO Category
(Category, Description) VALUES ('" +
ECategory->Text + "', '" + EDescription->Text +
"'):";
NRec=SQLTablel->0RecordCount+l;
break;
}
case kosDelete:
{
SQLText="Delete From Category Where CategoryNo=" +
ECatNo->Text + ";";
NRec-=l;

SQLConnectionl->Execute(SQLText, NULL, NULL)


ECategory->ReadOnly=true;
EDescription->ReadOnly=true;
if (KindOf State == koslnsert) MoveToRecO;
if (KindOfState == kosDelete)
{
if (NRec > 1) MoveToRecO;
if(NRec == 0)
{
ECatNo->Clear() ;
ECategory->Clear() ;
EDescription->Clear() ;

KindOf State=kosBrowse;
Глава 4. Клиент-серверные базы данных 259

void ^fastcall TForml::BPostClick(TObject «Sender)


{
If((KlndOfState !- kosEdit) && (KindOfState != koslnsert))
return;
int Repl;
Repl=Application->MessageBox(
"Сохранить изменения?",
"Внимание!".
MB_YESNO + MB_ICONQUESTION);
if (Repl == IDYES) PostingO;
else CancelingO;

void fastcall TForml::Canceling(void)


if((KindOfState != kosEdit) && (KindOfState != koslnsert))
return;
ECatNo->Text=SQLTablel->FieldByName("CategoryNo")->AsString;
ECategory->Text=SQLTablel->FieldByName("Category")->AsString;
EDescription->Text=SQLTablel->FieldByName("Description")->
AsString;
Ki ndOfState=kosBrowse;
ECategory->ReadOnly=true;
EDescription->ReadOnly=true;

void _fastcall TForml: :SQLTablelBeforeScroll(TDataSet «DataSet)


{
if(Process) return;
if ((KindOfState == kosEdit) || (KindOfState == koslnsert))
{
int Repl;
Repl=Application->MessageBox(
"Остались несохраненные изменения. Сохранить?",
"Внимэнив ' **
MB_YESNO + MB_ICONQUESTION) ;
if (Repl == IDYES) PostingO;
else CancelingO ;

void _fastcall TForml: :BInsClick(TObject «Sender)


{
KindOfState=kosInsert;
ECatNo->Text="";
ECategory->Text="";
EDescription->Text="" ;
ECategory ->ReadOnly=false;
EDescription->ReadOnly=false;
260 Borland C++ Builder 6. Разработка приложений баз данных

ECategory->SetFocus() ;

void _ fastcall TForml: :MoveToRec(void)


{
Process=true;
SQLTablel->First();
for(long i=l; KNRec; i++) SQLTablel->Next() ;
if(NRec == SQLTablel->RecordCount) SQLTablel->Next()
Process=false;

void _ fastcall TForml: :BDelClick(TObject *Sender)


{
Ki ndOf State=kosDelete ;
int Repl;
Repl=Application->MessageBox (
"Удалить запись?", "Внимание!",
MB_YESNO + MB_ICONQUESTION) ;
if (Repl == IDYES) PostingQ;
else KindOfState=kosBrowse;

void _ fastcall TForml: :FormClose(TObject «Sender.


TCloseAction &Action)
{
if((KindOfState == kosEdit) || (KindOfState == koslnsert))
{
int Repl;
Repl=Application->MessageBox(
"Остались несохраненные изменения. Сохранить?",
"Внимание! " ,
MB_YESNO + MB_ICONQUESTION);
if (Repl == IDYES) PostingO;
else CancelingO ;

Замечание
Приведенный в листингах 4.1 и 4.2 пример приложения далек от совершенства, и его
вряд ли можно использовать на практике. Этот пример призван проиллюстрировать
некоторые приемы работы с однонаправленными наборами данных. Придется прило-
жить еще некоторые усилия, чтобы довести приведенный код до приемлемого уровня.
Особенно это касается случаев использования больших наборов данных.

Компоненты TSQLQuery, TSQLStoredProc и TSQLDataSet


При использовании экземпляра компонента TSQLTable к серверу БД формируется
запрос, возвращающий все поля и все записи таблицы, имя которой указано в свойстве
TableName. Если осуществляется удаленный доступ к таблице, имеющей большое ко-
Глава 4. Клиент-серверные базы данных 261

личество записей, использование компонента TSQLTable неприемлемо. Это становится


понятно, если принять во внимание, что применить фильтр к однонаправленному набору
данных не удастся. В этом случае следует воспользоваться компонентом TSQLQuery.
Компонент TSQLQuery — это программная оболочка вокруг запроса SQL к базе дан-
ных. Строка оператора SQL задается в свойстве SQL. Если запрос возвращает набор дан-
ных, то открыть его можно, указав значение true для свойства Active. Выполнить запрос
на изменение данных (INSERT, UPDATE, DELETE) или структуры БД (один из операторов
определения данных (DDL)) можно при помощи метода ExecSQL. Все, что было сказа-
но в предыдущей главе о компоненте TQuery из набора BDE, в полной мере относится
и к компоненту TSQLQuery. Исключение составляет то, что набор данных, возвращаемый
TSQLQuery, — однонаправленный со всеми вытекающими отсюда последствиями.
Для работы с хранимыми процедурами механизм dbExpress имеет специализирован-
ный компонент. Если свойство DBConnection объекта TSQLStoredProc содержит ссылку на
правильно сконфигурированный объект-соединение, то список свойства StoredProcName
будет содержать перечень всех имеющихся в базе данных хранимых процедур. Если
хранимая процедура возвращает набор данных, то получить доступ к нему можно, задав
для свойства Active значение true. Этот набор данных также будет однонаправленным.
Хранимую процедуру, не возвращающую набор данных (т.е. только выполняющую какие-
либо действия), можно запустить на выполнение при помощи метода ЕхесРгос.
Для работы с входными (input) и выходными (output) параметрами хранимой про-
цедуры предназначено свойство Params. Как только из списка свойства StoredProcName
выбрано наименование хранимой процедуры, свойство Params автоматически заполняет-
ся именами ее входных и выходных параметров. В режиме конструктора для настройки
и использования параметров предназначен специальный редактор, который отображается
на экране после щелчка на кнопке построителя свойства Params. Если выбрать в окне это-
го редактора какой-либо параметр, в инспекторе объектов будут отображены его свойства.
К их числу, кроме имени параметра, относятся также следующие.
Q РагатТуре. Параметр может принимать одно из значений типа ParamType: ptlnput
(входной параметр), ptOutput (выходной параметр), ptinputOutput (параметр использу-
ется и как входной, и как выходной), ptResult (используется для возвращения какого-
либо значения, чаще всего — кода ошибки; параметр этого типа может быть только
один), ptUnknown (параметр неизвестного или неопределенного типа; перед исполь-
зованием хранимой процедуры этому параметру необходимо придать какой-нибудь
определенный тип).
Q DataType. Параметр может иметь один из типов данных, поддерживаемых серверами
БД, которые перечислены в свойстве DriverName соединения TSQLConnection. Даже
если объект TSQLStoredProc подключен к БД определенного формата, список свойства
DataType будет содержать также и типы данных, которые для этого сервера БД недо-
ступны. Поэтому, выбирая тип данных, будьте предельно внимательны.
О Size, Precision и NumericScale. Эти свойства задают размер значения для параметра,
точность и количество знаков после десятичной точки (для чисел с плавающей или
фиксированной точкой).
Q Value. Это свойство содержит значение параметра.
Перед тем, как воспользоваться хранимой процедурой, необходимо задать значения ее
входных параметров. Это можно сделать или в режиме конструктора, воспользовавшись
262 Borland C++ Builder 6. Разработка приложений баз данных

значением свойства Value входного параметра, или в режиме выполнения приложения —


при помощи следующего синтаксиса:
StoredProcl->DBConnection="SQLConnectionl";
StoredProcl->StoredProcName="ADD_EMPLOYEE";
StoredProcl->Params->Clear() ;
StoredProcl->Params->ParamByName("LName")->AsString="HeKTO";
После того как значения для входных параметров заданы, хранимую процедуру можно
выполнить (или открыть). В режиме выполнения приложения для этого нужно вызвать
метод ЕхесРгос, а в режиме конструктора — воспользоваться пунктом Execute контекст-
ного меню объекта TSQLStoredProc. Открыть набор данных, возвращаемый хранимой
процедурой, можно или задав значение true свойству Active объекта TSQLStoredProc, или
вызвав его метод Open. Выходные параметры могут быть у хранимых процедур любого
типа, в том числе и у процедур, возвращающих набор данных. Получить доступ к их
значениям можно в режиме выполнения приложения с помощью того же синтаксиса, что
использовался для входных параметров. В режиме конструктора следует воспользоваться
свойством Value выходного параметра.
И последний однонаправленный наследник класса TDataSet, входящий в состав ме-
ханизма dbExpress, — компонент TSQLDataSet. Это наиболее общий компонент из всех,
упомянутых в этом разделе. С его помощью в клиентском приложении можно пред-
ставлять и таблицу, и запрос, и хранимую процедуру. Основные свойства компонента
TSQLDataSet — CommandType и CommandText. Вначале, конечно, нужно связать свой-
ство SQLConnection с правильно настроенным объектом TSQLConnection. В свойстве

-'

EMPLOYEE
INVOICES
ITEMS
MONLIST
STORAGE

CATEGORY
CATEGORYNO
DESCRIPTION

Рис. 4.21. Редактор, прикрепленный к свойству CommandText компонента


TSQLDataSet
Глава 4. Клиент-серверные базы данных 263

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.

Клиентский набор данных TSQLCIientDataSet


Компонент TSQLClientDataset — единственный наследник класса TDataSet из набора
dbExpress, на который не распространяются ограничения, связанные с отсутствием буфе-
ризации записей. Дело в том, что любой клиентский набор данных оперирует со своей
собственной копией набора данных, для которой поддерживается механизм буферизации
со всеми вытекающими отсюда последствиями. Благодаря этому клиентский набор дан-
ных может использоваться для создания клиентских приложений, в которых используют-
ся различные варианты доступа и модификации данных. Основное назначение любых
клиентских наборов данных —работа в режиме кэширования изменений. Кроме того, при
помощи клиентских наборов данных можно организовать файловую модель доступа, именуе-
мую MyBase, а также портфельную модель БД. Любой клиентский набор данных можно со-
хранить в виде файла xml или cds; можно также загрузить его из файла xml или cds.
Механизм MyBase в чистом виде подразумевает, что клиентский набор данных будет
работать с набором данных (таблицей), который хранится в одном из файлов на локаль-
ном диске. Из такого файла данные загружаются в объект TSQLCIientDataSet и после мо-
дификации выгружаются в тот же файл или в другой, по выбору.
Внешний файл, в котором хранится набор данных, может быть одного из двух ти-
пов — с расширением xml или cds. Если такой файл, содержащий набор данных, у вас уже
есть, использовать для подключения к нему объект TSQLConnection не нужно. Достаточно
расположить на форме клиентский набор данных TSQLCIientDataSet и указать в свойстве
FileName имя и путь к файлу. Это свойство снабжено кнопкой построителя, щелчок на
которой отображает на экране стандартный диалог открытия файла. Еще один вариант
подключения— в режиме конструктора выбрать пункт Load from MyBase table из кон-
текстного меню объекта TSQLCIientDataSet. Далее можно подключить кэкзмепляру
TSQLCIientDataSet объект TDataSource, а к нему, в свою очередь, — любой из элементов
визуализации и управления данными, в том числе и сетку (TDBGrid). Сразу после откры-
тия клиентского набора данных информацию можно просматривать или редактировать.
264 Borland C++ Builder 6. Разработка приложений баз данных

Кроме того, загрузить данные из файла в клиентский набор данных можно при помо-
щи метода 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

Получить денные! Отключить I Сохранить данные I

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 бит,

Рис. 4.22. Простое тестовое приложение,


иллюстрирующее использование портфельной модели данных
Кнопка BGetData (подпись Получить данные) предназначена для копирования на-
бора данных, который задан свойством CommandText клиентского набора данных
SQLClientDataSetl , во внешний файл на локальном диске. Ниже приведен примерный вид
обработчика события нажатия кнопки BGetData:
void _ fastcall TForml: :BGetDataClick(TObject «Sender)
int repl;
if (FileExists(DataFileName))

repl=Application->MessageBox(
"Файл существует. Заменить?",
"Внимание!",
MB_YESNO + MB_ICONQUESTION);

if(repl == IDNO) return;


SQLClientDataSetl->Active=false;
SQLClientDataSetl->FileName="";
SQLClientDataSetl->DBConnection=SQLConnectionl;
SQLClientDataSetl->CommandType=ctTable;
SQLClientDataSetl->CommandText="ITEMS";
SQLClientDataSetl->Active=true;
SQLClientDataSetl->SaveToFile(DataFileName, dfBinary);
SQLClientDataSetl->Active=false;
BOnOff->Caption = "Подключить";

Предполагается, что имя внешнего файла портфельного набора и путь к нему хранятся
266 Borland C++ Builder 6. Разработка приложений баз данных

в глобальной переменной DataFileName, которая определяется так:


Ansi String DataFileName="d: \ \ D a t a \ \ B r i e f . c d s " ;
Конечно, имя файла и путь могут быть другими.
В первых строках кода обработчика события нажатия кнопки BGetData проверяется
наличие заданного файла. Если в указанной папке существует файл с заданным именем,
пользователю выдается запрос на разрешение перезаписи этого файла актуальными дан-
ными с сервера БД. Если пользователь не согласен, обработчик события заканчивает на
этом свою работу. В противном случае выполняется набор операторов, записывающих
набор данных во внешний файл. Вначале закрывается набор данных, связанный с объек-
том SQLClientDataSet. Это следует сделать, так как последующие действия можно выпол-
нять только с закрытым набором данных. К этим действиям относятся: очистка свойства
Filename, подключение к соединению SQLClientDataSet 1, задание в качестве исходного
набора данных таблицы (ctTable) ITEMS. Обратите внимание: имя таблицы необходимо
набрать прописными буквами, иначе будет выдано сообщение о том, что имя таблицы,
запроса или хранимой процедуры неизвестно.
Прежде чем воспользоваться методом SaveToFile, необходимо открыть набор данных,
так как это возможно только при открытом наборе. Затем его можно закрыть. Последний
оператор обработчика события нажатия кнопки BGetData задает для кнопки BOnOffaoy-
пись Подключить. В результате выполнения обработчика на локальном диске будет соз-
дан файл с расширением cds, в котором будут храниться все записи таблицы Items.
Кнопка BOnOff предназначена для подключения и отключения от файла с набором
данных таблицы Items. Обработчик события нажатия этой кнопки может иметь примерно
такой вид:
void _ fastcall TForml: :BOnOffClick(TObject «Sender)
{
if (BOnOff->Caption == "Подключить")
{
SQLClientOataSetl->DBConnection=NULL;
SQLClientDataSetl->FileName=DataFileName;
SQLClientDataSetl->Active=true;
BOnOff->Caption = "Отключить";
else

SQLClientDataSetl->Act1ve=false;
BOnOff->Caption = "Подключить";

В коде обработчика вначале проверяется текст подписи кнопки. Если кнопка имеет
подпись Подключить, выполняется подключение к файлу с набором данных. Для этого
очищается свойство DBConnection (на всякий случай, ведь неизвестно в какой ситуа-
ции может быть нажата кнопка), а затем в свойство FileName записывается имя и путь
к нужному файлу. В заключение набор данных открывается и текст подписи кнопки ме-
няется на Отключить. Для отключения от файлового набора данных достаточно закрыть
набор данных.
Кнопка BSaveData с текстом подписи Сохранить данные предназначена для синхро-
Глава 4. Клиент-серверные базы данных 267

низации файлового набора данных с соответствующим набором данных (таблицами),


хранящимся на сервере. Прежде чем привести текст обработчика события нажатия этой
кнопки, следует сделать несколько замечаний.
Если свойство LogChanges имеет значение true (это значение по умолчанию), то после
сохранения набора данных в файле компонент TSQLClientDataSet отслеживает все изме-
нения, сделанные в файловой версии набора данных (модификация, добавление, удаление
записей). Доступ к внутреннему набору данных объекта TSQLCLientDataSet можно полу-
чить при помощи свойства Data. При подключении к файловому набору данных свойство
Data содержит набор данных, скопированный в файл из таблиц сервера. Все изменения,
сделанные после отключения от сервера, сохраняются отдельно; доступ к ним можно по-
лучить при помощи свойства Delta, которое имеет тип OleVariant. Количество изменений
можно получить при помощи свойства ChangeCount.
Сохранить на сервере все изменения, сделанные в файловом наборе данных, можно
при помощи методу ApplyUpdates. Вначале необходимо подключиться к серверу (очистить
свойство FileName и связать свойство DBConnection с объектом TSQLConnection), а затем
вызвать метод ApplyUpdates, объявленный так:
virtual int fastcall ApplyUpdates(int MaxErrors);
Единственный аргумент метода ApplyUpdates — MaxErrors — задает количество
ошибок при попытке обновить набор данных на сервере, при достижении которого об-
новление прекращается. Для свойства MaxErrors можно задать значение -1; в этом случае
никаких ограничений на количество ошибок не будет.
При работе метода ApplyUpdates выполняются следующие действия.
Q Генерируется событие Before ApplyUpdates.
Q Вызывается внутренний провайдер компонента TSQLClientDataSet, который на основе
свойства Delta пытается обновить набор данных на сервере. Если произошли ошибки,
внутренний провайдер возвращает информацию о конфликтных записях.
Q Генерируется событие After ApplyUpdates.
Q Вызывается метод Reconcile. Этот метод обрабатывает информацию о записях, об-
новление которых вызвало ошибки, и для каждой такой записи генерирует событие
OnReconcileError. Кроме того, из свойства Delta удаляется информация о записях,
успешно сохраненных на сервере. Вызывается также метод MergeChangeLog для
встраивания успешно сохраненных изменений в свойство Data, и соответствующим
образом меняется значение свойства ChangeCount.
Метод ApplyUpdates возвращает целое число, обозначающее количество ошибок при
обновлении записей на сервере.
Если свойство LogChanges имеет значение false, все изменения сразу вносятся
в свойство Data. В этом случае невозможно обновить набор данных на сервере на основе
изменений, сделанных в файле. Такой подход можно использовать только при организа-
ции исключительно файловой БД. Это может несколько повысить производительность
приложения.
Ниже приведен код обработчика события нажатия кнопки BSaveData:
void fastcall TForml::BSaveDataClick(TObject *Sender)
{
int repl;
if(SQLClientDataSetl->ChangeCount == 0) return;
268 Borland C++ Builder 6. Разработка приложений баз данных

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

Рис. 4.23. Результаты деятельности объекта TSQLMonitor


Использование автоматической записи в файл сообщений SQL — наиболее простой
и наименее управляемый способ применения компонента TSQLMonitor. Более гибкий
и удобный способ — использование события OnLogTrace. Обработчик этого события
объявлен так:
typedef void fastcall ( closure *TTraceLogEvent)
(System::TObject* Sender, pSQLTRACEDesc CBInfo);
272 Borland C++ Builder 6. Разработка приложений баз данных

Событие 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();

Для тех же целей можно воспользоваться другим событием компонента TSQLMonitor.


Событие ОпТгасе происходит непосредственно перед тем, как сведения о событии будут
записаны в список строк TraceList. Обработчик этого события объявлен так:
typedef void fastcall ( closure *TTraceEvent)
(System::TObject* Sender, pSQLTRACEDesc CBInfo, bool &LogTrace);
Кроме первых двух аргументов, которые описывались при рассмотрении события
OnLogTrace, в тело обработчика этого события передается также и аргумент LogTrace ло-
гического типа. По умолчанию этот аргумент имеет значение true, т.е. текущее сообщение
записывается в список TraceList. Если событие не нужно записывать в список TraceList,
задайте для аргумента LogTrace значение false.
Компонент TSQLMonitor имеет еще несколько полезных свойств, достойных упоми-
нания. Свойство целого типа TraceCount, доступное только для чтения, возвращает коли-
чество сообщений, содержащихся в свойстве TraceList. После очистки свойства TraceList
(метод Clear) свойство TraceCount сбрасывается в 0. При помощи свойства MaxTraceCount
целого типа можно задать максимальное количество сообщений, которые будут записаны
в список сообщений TraceList.
Вот, пожалуй, и все, что хотелось сказать о компоненте TSQLMonitor. Замечу лишь,
что использовать его можно не только для отладочных целей. Можно также исследо-
вать количество и тип действий, генерируемых в ответ на те или иные операции, вы-
полняемые по отношению к серверу. Кроме того, можно определить, какое количество
информации передается от клиентского приложения серверу и обратно. Это поможет
оптимизировать приложение, заменив громоздкие и сложные операции более простыми
и эффективными.

Резюме
В этой главе были рассмотрены основные вопросы разработки клиентских при-
ложений, взаимодействующих с клиент-серверными базами данных. Упор делался на
использование локального сервера Interbase. Вначале были рассмотрены приемы ра-
боты с основными инструментами, поставляемыми с сервером Interbase, — утилитами
IBConsole и ISQL. Было показано, как с их помощью можно создавать и управлять база-
ми данных, таблицами, индексами, представлениями, доменами, триггерами и другими
объектами Interbase.
Большое внимание было уделено использованию баз данных Interbase. В частности,
был приведен обзор типов данных, поддерживаемых сервером Interbase, описаны приемы
манипуляции объектами Interbase (создание, удаление, модификация), создание системы
безопасности и управление ею.
Заключительная часть главы посвящена описанию основных приемов создания
клиентских приложений. Рассмотрены способы подключения к базам данных Interbase
при помощи механизмов доступа к данным dbExpress, IBExpress и ADO. Были подроб-
274 Borland C++ Builder 6. Разработка приложений баз данных

но рассмотрены все компоненты, содержащиеся на вкладке dbExpress. Основное BHJ


мание уделялось приемам использования однонаправленных наборов данных, к чис!
которых принадлежат TSQLTable, TSQLQuery, TSQLStoredProc и TSQLDataSet. Быт
показано, как при помощи небольшого по объему кода обеспечить возможность реда!
тировать однонаправленный набор данных и перемещаться по его записям в любо
направлении.
В заключение были рассмотрены компоненты TSQLClientDataSet и TSQLMonitor. Пр
описании клиентского набора данных основное внимание было уделено использованш
модели MyBase (файловые базы данных) и портфельной модели доступа к данныл
Показано, как использовать компонент TSQLMonitor для отслеживания сообщений, коте
рыми обмениваются объект-соединение TSQLConnection и сервер баз данных.
Документирование
и анализ информации
В ЭТОЙ ГЛАВЕ
Создание отчетов при помощи набора компонентов Quick Report
Мастер создания табличных отчетов
Компонент TQuickRep
Компоненты полос отчета
Компоненты TQRLabel, TQRDBText, TQRExpr и TQRSysData
Отчеты с группировкой
Краткий обзор всех компонентов Quick Report
Набор компонентов Decision Cube
''". " ' Резюме

Р
ано или поздно при проектировании приложений БД возникает необходимость соз-
давать различные отчетные и аналитические формы. Такие формы позволяют по-
лучать твердые копии (т.е. распечатывать на бумаге) заранее сформированных набо-
ров данных (таблиц, запросов и т.д.). Для генерации отчетных форм (или просто отчетов)
предназначен набор компонентов 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. Разработка приложений баз данных

Мастер создания отчетов


Один из самых простых способов создать отчет — воспользоваться услугами мастер
Несмотря на то, что мастер этот достаточно неуклюж и неудобен, все же он пригодитс
для знакомства с основными компонентами, необходимыми при создании отчета. Прежд
всего, необходимо создать новое приложение. Запустить на выполнение мастер генераци
отчетов можно, выбрав пункт Other меню File \ New. В открывшемся окне New Items nepei
дите на вкладку Business и дважды щелкните на позиции Quick Report Wizard, На экран
будет отображено первое окно мастера (рис. 5.1).

Creates aiisiing style report with fields from


one table.

Рис. 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

Рис. 5.2. Второе окно мастера генерации отчетов


предназначено для выбора источника данных

Швш
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 Ц

iNo Item Category Description


1 Samsung ML-4500 1 A4, GDI, ОЗУ 2 Мб, 600 dpi. до 8 ppm, лотс
2 Samsung ML-1210 1 A4, GDI, ОЗУ 8 Мб, 600 dpi, до 12 ppm, Jioi
3 Epson EPL-5900L 1 A4, GDI, ОЗУ 2(13) Мб, 600 dpi. до 12 ррт
4 Canon LBP-810 1 A4, GDI, 600 dpi, до 8 ppm, лоток 100 л
5 Brother HL-1240 Mono Laser 1 A4. ОЗУ 2М6, 600x600 dpi, до 12 ppm.
6 Epson AcuLaser C1000w 1 A4, цвет, ОЗУ 16(256) Мб, 600 dpi/2400 dpig
7 Epson AcuLaser C2000 1 А4, цвет, ОЗУ 64(512) Мб, 600 dpi/2400 dpi

Рис. 5.4. Окно Print Preview, содержащее готовый отчет


Окно Print Preview основано, по-видимому, на компоненте QRPreview, который входит
в состав QuickReport. К сожалению, воспользоваться этим компонентом в своем приложении
затруднительно, так как в справочной системе Quick Report сведений о нем нет. Что касается
самого окна Print Preview, то в нем предоставляется ряд удобных возможностей. Доступ к ним
можно получить при помощи кнопок панели инструментов. Первые три кнопки, расположен-
ные в левой части панели, предназначены для масштабирования отчета. С их помощью можно
задать размер отчета, чтобы страница целиком помещалась в окне (Zoom to fit) или занимала
все окно по ширине (Zoom to width), и, наконец, нормальный размер страницы (100%).
Следующие четыре кнопки панели инструментов предназначены для навигации по
страницам отчета. С их помощью можно перейти к первой, предыдущей, следующей
Глава 5. Документирвоание и анализ информации 279

и последней странице. Нажатие кнопки 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 у вас возникнут проблемы с указанием имени пользователя и пароля, так
как окно для их ввода отображено не будет, и вы получите сообщение об ошибке. В ходе
работы мастер предлагает выбрать в качестве источника данных только таблицы БД, в то
время как в подавляющем большинстве случаев в этом качестве используются запросы.
Учитывая все сказанное, можно сделать вывод, что использовать мастер генерации от-
четов нецелесообразно. Поэтому в следующем разделе мы рассмотрим, как создать про-
стой отчет в режиме конструктора самим.

Создание простого отчета в режиме конструктора


Создать пустой отчет в режиме конструктора несложно. Нужно просто открыть новую
форму (пункт Form меню File \ New) и добавить к ней экземпляр компонента TQuickRep.
Объект TQuickRep, размещенный на форме, напоминает лист тетради в клеточку. При раз-
мещении его на форме он занимает место, соответствующее размеру листа бумаги фор-
мата А4 (примерно 1123 х 794 пиксел). Несмотря на это, вы можете перемещать объект
TQuickRep по форме и менять при этом его размеры как угодно.
На следующем шаге следует настроить источник данных для отчета. Воспользуемся
для этого механизмом dbExpress (назло мастеру генерации отчетов). Добавьте к форме,
содержащей объект TQuickRep, экземпляры компонентов TSQLConnection и TSQLDataSet,
оставив их имена по умолчанию. Хотя правильно было бы использовать модуль данных
и разместить эти объекты именно там. Настройте соединение, подключив его к базе дан-
ных MylBData, после чего свяжите с ним объект SQLDataSetl. Задайте в качестве значе-
ния свойства CommandText следующую строку:
SELECT SALES.SALEDATE, SALES.CHECKN, EMPLOYEE.LNAME,
ITEMS.ITEM, SALEPARTS.QTY
FROM SALES
INNER JOIN SALEPARTS
ON (SALEPARTS.SALE = SALES.SALENO)
INNER JOIN EMPLOYEE
ON (EMPLOYEE.EMPLOYEENO = S A L E S . E M P L O Y E E )
INNER JOIN ITEMS
ON (ITEMS.ITEMNO = SALEPARTS.ITEM)
WHERE SALES.SALEDATE BETWEEN ' 6 / 1 / 0 2 ' AND ' 6 / 3 0 / 0 2 '
280 Borland C++ Builder 6. Разработка приложений баз данных

Запрос, основанный на приведенной строке оператора SQL, использует четыре ев:


занные между собой таблицы. Он возвращает набор данных, содержащий значения поле
CheckN из таблицы Sales, LName из таблицы Employee, Item— из Items иСИу— i
SaleParts. В набор данных отбираются записи из связанных таблиц, для которых знач<
ние поля SaleDate таблицы Sales лежит в пределах между 01.06.02 и 30.06.02. Другим
словами, запрос формирует отчет о продажах за июнь 2002 г.
Для определения таблиц Sales и SaleParts, входящих в приведенную строку запрос;
можно воспользоваться операторами SQL, которые показаны в листинге 5.1.

Листинг 5.1. Отрывок сценария, который можно использовать для определения табли
SALES и SALEPARTS

CREATE GENERATOR SALE_GEN;

CREATE TABLE SALES


(
SALENO PKEYFIELD,
SALEDATE TIMESTAMP NOT NULL,
CHECKN VARCHAR(IO) NOT NULL,
EMPLOYEE FKEYFIELD,
COMMENT VARCHAR(SO).
PRIMARY KEY (SALENO),
UNIQUE (CHECKN)
);
ALTER TABLE S A L E S ADD CONSTRAINT SALETOEMP FOREIGN KEY (EMPLOYEE)
REFERENCES EMPLOYEE (EMPLOYEENO)
ON UPDATE CASCADE ON DELETE CASCADE;
SET TERM л ;

CREATE TRIGGER SET_SALE FOR SALES


ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
NEW.SaleNo=GEN_ID(Sale_GEN,l);
END
Л

SET TERM ;л

CREATE GENERATOR SALEPARTS_GEN;


CREATE TABLE SALEPARTS

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

ALTER TABLE SALEPARTS


ADD CONSTRAINT SALEPARTSTOSALES FOREIGN KEY (SALE)
REFERENCES SALES («SALENO»)
ON UPDATE CASCADE ON DELETE CASCADE;
Л
SET TERM ;
CREATE TRIGGER SET_SALEPARTS FOR SALEPARTS
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
NEW.SalePartNo=GEN_ID(SaleParts_GEN,l);
END
л

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. Разработка приложений баз данных

Примерно то же нужно проделать и с полосой ColumnHeaderBand. Разместите на не:


пять экземпляров компонента TQRLabel, выровняв их по горизонтали с соответствую!!!
ими объектами, расположенными на полосе DetailBand. Последовательно задайте дл:
них следующие подписи (свойство Caption): CheckNo, SaleDate, Employee, Item и Quan
tity. Чтобы отделить заголовки столбцов от данных, разверните свойство Frame полось
и задайте значение true для подсвойств DrawBottom и DrawTop. Затем добавьте на полос]
TitleBand экземпляр компонента TQDLabel. Задайте для его свойства Caption знамени*
«.Продажи за июнь 2002 г» и выровняйте по центру полосы. Этот заголовок будет ото-
бражаться на первой странице отчета. Для того чтобы выделить заголовок, можно вос-
пользоваться свойством Font объекта TQRLabel. Например, можно задать размер шрифта
в 12 пунктов и стиль отображения fsBold.
Просмотреть отчет, не выходя из режима конструктора, можно при помощи пункта
Preview контекстного меню объекта TQuickRep. Сделать то же в режиме выполнения при-
ложения можно при помощи строки кода:
Form2->QuickRepl->Preview() ;
Получившийся отчет в режиме конструктора представлен на рис. 5.5, а в режиме про-
смотра — на рис. 5.6.
pjfp^r~

|ТН* Продажи
,__::
за июнь. J*5>ft
_ _ „ _ . _
t"heckNo!TfealeDate Employee Htem
bhlECKN ^ALEDATEtNAME ITEM
.__-*=-- - _;• « *

Рис. 5.5. Простейший отчет табличного типа, созданный в режиме конструктора


В следующем разделе будет более подробно рассмотрен компонент TQuickRep, лежа-
щий в основе отчетов.

Компонент TQuickRep
Форма, на которой размещен экземпляр объекта TQuickRep, становится контейнером
для отчета. В этом случае она называется формой отчета. Ранее мы уже использовали
основные свойства компонента TQuickRep — DataSet и Bands. Свойство DataSet предна-
значено для организации связи отчета с одним из наборов данных любого из механизмов
доступа к данным (BDE, dbExpress и т.д.).
Использование свойства Bands — один из путей добавления в отчет полос, которые
являются экземплярами компонента TQRBand. Развернув свойство Bands, можно по-
лучить доступ к любому из подсвойств логического типа, имеющих в наименовании
префикс Has. Меняя значения этих подсвойств, можно добавить или удалить из отчета
следующие полосы (курсивом выделены имена полос, т.е. объектов типа TQRBand, а не
компонентов).
Глава 5. Документирвоание и анализ информации 283
•~l'. Процажи за июнь 2002 г.

|а|Щев
1
*
И < > И | т т \ У ~ gosej
1
'

Продажи за июнь 2002 г.


1
CheckNo SaleDate Employee Item
Chk-166 01.06.02 Бобров Epson Stylus Photo 810 _J
Chk-166 01.06.02 Бобров Socket 423 (Pentium 4) Intel DSSOGBG^;;
Chk-166 01.06.02 Бобров Epson Stylus Color C80
Chk-166 01.06.02 Бобров Canon CanoScan N656U
Chk-167 02.06.02 Живой Intel Pentium 4 1.6 GHz
Chk-168 02.06.02 Живой HP DeskJet 980C
Chk-168 02.06.02 Живой Socket 478 (Pentium 4) Abit 7H7II
Chk-168 02.06.02 Живой Canon CanoScan D646U jH
Chk-168 02.06.02 Живой DIMM128MbPC133. NCP
Chk-169 02,06.02 Живой 17"(16") Samsung 753DFX
ШУШИ ll^.,.. | . " V V . . . :'•;;.,,;.

\^°*&*~

Рис. 5.6. Простейший отчет табличного типа в режиме просмотра

Q PageHeaderBand (подсвойство HasColumnHeader). Эта полоса печатается в самом


начале каждой страницы отчета, поэтому ее используют для организации верхнего
колонтитула отчета. Будет ли печататься полоса верхнего колонтитула на первой стра-
нице отчета, зависит от значения подсвойства FirstPageHeader свойства Option ком-
понента TQuickRep. По умолчанию это подсвойство имеет значение true, т.е. верхний
колонтитул печатается на первой странице.
О TitleBand (подсвойство HasTitle). Полоса TitleBand (точнее, расположенные на ней
элементы управления) печатается один раз в начале первой страницы отчета, а если
на этой странице печатается и полоса верхнего колонтитула, то полоса заголовка по-
мещается после него. В силу этих свойств полоса TitleBand используется в основном
для печати заголовка отчета.
Q ColumnHeaderBand (подсвойство HasColumnHeader). Элементы управления, располо-
женные на этой полосе, печатаются один раз в начале каждой страницы отчета, после
полос верхнего колонтитула и заголовка отчета (если они есть на странице), непосред-
ственно перед полосой детализации (см. ниже). Чаще всего на этой полосе размеща-
ются элементы управления TQRLabel, подписи которых служат в качестве заголовков
столбцов в табличном отчете.
Q DetailBand (подсвойство HasDetail). Значения элементов управления, размещенных на
этой полосе, повторяются в отчете для каждой записи набора данных, на который ссы-
лается свойство DataSet объекта TQuickRep. В простейшем случае на полосе детализа-
ции по горизонтали размещаются элементы управления TQRDBText, ссылающиеся на
поля набора данных, который указан в свойстве DataSet. Таким образом, в отчет по-
следовательно выводятся значения указанных полей для всех записей набора данных.
284 Borland C++ Builder 6. Разработка приложений баз данных

Полоса детализации помещается на странице вслед за полосами верхнего колонтитул;


заголовка отчета и заголовка столбцов (если они есть).
U SummaryBand (подсвойство HasSummary). Полоса итогов выводится один раз в конц
отчета, сразу после завершения вывода полосы детализации. Чаще всего эта полос
используется для печати различной итоговой информации отчета.
О PageFooter (подсвойство HasPageFooter). Полоса нижнего колонтитула печатаете:
в нижней части каждой страницы отчета. Если подсвойство LastPageFooter свойств;
Options объекта TQuickRep имеет значение false, то нижний колонтитул не печатаете!
на последней странице отчета.
Чтобы проиллюстрировать приемы использования полос отчета, можно создать при
мер табличного отчета, содержащего все вышеупомянутые полосы. Итак, создайте но
вую форму и добавьте к ней экземпляр компонента TQuickRep. Далее, нужно настроит)
источник данных для отчета. Снова воспользуемся услугами механизма dbExpress. Hz
макет отчета добавьте по одному экземпляру компонентов TSQLConnection и TSQLDa-
taSet. Соединение, как и раньше, настройте на подключение к базе данных MylBData
Объект SQLDataSetl свяжите с объектом SQLConnectionl, а в качестве значения свойства
CommandText введите следующую строку:
SELECT SALES.SALEDATE, SALES.CHECKN, ITEMS.ITEM,
SALEPARTS.QTY, ITEMS.PRICE
FROM ITEMS
INNER JOIN SALEPARTS
ON (SALEPARTS.ITEM = ITEMS.ITEMNO)
INNER JOIN SALES
ON (SALEPARTS.SALE = SALES.SALENO)
WHERE SALES.SALEDATE BETWEEN ' 0 1 . 0 6 . 0 2 ' AND ' 3 0 . 0 6 . 0 2 ' ;
Приведенный выше оператор SQL очень напоминает оператор, использовавшийся
в предыдущем примере. Запрос возвратит набор данных, состоящий из значений полей
SaleDate, CheckN, Item, Qty и Price. В итоговый набор данных будут отбираться только
записи, соответствующие продажам в июне месяце 2002 г. Откройте набор данных, задав
его свойству Active значение true.
Разверните свойство Bands и для всех входящих в него подсвойств задайте значение true.
В результате в отчет будут добавлены все полосы. Начнем создание отчета с полос верхнего
и нижнего колонтитула. На полосу PageHeaderBand добавьте два экземпляра компонента
TQRSysData. Один из них выровняйте по левому краю полосы, другой — по правому.
Основное свойство компонента TQRSysData— Data. Список, прикрепленный
к свойству Data, содержит перечень констант, указывающих, какие именно данные будет
отображать объект TQRSysData. Можно выбрать одно из следующих значений:
О qrsDate — текущая системная дата.
U qrsTime — текущее системное время.
Q qrsDateTime — текущая системная дата и время.
Q qrsReportTitle — текст заголовка отчета, хранящийся в свойстве ReportTitie объекта
TQuickRep. Этот же текст отображается и в заголовке окна Print Preview.
U qrtPageNumber — номер текущей страницы отчета. Обычно используется в полосах
верхнего или нижнего колонтитула для нумерации страниц.
Глава 5. Документирвоание и анализ информации 285

Q qrtDetailNo— номер по порядку текущей строки набора данных, отображаемой


в полосе детализации. Обычно используется для нумерации строк отчета.
Q qrsDetailCount — общее количество строк, выведенных в полосу детализации.
Вернемся к верхнему колонтитулу. Объект TQRSysData, расположенный в правой
части полосы, должен отображать системную дату на момент печати отчета, поэтому за-
дайте для его свойства Data значение qrsDate. Для свойства Data объекта TQRSysData,
который размещен у левой кромки полосы верхнего колонтитула, задайте значение qrs-
ReportTitle. В свойство ReportTitle объекта TQuickRep введите какой-нибудь подходящий
текст, например, «Продажи за июнь 2002 г.». Именно этот текст и будет отображаться
в левой части верхнего колонтитула. В заключение, разверните свойство Frame полосы
PageHeaderBand и задайте для подсвойства DrawBottom значение true. В результате верх-
ний колонтитул будет отделен от остального содержимого отчета горизонтальной линией.
Чтобы верхний колонтитул не отображался на первой странице отчета, следует задать
значение false подсвойству FirstPageHeader свойства Options объекта TQuickRep.
Нижний колонтитул будет использоваться для отображения номеров страниц от-
чета. Разместите по центру полосы PageFooterBand еще один экземпляр компонента
TQRSysData. Для его свойства Data задайте значение qrtPageNumber. Чтобы отделить
нижний колонтитул от остального содержимого страницы, нужно задать значение true
подсвойству DrawTop свойства Frame полосы PageFooterBand.
На полосу заголовка отчета (TitleBand) добавьте объект TQRLabel и введите в его свой-
ство Caption ту же строку, что и в свойство ReportTitle объекта TQuickRep. Сам объект
можно разместить по левому краю полосы заголовка отчета.
Полосы детализации и заголовков столбцов настраиваются примерно так же, как
и в предыдущем примере. В полосе DetailBand разместите экземпляр компонента
TQRSysData, вслед за ним по горизонтали пять экземпляров компонента TQRDBText
и один экземпляр компонента TQRExpr. Отчет будет содержать семь столбцов. Над каж-
дым столбцом на полосе ColumnHeaderBand разместите семь экземпляров компонента
TQRLabel, задав для их подписей (свойство Caption) следующие значения (слева на-
право): «Лг», «SaleDate», «CheckNo», «Item», «Quantity», «Price» и «Cost». Чтобы немного
выделить наименования столбцов, можно добавить в стиль шрифта каждого из объектов
TQRLabel значения fsBold ufsltalic.
Свойству Data объекта TQRSysData, который размещен в полосе детализации, задайте
значение qrtDetailNo. Здесь будут отображаться номера строк отчета. Следующие пять
объектов (экземпляров компонента TQRDBText) нужно подключить к набору данных
SQLDataSetl (свойство DataSet). В качестве поля данных (свойство DataField) для каж-
дого из этих элементов последовательно (слева направо) укажите следующие поля набора
данных: SALEDATE, CHECKN, ITEM, QTY, PRICE.
И последний элемент, который мы разместили на полосе детализации, — экземпляр
компонента TQRExpr. Этот компонент предназначен для отображения различных вы-
числяемых значений, основанных, в том числе, и на значениях полей набора данных.
В отличие от компонента TQRDBText, компонент TQRExpr не имеет свойства DataSet.
Основное его свойство — Expression — должно содержать выражение, при помощи ко-
торого будут вычисляться значения для отображения. Это выражение может содержать
и ссылку на один или несколько доступных наборов данных.
Свойство Expression снабжено кнопкой построителя, при помощи которой можно
отобразить на экране окно мастера выражений (Expression Wizard). Этот мастер может
286 Borland C++ Builder 6. Разработка приложений баз данных

Enter expression;
SQLDataSetl .QTY" SQLDataSetl PRICE

Insert a* cursor position


V'Databasefield

* I / - j 5 > I <> I <- I >- I NotlAndj Or

Рис. 5.7. Диалоговое окно мастера выражений

Insert database Weld in expression —


Select dataset Available fields
SALEDATE
CHECKN
ITEM
QTY
PRICE

Рис. 5.8. Диалоговое окно, облегчающее вставку в выражение поля набора данных
значительно облегчить работу по вводу нужного выражения. Основную часть окна
мастера выражений занимает элемент управления MEMO, в котором и формируется
выражение (рис. 5.7).
Выражение можно вводить самому, а можно воспользоваться кнопками в нижней части
окна мастера выражений. Посредством кнопки Database field можно отобразить на экране
вспомогательное окно, при помощи которого в выражение вставляют нужное поле набо-
Глава 5. Документирвоание и анализ информации 287
ра данных. В левой части этого окна располагается список доступных наборов данных,
а в правой — список полей выбранного слева набора данных (рис. 5.8). К сожалению,
в этом списке есть только те наборы данных, которые расположены на этой форме отчета.
Если набор данных расположен, например, в отдельном модуле данных, то даже несмотря
на включение соответствующего заголовочного файла в форму отчета, сослаться на него,
как это делается в подобных случаях, нельзя.
Lxpression Wizard

-Select function-
Category

Date & time STR


Math & trig UPPER !
Statistical LOWER
Database PRETTY
Logical TIME
Information DATE
Other COPY

rns <X> or <Y> depending on the boolean expression

Рис 5.9. Вспомогательное окно для вставки в выражение функции


При помощи кнопки Function можно отобразить на экране вспомогательное окно для
вставки в выражение одной из имеющихся в распоряжении мастера функций (рис. 5.9).
Выбрав нужную функцию, можно нажать кнопку Continue. В результате на экране будет
отображено еще одно окно мастера выражений (см. рис. 5.7). С его помощью можно вста-
вить в определение функции выражение для ее аргумента (аргументов).
И, наконец, при помощи кнопки Variable можно отобразить на экране вспомогательное
окно для вставки в выражение одной из предопределенных переменных. Первоначально
можно вставить одну из шести переменных (см. рис. 5.10): PAGENUMBER (номер
страницы), COLUMNNUMBER (номер колонки), REPORTTITLE (заголовок отчета),
APPSTARTTIME (системное время на момент запуска приложения), APPSTARTDATE (си-
стемная дата на момент запуска приложения) и APPNAME (наименование приложения).
Кроме того, можно добавить новые переменные или модифицировать определения для
уже имеющихся. Для этого служит кнопка Modify variables.
В нашем примере экземпляр компонента TQRExpr должен содержать стоимость товара,
вычисляемую как произведение проданного количества на соответствующую цену товара,
хранящегося на складе. Для свойства Expression нужно задать следующее выражение:
SQLDataSetl.QTY * SQLDataSetl.PRICE
На этом настройку полосы детализации можно считать законченной. Осталось
настроить полосу итогов (SummaryBand). Предполагается, что она будет отображать
общий объем продаж, вошедших в отчет. Добавьте на эту полосу по экземпляру
288 Borland C++ Builder 6. Разработка приложений баз данных

COLUMNNUMBER
REPORTTITLE
APPSTARTTIME
APPSTARTDATE
APPNAME

Рис. 5.10. Вспомогательное окно для вставки в выражение


предопределенной переменной

компонентов TQRExpr и TQRLabel, разместив их по горизонтали. Чтобы выделит!


их, в свойство Font объекта TQRExpr добавьте стили fsBold ufsltalic, а в свойстве
Font объекта TQRLabel — стили fsBold HfsUnderline. Для свойства Caption объектг
TQRLabel задайте значение «.Итого:», а для свойства Expression объекта TQRExpr од-
ним из описанных выше способов укажите значение:
SUM(SQLDataSetl.QTY * SQLDataSetl.PRICE)
Отчет готов. Чтобы оценить его в режиме предварительного просмотра, следует вы-
брать пункт Preview объекта TQuickRep. Сконструированный отчет представлен на рис.
5.11 (в режиме конструктора) и 5.12 (в режиме предварительного просмотра).
Замечание
Открыть отчет в режиме предварительного просмотра можно и при помощи мето-
дов PreviewModal и PreviewModeless. В первом случае окно Print Preview будет открыто
в модальном режиме, а во втором одновременно с открытием окна Print Preview бу-
дет продолжено выполнение программы, начиная со следующего за вызовом метода
PreviewModeless оператора. Главное отличие метода Preview от двух других заключается
в том, что при вызове Preview организуется отдельный поток (thread), в то время как при
вызове методов PreviewModal и PreviewModeless этого не происходит. Если драйвер БД
не является потокобезопасным (not thread safe), то пользоваться можно только методом
Preview.

Распечатать готовый отчет можно и напрямую, минуя режим предварительного про-


смотра. Для этого служит метод Print компонента TQuickRep. В результате вызова этого
метода отчет отправляется на печать на принтер, выбранный по умолчанию. Чтобы вы-
брать другой принтер, перед печатью отчета нужно вызвать метод PrinterSetup. На экране
Глава 5. Документирвоание и анализ информации 289

JReport title);
'Page Header

Продажи за июнь 2002 г.

r
"SaleDate "CheckNo Item "Quantity Price Cosi

T
pet "SALEDATE^CHECKN ] JTEM QTY T>RICE "^QLDataSef
r
"Итого: SUM(SQLDataSet1.QTY'
L
' '

Page F,it>!«(

Рис. 5.11. Отчет, использующий все полосы объекта TQuickRep


в режиме конструктора

С?» Продажи за июнь 2002 г.


;Be[ffli н < . > и ! @*[Щ Close)

Про да* и за ию ь2002 г. 24.04.03 1

ChecWo Mm Quantity Price Cos*

45 34.06.02 Chk-177 Плата >идеозах|ат !WinbondS200F 1 68 38


46 34.08.02 Chk-177 AMD Alhlon 950 MHz 1 71 71
47 34.08.02 Chk-177 Epson AcuLaser C2000 2 2156 4312 J
48 34.06.02 Chk-177 Pinnacle Studio DV500 Plus professional 2 759 i5ie
49 34.08.02 Chk-17B 17'(1B') Samsung 755 DFX 1 204 204
50 34.08.02 Chk-178 Inno VISION Tornado ASP 32 Mb 1 46 46
61 34.08.02 Chk-178 Socket A PC Parser 91 1/100 3 64 182
52 34.08.02 Chk-178 DVD-ROM Diiw LG DRD-8160B 3 85 195
63 34.06.02 Chk-178 VIA СЗ ЭООА MHz 4 42 1Я
54 34.06.02 Chk-179 KB6869 Клавиатура рус/лат 4 6 24
55 35.06.02 Chk-180 Плата Fast EthernetC omplex 3 14 42
56 35.06.02 Chk-181 Epson SVIusPhoto810 3 122 368
57 35.08.02 Chk-181 Socket 478 (Pentium 4) I ntel D893MVL 3 168 504
53 35.06.02 Chk-182 Плата Fast Elheinet Surecom EP-320JX, S1 2 8 18
59 35.06.02 Chk-182 HUB HP PmCunre Si»itch 2324(J4318A) 3 813 1839 pi
OU JO.UO.U^ Chk-182 Плата Fast ElhernetSCom ElherLinh 2 42 ' 84 ^^^^^^ '
185%|Pagefof'§

Рис 5.12. Отчет, использующий все полосы объекта TQuickRep


в режиме предварительного просмотра

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

При выходе из режима предварительного просмотра генерируется событие


AfterPreview, имеющее стандартный тип TNotifyEvent. Его можно использовать для ка-
ких-либо специфических действий, например для информационных целей. С методом
Print также связано два события: BeforePrint и AfterPrint. Впрочем, событие BeforePrint
связано также с методами группы Preview. Это событие возбуждается после вызова одно-
го из методов Print, Preview, PreviewModal и PreviewModeless перед генерацией отчета.
Обработчик этого события объявлен следующим образом:
void fastcall TForm2::QuickReplBeforePrint(TCustomQuickRep *Sender,
bool &PrintReport)
Кроме стандартного аргумента Sender, имеющего тип TCustomQuickRep, в тело об-
работчика передается также аргумент PrintReport логического типа. Задав для этого
аргумента значение false, можно предотвратить генерацию отчета. Событием BeforePrint
можно воспользоваться, например, чтобы внести какие-нибудь изменения в значения эле-
ментов управления отчетов, а также для отмены печати отчета в случае, когда отчет пуст.
После того как отчет отправлен на печать, генерируется событие AfterPrint.
Еще одна пара событий связана с началом/окончанием отображения страницы отчета.
Событие OnStartPage происходит перед тем, как страница будет отображена. Обработчик
этого события имеет стандартный тип TNotifyEvent. С его помощью можно отобразить на
уже сгенерированной странице некую дополнительную информацию, например, рамку
страницы с нужным штампом. Событие OnEndPage происходит после того, как страница
полностью готова. Это событие используется для внутренних нужд, так что обрабатывать
его самостоятельно не рекомендуется. И последнее, седьмое событие, — OnNeedData —
происходит тогда, когда необходимо получить данные для отчета. Подробнее об этом со-
бытии можно узнать по адресу www.qusoft.com.

Полосы отчета
В предыдущем разделе уже рассказывалось, как при помощи свойства 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. Разработка приложений баз данных

G rbGroupHeader — полоса, которая будет использоваться в качестве верхнего колон-


титула полосы группировки (экземплярами компонентов TQRGroup и TQRSubDetait).
Печатается перед очередной полосой детализации, когда меняется определяющее
группу значение.
Q rbOverlay — это значение включено для обратной совместимости с Quick Report вер-
сии 1.0. Использовать его не рекомендуется.
О rbChild— значение зарезервировано для использования одним из экземпляров компо-
нента TQRChildBand. Самим задавать это значение не рекомендуется.
Важнейшие из вышеперечисленных типы полос — rbGroupHeader и rbGroupFooter.
С их помощью можно организовать группировку по отдельным значениям. Вместо поло-
сы типа rbGroupHeader удобнее использовать экземпляр компонента TQRGroup.
Рассмотрим на примере процесс создания табличного отчета с группировкой.
В качестве основы воспользуемся примером, который был описан в предыдущем разделе.
Однако на этот раз вместо механизма dbExpress ради простоты изложения (например, во
избежание проблем из-за различия формата данных) будем использовать механизм BDE.
Итак, откройте приложение, форма отчета которого изображена на рис. 5.12, и удалите
из этой формы объекты TSQLConnection и TSQLDataSet. Вместо них добавьте на форму
отчета экземпляр объекта TQuery с вкладки BDE. Свяжите объект Query 1 с базой данных
MyData, которая содержит те же таблицы, что и БД MylBData типа Interbase, использован-
ная в качестве примера ранее в этой и предыдущей главах. В качестве значения свойства
SQL объекта Query 1 задайте следующую строку:
S E L E C T S A L E S . S A L E D A T E , S A L E S . C H E C K N , ITEMS.ITEM,
S A L E P A R T S . Q T Y , ITEMS.PRICE
FROM ITEMS
INNER JOIN SALEPARTS
ON (SALEPARTS.ITEM = ITEMS.ITEMNO)
INNER JOIN S A L E S
ON ( S A L E P A R T S . S A L E = S A L E S . S A L E N O )
WHERE S A L E S . S A L E D A T E BETWEEN ' 0 1 . 0 6 . 0 2 ' AND ' 3 0 . 0 6 . 0 2 '
ORDER BY S A L E S . S A L E D A T E ;

Эта строка SQL-оператора отличается от использовавшейся ранее наличием предло-


жения ORDER BY, которое задает сортировку записей по значению поля SALEDATE (дата
продажи). Откройте набор данных, задав для свойства derive объекта Query 1 значение
true. Затем нужно задать новое значение для свойства DataSet всех объектов TQuickRep
и TQRDBText. В определении вычисляемого элемента управления, расположенного на по-
лосе итогов (Summary), следует убрать все упоминания об объекте SQLDataSetl.
Группировку набора данных можно организовать по датам продажи (по значениям
поля SaleDate). Группе записей должно предшествовать соответствующее значение даты
продажи, а непосредственно после группы записей может следовать итоговая сумма про-
даж за этот день. Ничего не меняя в расположении и настройке элементов управления
и полос отчета, взятого за основу, добавьте к нему полосу TQRGroup (заголовок группы)
и полосу TQRBand. Для свойства BandType объекта TQRBand задайте значение rbGroupH-
eader (первоначально объект TQRBand будет полосой заголовка (rbTitle)).
Обе добавленные полосы будут помещены после всех остальных полос отчета.
Основное свойство полосы Group Header, непосредственно связывающее ее с конкретной
полосой детализации, — свойство Expression. Оно имеет кнопку построителя, щелчок на
Глава 5. Документирвоание и анализ информации 293

которой приводит к отображению на экране диалогового окна 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. Разработка приложений баз данных

г ' . Продажи за июнь 2002 г.

A.

Продажи за июнь 2002 г. 29.04.03

Г*нщажи за июнь 2002 г.

№ SafeOate CftecMVo Hern Quantity Price CO*

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

' Продажи за июнь 2002 г


<

Продаем за июнь 2002 г.


3
Продажи за июнь 2002 г.

№ SafeDsfe СЛесЖо /fern Quantity Prro? Cos*

01.06.02

Chk-166

1 01.06.02 Chk-166 CanonCmoSoanNeseu 87.00* 261


2 01.06.02 СН<-1вЭ Epson SVIus Color C80 185.00* 740
3 01.06.02 CN<-1» So*et423 (Pentium-ф Intel 0890GBC 132.CO* 396
4 01.06.02 СпЧ-1ве Epson SVIus Photo 810. 122.00* 336

1763

1763

02.06.02

Chk-167

6 02.06.02 Chk-187 lntelPenlium41.6 GHz 173.00$ 173

Рис. 5.14. Отчете группировкой по дате продажи и по номеру чека

бавить дочернюю полосу в отчет можно, задав значение true свойству HasChild полосы,
которая должна быть родительской. Чтобы дочерняя полоса всегда печаталась на той же
странице, что и родительская, задайте имя последней в свойстве LinkBand.
Полоса TQRStringsBand предназначена для отображения в отчете списка строк,
который задается при помощи свойства Items, имеющего тип TStrings*. При помощи
этого компонента, например, можно распечатать содержимое текстового файла. Полоса
TQRStringsBand печатается для каждого элемента списка строк. После размещения поло-
сы в отчете в свойство Expression компонента TQuickRep добавляется еще один элемент,
имеющий то же имя, что и полоса. С его помощью можно задать способ доступа к отде-
льным строкам списка.
Все компоненты полос имеют два события: BeforePrint и AfterPrint. Первое генери-
руется непосредственно перед печатью полосы. В тело обработчика этого события по
ссылке передается аргумент PrintBand логического типа. Если в обработчике события
BeforePrint задать для этого аргумента значение false, то полоса печататься не будет.
Событие AfterPrint генерируется сразу после печати полосы, даже если печать была отме-
нена в обработчике события BeforePrint. При помощи аргумента BandPrinted логического
типа можно определить, была ли полоса выведена в отчет. Этим можно воспользоваться
для корректировки размеров и положения элементов управления на странице.
Ниже приведен пример обработчика события BeforePrint, который используется для
фильтрации исходного набора данных:
296 Borland C++ Builder 6. Разработка приложений баз данных

void _ fastcall TForm2 : :QRBandlBeforePrint(TQRCustomBand *Sender,


bool &PrintBand)
{
PrintBand=
((Queryl->FieldByName(«SALEDATE»)->
AsDateTime >= TDateTime(«01 .06.02»))
&&
(Query !->FieldByName(«SALEDATE»)->
AsDateTime = TDateTime(«36.06.02»))) ;

Краткий обзор компонентов Quick Report


В предыдущих разделах этой главы рассказывалось об использовании компонента
TQuickRep, компонентов полос (TQRBand, TQRGoup, TQRSubDetail, TQRStringsBand,
TQRChildBand), а также элементов управления TQRLabel, TQRDBText, TQRExpr,
TQRSysData. На вкладке QReport еще достаточно много компонентов. Подробное их
описание выходит за рамки книги. В этом разделе будут кратко упомянуты еще несколько
компонентов, относящихся к набору Quick Report.
Четыре компонента— TQRMemo, TQRExpr Memo, TQRRichText и TQRDBRichText —
предназначены для представления в отчете многострочного текста. Первые два компонен-
та — это аналоги Quick Report стандартного компонента ТМето. Текст для отображения
задается при помощи свойства Lines. Компонент TQRExprMemo позволяет программно ге-
нерировать содержимое свойства Lines при помощи выражений Quick Report. Выражения
Quick Report включаются в текст между парой фигурных скобок. Чаще всего в качестве
выражений используются наименования полей набора данных, к которому подключен
отчет (объект TQuickRep). При генерации отчета вместо выражений будут подставлены
актуальные значения, например:
Supplier: {Supplier}
City: {City}
Address: {Address}
Contact: {Contact}
Компоненты TQRRichText и TQRDBRichText являются аналогами Quick Report ком-
понентов TRichEdit и TDBRichEdit соответственно. Для хранения текста в компоненте
TQRRichText используется свойство Lines, в то время как компонент TQRDBRichText по-
лучает текст из поля набора данных.
Компоненты TQRShape, TQRImage и TQRDBImage представляют в отчете графические
объекты. При помощи компонента TQRShape можно отобразить в отчете одну из фигур, до-
ступных в списке свойства Shape: прямоугольник (rbsRectangle), круг (rbsdrcle), вертикаль-
ную линию (rbsVertLine), горизонтальную линию (rbsHorLine), пару горизонтальных линий
по границе объекта TQRShape (rbsTopAndBottom), пару вертикальных линий по границе
объекта TQRShape (rbsRightAndLeft). Компоненты TQRImage и TQRDBImage предназначе-
ны для отображения в отчете изображений, представленных в форматах *.jpg, *.jpeg, *.bmp,
*.ico, *.emfu *.wmf. Компонент TQRImage получает изображения из файла (свойство Picture),
а компонент TQRDBImage — из поля набора данных (свойства DataSet и DataFiela).
Компонент TQRCompositeReport позволяет объединять несколько отчетов (объектов
TQuickRep) в один составной отчет. Описание этого компонента в справочной системе
Глава 5. Документирвоание и анализ информации 297

отсутствует, поэтому пользоваться им довольно затруднительно. Основным свойством со-


ставного отчета является свойство-контейнер Reports, содержащее список всех отдельных
отчетов (TQuiickRep). Каждый из них должен быть заранее создан и настроен. Добавить
простой отчет в составной можно при помощи метода Add. Для печати составного отчета
предназначен метод Print, а для перехода в режим предварительного просмотра — метод
Preview. Ниже приведен отрывок кода, в котором составной отчет QRCompositeReportl,
включающий отчеты QuickRepl (форма Form!) и QuickRepl (форма Form2), переводится
в режим предварительного просмотра.
QRComposi teReportl->Reports->Add(Forml->QuickRepl);
QRCompositeReportl->Reports->Add(Form2->QuickRep2);
QRCompositeReportl->Preview();
Еще один компонент, информации о котором нет в справочной системе (во всяком слу-
чае, в той версии Borland C++ Builder, которая была в моем распоряжении), — TQRPreview.
Судя по всему, он предназначен для организации настраиваемого окна предварительного
просмотра. За более подробными сведениями об этом следует обратиться на сайт фирмы
QuSoft по адресу www.qusoft.com. То же можно сказать и о компоненте TQRChart, ин-
формации о котором тоже нет. Хотя работа с компонентом TQRChart не очень отличается
от работы с другими подобными компонентами (TDBChart с вкладки DataControls или
TChart с вкладки Additional палитры компонентов).
И последняя группа компонентов с вкладки QReport— QRTextFilter, QRCSVFilter и
QRHTMLFilter. Каждый из них позволяет экспортировать содержимое отчета в файл в текст-
овом формате (ASCII), в файл формата * csv (текстовый файл с разделителями) и в HTML-
файл. При размещении экземпляра каждого из этих компонентов в форме отчета, при сохра-
нении отчета в рамках окна предварительного просмотра (Print Preview) в списке Тип файла
диалогового окна сохранения файла появятся дополнительные элементы: Text file (*. TXT) (для
компонента QRTextFilter), Comma Separated (*.CSV) (для компонента QRCSVFilter) к HTML
Document (*.HTM). Все упомянутые компоненты практически не имеют свойств, поэтому ис-
пользовать их очень просто. В режиме выполнения программы для создания фильтра следует
воспользоваться методом Create, а экспортировать содержимое отчета в файл выбранного
формата можно при помощи метода ExportToFilter.

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. Разработка приложений баз данных

Компонент TDecisionQueiy снабжен собственным редактором, который можно ото-


бразить на экране, дважды щелкнув на значке компонента в форме (или выбрав из его
контекстного меню пункт Decision Query Editor). Окно редактора содержит две вклад-
ки — Dimensions/Summaries и SQL Query (рис. 5.15).

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

Г" Count (*} tor Averages

Uuety Fie Iris

SQL Builder. Cancel

Рис. 5.15. Окно редактора Decision Query Editor, открытое на вкладке


Dimensions/Summaries, поможет сконструировать строку нужного запроса SQL
При помощи элементов управления вкладки Dimensions/Summaries можно скон-
струировать строку подходящего запроса SQL. В отличие от компонента TQuery,
запрос SQL, лежащий в основе компонента TDecisionQuery, обязательно должен со-
держать операцию группировки (GROUP BY) и итоговые операции над значениями
полей набора данных (SUM, AVG, COUNT). Если запрос основан всего на одной та-
блице, то указать ее имя можно при помощи элементов управления Database и Table.
В результате список доступных полей будет отображен в элементе управления List of
Available Fields. Часть этих полей должна быть выбрана в качестве измерений Decision
Cube, а другие — в качестве итоговых значений. Измерения (dimensions) служат
в качестве заголовков строк или столбцов сводной таблицы, в то время как итоговые
значения отображаются в ее ячейках. Поля (или выражения, основанные на значениях
полей), которые выбраны в качестве измерений, перечисляются в операторе SQL не
только в предложении SELECT, но и в конструкции GROUP BY. Поля, выбранные для
использования в итоговых значениях, перечисляются в предложении SELECT с указа-
нием агрегатных функций (SUM, COUNT v. др.).
Более сложный оператор запроса можно создать при помощи построителя SQL, ото-
бражающегося на экране при нажатии кнопки SQL Builder. Этот тот же построитель, кото-
рый используется компонентом TQuery.
Глава 5. Документирвоание и анализ информации 299
Аналог компонента TDataSource в наборе Decision Cube — TDecisionSource. Однако
этот компонент подключается к набору данных TDecisionQuery не напрямую, а через
TDecisionCube. Компонент TDecisionCube преобразует строку оператора SQL компонента
TDecisionQuery, формируя для TDecisionSource набор данных в виде перекрестной табли-
цы. Компонент TDecisionCube снабжен редактором Decision Cube Editor, который можно
отобразить на экране или щелкнув на кнопке построителя свойства DimensionMap, или
дважды щелкнув на значке компонента на форме, или выбрав из его контекстного меню
пункт Decision Cube Editor. При помощи этого редактора можно настроить различные
свойства компонента (максимальное количество измерений, итогов и т.д.), а также свой-
ства доступных полей (тип активности, формат, уровни группировки и т.д.).

ЕЭ
£ЮН 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 КАП ПП«
'"""^:

Рис. 5.16. Помесячные уровни продажно каждому наименованию товара


Информационная цепочка при использовании компонентов Decision Cube выглядит так:
T D e c i s i o n Q u e r y -> TDecisionCube -> TDecisionSource
К компоненту TDecisionSource можно подключить один из двух доступных элементов
визуализации и управления данными— TDecisionGrid или TDecisionGraph. Первый из
них отображает данные в виде перекрестной таблицы и напоминает компонент TDBGrid,
а второй — виде диаграммы (работает почти так же, как и TDBChart).
Рассмотрим простейший вариант использования вышеупомянутых компонентов.
На пустую форму нового приложения добавьте по одному экземпляру компонентов
TDecisionQuery, TDecisionCube, TDecisionSource и TDecisionGrid, оставив их имена
без изменений. Откройте редактор строк, прикрепленный к свойству SQL компонента
TDecisionQuery, и введите следующий текст:
SELECT Sales.SaleDate, Items.Item, SUM( Items.Price * Saleparts.Qty )
FROM Sales
INNER JOIN Saleparts
ON (Saleparts.Sale = Sales.SaleNo)
300 Borland C++ Builder 6. Разработка приложений баз данных

INNER JOIN Items


ON (Items.ItemNo = Saleparts.Item)
GROUP BY Sales.SaleDate, Items.Item
В качестве базы данных (свойство DatabaseName) задайте MyData и укажите для свой-
ства Active объекта DecisionQuery1 значение true. Запрос, основанный на приведенной
выше строке оператора SQL, возвращает информацию о датах продаж и наименованиях
проданных товаров. По этим полям производится группировка, т.е. они могут использо-
ваться в качестве измерений. В качестве итоговых значений будет фигурировать объем
продаж по каждой группе.

-ах
.~i—,j-..-ii

TOecisionGraph
• 255.718 май, 2002
• 275.557 июн. 2002
П 334,839 июл, 2002
• 128,592 авг. 2002

май, 2002 июн, 2002 июя, 2002 авг, 2002


SaleDate

Рис. 5.17. Информация об объеме продаж за месяц,


представленная в графическом виде при помощи компонента TDecisionGraph
Перейдем к объекту DecisionCubel. Свяжите его с объектом DecisionQuery 1 (свойство
DataSei) и откройте редактор компонента Decision Cube Editor на вкладке Dimension
Settings. В списке Available Fields выберите поле SaleDate и задайте для него уровень
группировки в один месяц (выберите значение Month из комбинированного списка
Grouping). Таким образом, информация будет группироваться не по каждой дате продажи,
а по значению месяца.
Затем подключите объект DecisionGridl к объекту DecisionSourcel, aero, в свою
очередь, — к DecisionCubel. Результат этих героических усилий изображен на рис. 5.16.
Обратите внимание на то, что поле Items автоматически было использовано в качестве
заголовка строк, а поле SaleDate — в качестве заголовка столбцов. Однако этот порядок
вещей легко изменить, перетаскивая наименования полей при помощи мышки. Советую
поэкспериментировать с этим, наблюдая, как будет изменяться отображение информации.
В нижней части каждого столбца отображается итоговая информация по этому столбцу,
Глава 5. Документирвоание и анализ информации 301

а в правой части каждой строки — итоговая информация по строке. В последней ячейке


нижней строки отображается итог по всей таблице.
Слева от каждого заголовка столбца или строки расположена желтая кнопка со знаком
«+» или «-». Щелкая на этой кнопке, можно разворачивать (отображать) или сворачивать
(скрывать) информацию, связанную с данным заголовком. В этом случае будут доступны
только итоговые значения. Еще один интересный вариант предоставляет команда Drill in
to this value, доступная из контекстного меню любого из значений заголовков. В результате
выполнения этой команды в таблице останется информация только о выбранном элемен-
те. Вернуться к предыдущей схеме отображения можно, снова задав при помощи кон-
текстного меню отображение для соответствующего заголовка.
Благодаря набору возможностей по манипуляции и настройке схемы отображения для
сетки TDecisionGrid, механизм Decision Cube часто называют системой многомерного
анализа данных.
Столь же просто работать и с компонентом TDecisionGraph. В качестве примера вос-
пользуемся тестовым приложением, описанным чуть ранее. Удалите из формы экземпляр
компонента TDecisionGrid, а вместо него добавьте на форму экземпляр компонента
TDecisionGraph, подключив его к объекту DecisionSourcel. Из строки оператора SQL объ-
екта DecisionQueryl удалите все упоминания о поле Item таблицы Items (в этом примере
поле Item нам не понадобится). Активизируйте набор данных, задав для свойства Active
объекта DecisionGraphl значение true. Результат представлен на рис. 5.17. Пользуясь
огромным арсеналом свойств, присущим всем компонентам-диаграммам, можно настро-
ить полученную диаграмму на свой вкус.
И в заключение несколько слов о компоненте TDecisionPivot. Он предназначен для
более полного и удобного управления структурой представления информации. Чтобы
показать его возможности, снова воспользуемся приведенным выше примером при-
ложения, где применялся компонент TDecisionGrid. Добавьте на форму экземпляр ком-
понента TDecisionPivot, связав его с объектом DecisionSourcel. В свойство SQL объекта
DecisionQueryl введите следующий текст:
SELECT Sales.SaleDate, Items.Item, SUM( Items.Price * Saleparts.Qty ),
COUNT( Items.Price * Saleparts.Qty )
FROM Sales
INNER JOIN Saleparts
ON (Saleparts.Sale = Sales.SaleNo)
INNER JOIN Items
ON (Items.ItemNo = Saleparts.Item)
GROUP BY Sales.SaleDate, Items.Item
Этот оператор SQL отличается от использовавшегося ранее тем, что наряду с объе-
мом продаж по каждой группе записей возвращается и количество записей, входящих
в группу. Это позволит объекту DecisionCubel вычислять значения средних продаж по
группам. Откройте набор данных DecisionQueryl, задав для его свойства Active значение
true. Результат изменений изображен на рис. 5.18.
302 Borland C++ Builder 6. Разработка приложений баз данных

Ш 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

Рис. 5.18. Компонент TDecisionPivot предоставляет удобный путь


для манипуляций схемой отображения информации
Объект TDecisionPivot имеет три группы кнопок. В левой его части расположена
кнопка со стрелкой. Надпись на ней сообщает о том, какое именно итоговое значение ис-
пользуется для отображения в ячейках сетки. При щелчке на этой кнопке отображается
список всех доступных в данный момент итоговых значений. В приведенном выше при-
мере доступны будут следующие варианты: объем продаж по группе, количество продаж
и величина среднего уровня продаж в каждой группе. Так можно быстро переключаться
от одного типа информации к другой.
Следующая группа состоит из кнопок, представляющих отдельные заголовки
строк. Предваряется группа изображением листа бумаги с горизонтальными линиями.
Следующая группа кнопок, начинающаяся с изображения листа с вертикальными полоса-
ми, представляет заголовки столбцов. Щелкая на одной из этих кнопок, можно скрывать/
отображать соответствующую информацию из таблицы, а перетаскивая кнопки из одной
группы в другую, можно изменять структуру представления информации.
Приведенной в этом разделе информации о механизме Decision Cube, на мой взгляд,
вполне достаточно для разработки довольно полезных и удобных аналитических форм.
Используя эти формы, можно рассматривать одни и те же данные под разными углами
зрения, что поможет быстро принять правильное решение в любой ситуации. Впрочем,
в этом и состоит назначение механизма Decision Cube.

Резюме
В этой главе рассматривались вопросы построения отчетных и аналитических форм
при помощи наборов компонентов 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

70x100/16, мягкий переплет, 896 стр.

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

70x100/16, тв. переплет, 1104 стр.

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

70x100/16, мягкий переплет,


656 стр.

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

Когда человек берет в руки книгу компьютерной темати-


Эта книга посвящена глубокому
ки, он наверняка задастся такими вопросами: насколь
исследованию основополагающих
актуальна информация, изложенная в книге? Когда была
концепций и алгоритмов, которые
издана книга? Знакома ли фамилия автора? На какой
можно отнести к разряду "вечных"
период книга сохранит свою актуальность? Вопросы да-
Изучив их, вы получите знания,
леко не праздные: приобретение книги связано с тратой
которые никогда не устареют и
определенной суммы денег, поэтому человек вправе
которыми вы будете пользоваться
ожидать как можно большего эффекта и желательно как
всегда
можно дольше.
Не секрет, что в последние годы программирование, пе-
рейдя "на поток", перестало быть искусством, а стало ходит в голову, — искать простым перебором и сравне-
ремеслом. Проникновение компьютеров практически во нием информационных элементов)? Как вообще обсто-
все сферы повседневной жизни привело к существен- ят дела с алгоритмами в'настоящий момент?
ному увеличению спектра задач, решаемых программи- А ситуация такова: структурное (алгоритмическое) про-
стами. Вполне естественно появление и постоянное со- граммирование не забыто, да и никогда не будет забы-
вершенствование инструментов разработки, позволяю- то просто потому, что сами по себе алгоритмы гораздо
щих даже начинающим без особых затрат времени и старее программирования. Ими пользовались еще до
усилий решать свои маленькие (и не только) задачи. появления не только компьютеров, но даже и мыслей
Представьте себе создание приложения для пополне- подобного рода. Это классические понятия, которые
ния, сортировки и поиска в большой информационной просто скрылись под разного вида оболочками и над-
системе по каким-то товарам. Вы создаете на экране стройками.
окошко (естественно, красивое), перетаскиваете туда
И эта книга как раз и посвящена глубокому исследова-
элементы "Таблица", "Сортировка", "Поиск" и так далее.
Затем среди свойств элемента "Сортировка" выбираете нию всех основополагающих концепций и алгоритмов,
которые можно отнести к разряду "вечных". Изучив их,
"Быстрая" (разумеется, ведь информации у вас неме-
рянно!)... Звучит заманчиво, не правда ли? А что же та- вы получите знания, которые никогда не устареют и ко-
кое сортировка, к тому же быстрая? А как, собственно, торыми вы будете пользоваться всегда. Ибо они—фун-
даментальная основа успешного Программирования
организуется поиск (кроме способа, который первым при-
(именно так, с большой буквы).
Роберт Седжвин
Фундаментальные Фундаментальные алгоритмы на C++.
алгоритмы Алгоритмы на графах
на звп ISBN 5-93772-054-7

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. Ф.И.О.

4. Кем и где работаете


5. Параметры Вашего ПК

6. Адрес, телефон, e-mail, web-узел

Дата заполнения "

Вышлите анкету по адресу: Украина, 03055, Киев-55, а/я 100, «Издательство «ДиаСофт».
ТОРГОВЫЕ МЕСТА ТОРГОВО-ИЗДАТЕЛЬСКОГО ДОМА «ДС»

ХАРЬКОВ ДНЕПРОПЕТРОВСК
КИЕВ
Г| Ш
ул. Пушкинская \ci/ У

ул. Пастера _| 1 1 \
ул. Привокзальная
метро "Петровка" AL^V
я1 ЗИН

с~

Островского, 1
пл.
пр-т Карла Маркса

САНКТ-ПЕТЕРБУРГ
пр-т Обуховской обороны, 105, ДК им. Крупской, м."Елизаровская"
•я, 190103. пр-т Лермонтовский, 44, а/я 66,000 "ДиаСофтЮП"
(

'
9>
:S ' m
•S:l
W
е
о.
о о

ПАРТНЕРЫ ТИ Д «ДС» АДРЕСА МАГАЗИНОВ,


ГДЕ МОЖНО КУПИТЬ КНИГИ ТИД «ДС»
Киев КИЕВ ХАРЬКОВ
т./ф. 212-12-54, 216-35-64 "Знания', ул. Крещатик, 46 "Вища школа",ул.Петровского.б
т. (044)224-22-91 т. (0572)47-80-20
Книга-почтой: 03055, а\я 100 "Техническая книга",
"Books", ул. Сумская, 51,
ул.Красноармейская,51 т. (044)227-25-1
e-mail: books@diasoft.kiev.ua т. (0572)14-04-71
"Сучасник", пр-т Победы, 29
e-mail: bss@diasoft.kiev.ua т. (044)274-52-35
"Библиотечный коллектор", МОСКВА
e-mail: stepanb@akcecc.kiev.ua пр-т 40-летия Октября, 100/2 "БиблиоТлобус", ул.Мясницкая.6
т. (044)263-20-54, 263-20-04 т. (095)928-87-44
Днепропетровск тел./факс (044)263-60-56 "Дом технической книги",
т.(0562)33-27-74, 34-02-09 Ленинский пр-т, 40, т. (095)137-60-38
ДНЕПРОПЕТРОВСК
Книга-почтой: 49008, а\я 466 "Московский дом книги",
"Техжчна книга",
e-mail: diasoft@mail.dnepr.net пл.Островского, 1
ул.Новый Арбат, 8
"Мир", Ленинградский пр-т, 78
т. (0562)33-09-55
Харьков. "Техническая книга" т. (095)152-45-11
т. (0572)47-20-67 пр.К.Маркса 40 "Мир печати* ул.2-я Тверская Ямская,54
т. (0562)744-86-72 т. (095)978-50-47, 978-55-07
e-mail: books@rail.kharkov.com "Молодая гвардия", ул.Большая Полянка,28
"Книги", пр-т Гагарина, 98,
т. (0562)43-63-81 т. (095)238-11-44, 238-00-32
Львов
т./ф. (0322)39-87-08 ДОНЕЦК САНКТ-ПЕТЕРБУРГ
ЧП "ИнфоКом'ул.Артема 83 "Дом книги", Невский пр-т, 28
e-mail: vlas@txnet.com
т. (0622)382-64-69 т.(812)318-64-16
Москва "Техническая книга",
КРИВОЙ РОГ ул. Пушкинская, 2,
т. (095)726-80-67 "Букинист-Солон" т.(812)164-65-65
e-mail: diasoft_msk@rosmail.ru пл.Освобождения 1
"Энергия", Московский пр-т, 189
т. (0564)92-37-32
т.(812)443-01-47
Санкт-Петербург
т.(812)251-41-94 ЛЬВОВ
"Техническая книга", пл.Рынок,10
МИНСК
e-mail: diasoft_spt@mail.convey.ru т. (0322)72-54-06 "Книга XXI"
пр-т Ф.Скорины, 92,
Индекс 190103, а\я №66 "Влас", Университет Львовский
политехник, корпуса 4,5, ст.м. Московская,
000 "ДиаСофт ЮП" т. (0172)64-31-05, 64-27-97
т. (0322)39-8708
Как сделать заказ и получить книги
Для организаций Для частных лиц
1) Получите полный прайс-лист по адресу, 0 Получите полный прайс-лист по адресу,
указанному ниже указанному ниже (или см. прайс, приведен-
ный в книге)
2) Аккуратно заполните бланк заказа. 2) Аккуратно заполните бланк заказа

3) Отправьте его нам по факсу, почте или 3) Отправьте его нам по факсу, почте или
e-mail. e-mail.
4 в
4) В течение одного рабочего дня в Ваш ад- ) течение одного рабочего дня в Ваш адрес
рее будет направлен счет по факсу или е- будет направлен счет по факсу или e-mail. В
mail. В течение срока действия счета мы течение срока действия счета мы гаранти-
ем
гарантируем наличие книг и неизменность РУ наличие книг и неизменность цены.
ц£1Ш Внимание! Не оплачивайте покупку до по-
лучения счета.
5) После оплаты счета Ваш заказ, а также 5) оплатите счет в любом коммерческом бан-
оригинал счета, расходная и налоговаяна- ке деньгиможно отправить на наш расчет-
кладные будут высланы Вам посылкой. ныйсчетипочтовьм переводом, но это не-

сколько дороже, чем через банк.


Заказы, поступившие по электронной почте, обрабатываются в первую очередь.
Заказы «наложенным платежом»—не принимаются.
Цены на книги не учитывают стоимость доставки, коммисионные банка или почтового перевода.
Стоимость доставки по Украине или курьером по Киеву — 7 грн.
Стоимость доставки по России — в зависимости от региона.

Заказ
от« » 2003 г

Название организации:
Для организации

Фамилия Имя Отчество:


Для частных лиц

Адресдоставки:
Почтовый индекс

ИНН: № св-ва плательщика НДС:


Топьш^пя организаций

Фамилия Имя Отчество:


Контактного лица

Телефон:/ / Факс:
E-mail: _

Код Название книги К-во

Получайте полный прайс-лист на все книги и направляйте заказы по адресу:


г.Киев:03055а/я 100, тел./факс(044)212-1254,216-3564е-пш1: books@diasoft.kiev.ua
stepanb@akcecc.kiev.ua
Индекс 190103, А/Я №66 ООО "ДиаСофт ЮП"
www.diasoft.kiev.ua — всегда полный ассортимент наших книг
Прайс-лист ТиД "ДиаСосЬт"
Код Автор Название Стр. Формат
Использование ПК в цепом.
4043 Нортон П., Гу/: Работа на персональном компьютере. Самоучитель 584 84x108/16
7277 Клименко Эффективный самоучитель работы на ПК. Основной курс 2-й; 496 70x100/16
6173 Клименко Эффективный самоучитель работы на ПК. 736 /0x100/16
7261 Михлин Е.М. Эффективный самоучитель работы на ПК. 2-издание 624 70x100/16
7276 Вебер Сборка, конфигурирование, настройка, модернизация и разго> 704 /0x100/16
6169 Домарев Безопасность информационных технологий 688 84x108/16
6583 зрукс Чарльз , Аттестация А+. Техник по обслуживанию ПК. Организация, об 816 84x108/16
4020 Нортон П., Ту; Внутренний мир персональных компьютеров, 8е издание. Изб 548 84x108/16
4683 Канер Сэм и д "естирование программного обеспечения 544 60x84/16
6538 Макгрегор Тестирование объектно-орентированого программного обеспе 432 70x100/16
Операционные системы.
5511 Кессел Пол Microsoft Windows 2000 Professional. Энциклопедия пользовал 832 70x100/16
5954 Браун Т. Microsoft Windows 2000 Server. Энциклопедия пользователя 672 70x100/16
6521 Чуприн А. И. Эффективный самоучитель работы в Windows XP Professiona 336 70x100/16
6884 Оуглтри Teppf Microsoft Windows XP 848 70x100/16
6455 Анонимный ЭЕ Максимальная защита в Linux. Второе издание 752 70x100/16
6523 Скловская (оманды Linux. Справочник. 2-е изд. переработанное и flononh 720 70x100/16
5717 Шенк Т. Red Hat Linux для системных администраторов. Энциклопеди, 672 84x108/16
6174 золл. Питтс Red Hat Linux 7 в офисе и дома 448 70x100/16
5758 Сэтчэлл Стеф Jnux IP Stacks в комментариях ( + CD-ROM ) 288 70x100/16
3926 Паркер Тим .inux 5.2. Энциклопедия пользователя + 2 CD-ROM 688 84x108/16
6718 золл Билл, fit Red Hat Linux 7.x Энциклопедия пользователя 880 70x100/16
1557 эурк Р. , Хорва Unix для системных администраторов. Энциклопедия пользо 864 60x84/8
4318 Бурк Робин, Хо UNIX для Internet Энциклопедия пользователя ^+ CD-ROM 496 84x108/16
44 Кепли М. Ответы на актуальные вопросы по OS/2 Warp 352 60x84/16
r
6496 Эбен Майкл reeBSD. Энциклопедия пользователя 736 70x100/16
6982 зилл Кэлкинс Solaris 8. Сертификация системного администратора 928 70x100/16
Программирование.
5003 Вильяме М. Программирование в Windows 2000. Энциклопедия пользоват 640 84x108/16
6522 Саймон Windows 2000 API. Энциклопедия программиста 2-е издание 1088 70x100/16
5953 Гриффите А. 1рогаммированиеСМОМЕ/СТК+. Энциклопедия программист 720 70x100/16
6704 У!айо Джозев С#. Искусство программированияю Энциклопедия программис 656 70x100/16
6894 Микелсен Кла Язык программирования С#. Лекции и упражнения 912 84x108/16
6905 "илберт Стиве Самоучитель Visual С++6 в примерах 496 70x100/16
7167 Олафсен Visual С++6 и MFC Энциклопедия пользователя 992 70x100/16
7168 Прата С. Язык программирования C++. Лекции и упражнения 656 84x108/16
6913 Прата С. Язык программирования С. Лекции и упражнения 896 84x108/16
7464 Седжвик Робе Фундаментальные алгоритмы на С. Части 1-4. Анализ, структур 672 70x100/16
7463 Седжвик Робе Фундаментальные алгоритмы на С.Части 1-5. Анализ, структур 1136 70x100/16
7465 Седжвик Робе Фундаментальные алгоритмы на С. Часть 5. Алгоритмы на гра 480 70x100/16
6885 Седжвик Робе Фундаментальные алгоритмы на C++. Анализ/ Структуры дан 688 70x100/16
6914 Седжвик Робе Фундаментальные алгоритмы на С++.Алгоритмы на графах. 496 70x100/16
5804 Хэзфилд Р. Искусство программирования на С. Фундаментальные алгори 736 84x108/16
4754 Либерти ,Дже C++ Энциклопедия пользователя с приложением 584 84x108/16
3621 Калверт Ч. Delphi 4. Самоучитель (без приложения) 192 84x108/16
2757 Калверт Ч. Delphi 4. Энциклопедия пользователя ( + CD-ROM ) 400 84x108/16
93 Конопка Р. Создание оригинальных компонент в среде Delphi 512 60x84/16
5845 Кандзюба С.П Delphi 6/7. Базы данных и приложения. Лекции и упражнения 576 70x100/16
7078 Глинский Я.Н Бейсик. Qbasic и Visual BaSIC 192 60x84/16
6964 Зеленяк О. П. Практикум программирования на Turbo Pascal. Задачи, алгор 320 60x84/16
6103 Глинский Я.Н Turbo Pascal 7.0 и Delphi. Учебное пособие 208 60x84/16
7063 Голубь Н.Г. Искусство программирования на Ассемблере. Лекции и упраж 656 70x100/16
6148 Лейнекер СОМ+ Энциклопедия программиста + CD-ROM 656 70x100/16
5799 Клименко Kylix 1 .0. Базы данных и приложения. Лекции и упражнения 288 70x100/16
Офисные пакеты.
7251 Линд Дебби Lotus Notes и Domino 5\6 Энциклопедия программиста. 1024 70x100/16
4303 Нортон Питер Microsoft Office 2000.Избранное от Питера Нортона Э52 84x108/16
6622 Кишик А. Office XP. Эффективный самоучитель. Быстро. ..просто. ..нагл 432 70x100/16
Текстовые редакторы.
6454 Кишик А.Н. Word 2002. Эффективный самоучитель. Быстро... просто... наг; 256 "0x100/16
Электронные таблицы.
6104 <ишик А.Н. Excel 2002. Эффективный самоучитель. Быстро. ..просто. ..нэп 240 70x100/16
Настольные базы данных.
6623 Тослед Б. С. Access 2000. Приложения баз данных. Лекции и упражнения 656 70x100/16
5096 Форт С .Хоуи " Программирование в среде Access 2000. Энциклопедия поль; 544 84x108/16
Базы данных клиент-сервер.
4925 Бэлсон Дон и , Внутренний мир OracleS Проектирование и настройка. ( + CD- 800 84x108/16
5057 "рин Джо Oracle 8/8i Server. Энциклопедия пользователя 576 84x108/16
6168 AIS Oracle 8. Энциклопедия пользователя. Изд. 2-е, перераб., доп 864 60x84/8
6728 5зн Хотка Oracle 9i 560 70x100/16
7217 Кайт Том Oracle для проффессионалов. Книга 1 Архитектура и основны 672 70x100/16
6965 Сичкареико В. SQL.-99. Руководство разработчика баз данных. 816 70x100/16
5640 эьелетич Шар MS SQL Server 2000. Энциклопедия пользователя 688 70x100/16
6729 "ешвинде Эва PostgreSQL. Руководство разработчика и администратора 608 70x100/16
2010 Мак-Налли Д. nformix. Энциклопедия пользователя 800 60x84/8
Графика и анимация.
2472 Боутон Г. и др Внутренний мир Adobe Photoshop 5 ( + CD-ROM ) 496 84x108/16
6694 Сишик Adobe Photoshop 7.0. Эффективный самоучитель .368 70x100/16
7262 Блатнер Дэви; Adobe Photoshop 7.0. Искусство допечатной подготовки. 762 70x100/16
7218 Зоробей Серг« Adob Illustrator 10 Эффективный самоучитель 400 70x100/16
7219 Мак-Клеланд Adob Illustrator 10 Полное руководство 848 70x100/16
7148 <овтанюк Ю.С Самоучитель CorelDRAW 1 1 528 70x100/16
7147 {овтанюк Ю.С CorelDRAW 1 1 для дизайнера 1024 70x100/16
6171 Хаббелл, Бор; 3D Studio VIZ 3 + CD-ROM 624 70x100/16
570 Эллиотт С. Внутренний мир 3D Studio МАХ. Том 1 ( + CD-ROM ) 752 84x1 08/8
649 Эспиноза Д. Внутренний мир 3D Studio МАХ Том 2-й ( + CD-ROM ) 432 84x108/16
2860 БордмэнТ.. X Внутренний мир 3D Studio MAX 2. Том 2: Моделирование и м 368 84x108/16
3925 Маэстри Джор Внутренний мир 3D Studio МАХ 2. Том 3: Анимация ( + CD-RC 408 84x108/16
5080 Миллер Ф. Внутренний мир 3D Studio MAX 3: моделирование, материаль 720 84x108/16
4634 Бордмэн Внутренний мир 3D Studio MAX 3 : Моделирование, материал 456 84x108/16
6925 Пи Ким 3D Studio MAX 4/5 для дизайнера. Искуство трехмерной аним 832 70x100/16
7278 Тёмин 3D Studio MAX 5. Эффективный самоучитель 2-издание 464 70x100/16
5029 Мортиер Р. Внутренний мир Вгусе 4 для дизайнера ( + CD-ROM > 336 70x100/16
6170 Китченс. Гаве BRYCE для дизайнера + CD-ROM 656 84x108/16
5069 Мильберн Кер Внутренний мир Flash 4 для дизайнера. ( + CD-ROM ) 448 70x100/16
6094 Кишик А.Н. Flash 5.0. Анимация. Эффективный самоучитель. Быстро. ..пр 240 70x100/16
6195 Дэн Аблан Lightwave 6/7 для дизайнера: Искусство трехмерного дизайн 864 70x100/16
6915 Шрайнер Дейь OpenGL Официальный справочник 512 70x100/16
6803 By Мэйсон OpenGL. Официальное руководство программиста 592 70x100/16
Издательские системы.
5163 Хансен Хан Разработка сценариев для PageMaker ( + CD-ROM ) 704 60x84/16
6566 Компания Ado Page Maker 7.0 . Учебник от Adobe 384 70x100/16
7061 Блатнер Дэви QuarkXPress 5. Искусство допечатной подготовки. 912 70x100/16
6844 Анита Деннис PDF и Adobe Acrobat 384 70x100/16
CAD-пакеты.
4753 Барчард Билг Внутренний мир AutoCAD 2000. ( + CD-ROM ) 688 84x108/16
6926 Чуприн А И . AutoCAD 2000/2002. Лекции и упражнения. 784 70x100/16
6780 Чуприн А. И. AutoCAD 2002. Трехмерное проектирование. Лекции и упра.жн 528 70x100/16
6559 Харрингтон AvtoCAD 2002 для конструкторов. Искусство проектирования 944 70x100/16
6235 Титаренко Mechanical Desktop 4,5,6. Искусство трехмернного проектиро 304 70x100/16
Сети, коммуникации и сетевые продукты.
3947 Спортак М. и Компьютерные сети. Книга 1: High-Performance Networking. Э 432 60x84/8
3927 Спортак М. и Компьютерные сети. Книга 2: Networking Essentials Энцикло 432 84x108/16
6384 Спортак Марк Компьютерные сети и сетевые технологии 736 70x100/16
6843 Гринфилд Де Оптические сети 256 70x100/16
6785 Остерлох TCP/IP. Семейство протоколов передачи данных в сетях комп 576 70x100/16
6893 Микелсен Хиэ Маршрутизация в IP-сетях. Принципы, протоколы, настройка 512 70x100/16
1295 Грин Д и др. Microsoft BackOffice 2. Энциклопедия пользователя ( + CD-R 800 60x84/8

Вам также может понравиться