Лабораторная работа №7 Создание пользовательских запросов. Цель работы: 1. Изучить методы связи приложений с базами данных. 2. Изучить операции языка манипулирования данными. 3. Изучить набор данных типа «запрос», его свойства и методы работы. Организация связи с базами данных в C++Builder Основой работы с базами данных в C++Builder является Borland Database Engine (BDE). BDE служит посредником между приложением и базами данных. Достоинством использования BDE является единый интерфейс для работы с различными базами данных. Благодаря этому достигается независимость пользовательских программ от реализации базы данных. Для того, чтобы связаться с базой данных приложение C++Builder обращается к BDE и сообщает обычно псевдоним базы данных и необходимую таблицу в ней. Каждому псевдониму в BDE поставлен в соответствие драйвер базы данных. Если в BDE имеется собственный драйвер соответствующей СУБД, то BDE связывается с базой данных через него. BDE поддерживает естественный доступ к таким базам данных как Microsoft Access, FoxPro, Paradox, dBase. Если собственного драйвера нужной СУБД в BDE нет, то используется драйвер ODBC. ODBC (Open Database Connectivity) – это продукт компании Microsoft по функциям аналогичный BDE. Поскольку для ODBC созданы драйверы практически к любым СУБД, в BDE включен драйвер, позволяющий использовать ODBC. Необходимо отметить, что C++Builder через ODBC работает медленнее, чем через собственные драйверы, включенные в BDE. Но благодаря связи с ODBC масштабируемость C++Builder существенно увеличилась и сейчас из C++Builder можно работать практически с любой СУБД. BDE поддерживает SQL — стандартизованный язык запросов, позволяющий обмениваться данными с SQL-серверами, такими, как Sybase, Microsoft SQL, Oracle, Interbase. Эта возможность используется особенно широко при работе на платформе клиент/сервер и в распределенных базах данных. Начиная с C++Builder 5, существует несколько альтернативных возможностей для работы с базами данных, минуя BDE. Во-первых, это разработанная Microsoft технология ActiveX Data Objects (ADO). ADO - это пользовательский интерфейс к любым типам данных, включая реляционные и не реляционные базы данных, электронную почту, системные, текстовые и графические файлы. Связь с данными осуществляется посредством технологии OLE DB. Другая технология InterBase Express (IBX) обеспечивает альтернативный доступ к базам данных Interbase. В библиотеке компонентов C++Builder имеется вкладка InterBase, содержащая компоненты для работы с InterBase, минуя BDE. Эти компоненты обеспечивают повышенную производительность и позволяют полностью реализовать возможности сервера InterBase, недоступные обычным компонентам BDE. Создание и просмотр псевдонимов драйверов и баз данных в BDE Administrator Программа BDE Administrator (Администратор BDE) позволяет просматривать, редактировать и создавать новые драйверы BDE баз данных различных типов: стандартные (STANDARD), SQL, Access, ODBC. При установке BDE на компьютер программа BDE Administrator включается в состав панели управления Windows. Окно программы имеет две страницы: Databases — базы данных и Configuration — конфигурация. На странице в левой панели расположено дерево псевдонимов баз данных. Выделив интересующий вас псевдоним в левой панели, в правой панели Definition можно увидеть все его характеристики. Число и смысл этих характеристик зависит от используемого драйвера. Для драйвера STANDARD, используемого, в частности, для баз данных Paradox, набор характеристик псевдонима минимальный: Туре — имя драйвера и PATH — путь к базе данных. Щелкнув на параметре PATH, вы увидите кнопку с многоточием. При ее нажатии отобразится обычный диалог Windows, позволяющий выбрать новый каталог. Таким образом, можно изменить характеристики псевдонима, если, например, изменилось расположение базы данных на диске. После этого все приложения, использующие этот псевдоним, автоматически будут работать с данными, даже не заметив изменения их месторасположения. В правой панели можно изменять только те параметры, имена которых не выделены жирным шрифтом. Значения выделенных параметров изменять нельзя. Для создания нового псевдонима надо щелкнуть правой кнопкой мыши и во всплывшем меню выбрать раздел New — новый псевдоним. Появится небольшое диалоговое окно, в выпадающем списке которого необходимо выбрать драйвер для создаваемого псевдонима. Тип драйвера STANDARD можно использовать для таблиц Paradox, dBASE, FoxPro, для текстов ASCII. Выбрав драйвер, щелкните ОК, и в дереве псевдонимов появится новая вершина, для которой можно задать имя — псевдоним. В правой панели появятся параметры, которые необходимо установить для создаваемого псевдонима. Для типа STANDARD эти параметры следующие: PATH Путь к базе данных DEFAULT DRIVER Один из следующих драйверов: PARADOX для таблиц Paradox (файлов .db), DBASE для таблиц dBASE (файлов .dbf), FOXPRO для таблиц FoxPro (файлов .dbf), ASCIIDRV для текстов ASCII (файлов .txt) ENABLE BCD Определяет, должна ли BDE транслировать числовые поля в значения с плавающей запятой, или в коды BCD. BCD позволяет избежать ошибок округления. Если ENABLE BCD = true, то поля DECIMAL и NUMERIC преобразуются в коды BCD После задания параметров щелкните правой кнопкой мыши и из всплывшего меню выберите раздел Apply. Новый псевдоним будет зафиксирован. Для удаления существующего псевдонима выделите его в левой панели, щелкните правой кнопкой мыши и из всплывшего меню выберите раздел Delete. Страница Configuration (конфигурация) позволяет изменять параметры выбранного драйвера и устанавливать системные параметры, определяющие, как преобразовываются строки с обозначением дат, времени и чисел в соответствующие значения. Компоненты, используемых для связи с базами данных Компоненты, используемые для работы с базами данных, расположены в библиотеке компонентов на страницах Data Access (доступ к данным) и Data Control (управление данными). Каждое приложение, использующее базы данных, обычно имеет по крайней мере по одному компоненту следующих трех типов: Компоненты — наборы данных (data set), непосредственно связывающиеся с базой данных. Это такие компоненты, как Table, Query, StoredProc. Компонент — источник данных (data source), осуществляющий обмен информацией между компонентами первого типа и компонентами визуализации и управления данными. Таким компонентом является DataSource. Компоненты визуализации и управления данными, такие, как DBGrid, DBText, DBEdit и множество других. Связь этих компонентов друг с другом и с базой данных можно представить схемой, приведенной на рисунке 1. Таблица базы данных data set: Table, Query или StoredProc data source: DataSource Визуализация и управление: DBGrid, DBText, DBNavigator ... Рис. 1. Схема взаимодействия компонентов C++Builder с базой данных Помимо указанных компонентов в приложении может размещаться компонент Database. Этот компонент в основном используется в приложениях, работающих на платформе клиент/сервер. Он обеспечивает общение с удаленным сервером, реализацию транзакций, работу с паролями. Компонент Database целесообразно вводить в приложение только в сравнительно редких случаях. Если он и введен явно, C++Builder автоматически создает его для каждой используемой в приложении базы данных. Еще один компонент, который тоже автоматически создается C++Builder — компонент Session. Это главный компонент любого приложения, работающего с базами данных. Но в явном виде эти компоненты имеет смысл вводить только в многозадачные приложения, в которых параллельно обрабатывается несколько потоков информации. Компонент Table Установка связей между компонентами и базой данных, навигация по таблице Для создания простейшего приложения, работающего с базой данных необходимо выполнить следующую последовательность действий: 1. Откройте новое приложение 2. Перенесите на форму компонент Table со страницы библиотеки Data Access. 3. Перенесите на форму с той же страницы библиотеки компонент DataSource, который будет являться источником данных. Оба эти компонента невизуальные, пользователю они будут не видны, так что их можно разместить в любом месте формы. 4. В качестве компонента визуализации данных возьмите компонент DBGrid со страницы Data Control. Это визуальный компонент, в котором будут отображаться данные таблицы. Поэтому ему необходимо задать соответствующий размер (растянуть), или в его свойстве Align установить alClient. 5. Установите цепочку связей между этими компонентами. Для этого необходимо выделить на форме компонент DBGrid1 и установить его свойство DataSource в Инспекторе Объектов в DataSource1. 6. Установите связь между источником данных и набором данных. Для этого выделите компонент DataSource1 и найдите в Инспекторе Объектов его главное свойство — DataSet. Установите это свойство раврым Table1. 7. Свяжите компонент Table1 с необходимой таблицей базы данных. Для этого установите свойство DatabaseName. В выпадающем списке этого свойства в Инспекторе Объектов выберите из списка всех доступных BDE псевдонимов баз данных, псевдоним, с которым необходимо установить связь. После этого установите значение свойства TableName. В выпадающем списке этого свойства перечислены таблицы, доступные в данной базе данных. Выберите необходимую вам таблицу. Прямо в процессе проектирования можно соединиться с базой данных. Соединение осуществляется установкой в true свойства Active. Если все сделано правильно, то вы увидите в поле компонента DBGrid1 данные из таблицы и сможете просмотреть их. Заранее выставлять для таблиц Active = true допустимо только в процессе настройки и отладки приложения, работающего с локальными базами данных. Вы можете прямо сейчас сохранить проект запустить его на выполнение и убедиться, что с ним можно работать. Вы можете просматривать данные, редактировать их (редактированные данные будут помещаться в базу данных в момент перехода от редактируемой записи к любой другой). Разрешить пользователю так просто, как в созданном приложении, редактировать данные в таблице в большинстве случаев недопустимо. Слишком велика вероятность, что без соответствующей проверки вводимых данных будут сделаны ошибки. Предотвратить редактирование данных можно, установив свойство ReadOnly компонента DBGrid1 в true. Другой способ сделать то же самое - установить в свойстве Options подсвойство dgEditing в false. Отметим еще одно свойство компонента Table — Exclusive. Это свойство определяет доступ к используемой таблице при одновременном обращении к ней нескольких приложений (например, при работе в сети или в многозадачном режиме). Если задать значение этого свойства true, то таблица будет закрыта для других приложений. Свойство можно изменять только при Active = false. В спроектированное приложение можно добавить еще один компонент, управляющий работой с таблицей — навигатор DBNavigator, расположенный на странице Data Control библиотеки компонентов. Для этого измените свойство Align компонента DBGrid1 на alBottom, сдвиньте верхний край этого компонента немного вниз и на верх формы поместите компонент DBNavigator. Компонент имеет ряд кнопок, служащих для управления данными: nbFirst перемещение к первой записи nbPrior перемещение к предыдущей записи nbNext Перемещение к следующей записи nbLast перемещение к последней записи nblnsert вставить новую запись перед текущей nbDelete удалить текущую запись nbEdit редактировать текущую запись nbPost послать отредактированную информацию в базу данных nbCancel отменить результаты редактирования или добавления новой записи nbRefresh очистить буфер, связанный с набором данных Пользуясь свойством навигатора VisibleButtons, можно убрать любые ненужные в данном приложении кнопки. Например, если вы не хотите разрешить пользователю вводить в базу данных новые записи, то можете установить в false кнопку nblnsert. Если вы хотите вообще запретить редактирование, то можно оставить только кнопки nbFirst, nbPrior, nbNext и nbLast, а все остальные убрать. Чтобы приложение с навигатором работало, надо установить основное свойство навигатора — DataSource — источник данных (имя компонента DataSource). Поля просмотра Для того, чтобы создать поля просмотра, прежде всего необходимо иметь на форме два компонента Table, в один из которых вы добавите это поле, а другой будет источником данных для этого поля. Откройте Редактор полей. Сделать этом можно щелкнув два раза на том компоненте Table, в который вы хотите ввести поле просмотра. В появившемся окне Редактора Полей щелкните правой кнопко мыши и из всплывшего меню выберите раздел New Field. Перед вами откроете окно создания нового поля. Введите имя (Name) создаваемого поля. Оно должно отличаться от имен других полей. Укажите тип (Туре) создаваемого поля. Он, как правиле должен соответствовать типу того поля в таблице просмотра, которое вы хотите просматривать. Для строк и некоторых других типов полей надо еще указать размер поля (Size). После того, как вы определили новое поле, нажмите в группе радиокнопок Field type кнопку Lookup. Выберите в выпадающем списке Key Fields ключевое поле (или поля) в таблице, в которой вы создаете новое поле, например ТаЫе1. Это то поле или поля, по которым вы будете связываться с другой таблицей. В выпадающем списке Dataset выберите таблицу, которую вы хотите просматривать, например Table2. Далее в выпадающем списке Lookup Keys выберите ключевое поле (или поля) просматриваемой таблицы. И, наконец, в выпадающем списке Result Field выберите просматриваемое поле. Щелкните на ОК. Вы вернетесь в Редактор Полей, в котором появится новое поле. В Инспекторе Объектов для него можно задать свойство DisplayLabel – соответствующий заголовок, который будет отображаться при выполнении программы в компонентах визуализации данных, например в DBGrid1. Вычисляемые поля Вычисляемыми полями называются поля, значения которых вычисляется по некоторой формуле на основании значений других полей записи. Процедура добавления вычисляемых полей аналогична процедуре добавления полей просмотра. При этом в группе радиокнопок Field type должена быть выделена опция Calculated. Не забудьте в Инспекторе Объектов задать для создаваемого поля значение DisplayLabel. Сама формула для вычисления значения такого поля задается следующим образом. Необходимо выйти из Редактора Полей, выделить компонент Table, в который было добавлено вычисляемое поле, перейти в Инспекторе Объектов на страницу событий и добавить в функцию, вызываемую при событии OnCalcFields оператор расчета значения поля, например: Table1AMOUNT->Value = Table1PRICE->Value * Table1P_QUANTITY->Value; Программирование работы с базами данных Состояние набора данных Состояние набора данных можно определить по основному свойству State компонента типа TTable. Это свойство доступно только во время выполнения и только для чтения. Набор данных может находиться в одном из следующих основных состояний: dsInactive Набор данных закрыт, данные недоступны dsBrowse dsEdit Данные могут наблюдаться, но не могут изменяться. Это состояние по умолчанию после открытия набора данных Текущая запись может редактироваться dsInsert Может вставляться новая запись dsSetKey Доступен режим поиска записи и операция задания диапазона изменений SetRange. Может наблюдаться только ограниченное множество данных и не может проводиться редактирование или вставка новой записи Состояния могут устанавливаться в приложении во время выполнения, но не непосредственно, а с использованием различных методов. Метод Close закрывает соединение с базой данных, устанавливая свойство Active набора данных в false. При этом State переводится в состояние dslnactive. Метод Open открывает соединение с базой данных, устанавливая свойство Active набора данных в true. При этом State переводится в состояние dsBrowse. Метод Edit переводит набор данных в состояние dsEdit. Методы Insert и InsertRecord вставляют новую пустую запись в набор данных, выполняют еще ряд операций, о которых вы можете посмотреть в справке C++Builder, и переводят State в состояние dslnsert. Методы EditKey, SetRange, SetRangeStart, SetRangeEnd и ApplyRange, связанные с поиском записи и с заданием допустимого диапазона изменения данных, переводят State в состояние dsSetKey. Многие другие методы также устанавливают автоматически различные состояния набора данных. При программировании работы с базой данных надо следить за тем, чтобы набор данных вовремя был переведен в соответствующее состояние. Пересылка записи в базу данных Пока идет редактирование текущей записи, изменения осуществляются в буфере, а не в самой базе данных. Пересылка записи в базу данных производится только при выполнении метода Post. Метод может вызываться только тогда, когда набор данных находится в состоянии dsEdit или dslnsert. Этот метод можно вызывать явно, например, Table1->Post(). Но кроме того он вызывается неявно при любых перемещениях по набору данных, т.е. при перемещении курсора на новую текущую запись, если набор данных находится в состоянии dsEdit или dslnsert. Отменить изменения, внесенные в запись, можно методом Cancel. При выполнении того метода, если предварительно не был вызван метод Post, запись возвращается состоянию, предшествовавшему редактированию, и набор данных переводится в состояние dsBrowse. Доступ к полям Поля отображаются объектами класса TField и производных от него классов TStringField, TSmallintField, TBooleanField и т.п. Эти объекты могут создаваться гремя способами: автоматически генерироваться для каждого компонента набора данных (Table и др.); в процессе проектирования с помощью Редактора Полей; программно в процессе выполнения приложения. Автоматическая генерация объектов класса TField происходит в момент открытия базы данных, если эти объекты не создаются другими способами: в процессе проектирования с помощью Редактора Полей или программно во время выполнения. Объекты генерируются для всех полей таблицы (это может быть избыточно для конкретного приложения). Создание объектов полей в процессе проектирования с помощью Редактора Полей было рассмотрено ранее. Этот вариант исключает автоматическое создание объектов. Таким образом, все поля, объекты которых не созданы с помощью Редактора Полей, недоступны для приложения. Использование Редактора Полей, очень удобно, так как позволяет задать в процессе проектирования множество полезных свойств для каждого поля. Программное создание объектов класса TField используется сравнительно редко. Доступ к объектам полей возможен тремя способами: по порядковому индексу объекта; по имени поля; по имени объекта. Доступ по порядковому индексу осуществляется через свойство TField Fields[int i], где i — индекс объекта. Индексация, как всегда в C++Builder, начинается с 0. Например, Table1->Fields[0] это первый объект поля таблицы Table1. Доступ по имени поля осуществляется с помощью метода FieldByName(«имя»). Например, Table1->FieldByName(«PRICE») - это объект, связанный с полем PRICE. Доступ по имени объекта возможен только к объектам, созданным с помощью редактора Полей. По умолчанию C++Builder формирует имена объектов полей (Name) из имени таблицы и имени поля. Например, Table1AMOUNT. Эти имена можно видеть, работая с Редактором Полей. Это имя может быть изменено на любое другое. Обращение к объекту по имени не требует, в отличие от предыдущих вариантов, ссылки на таблицу. Можно просто написать Table1AMOUNT и это будет необходимый объект. Автоматически создаваемые объекты имени не имеют — их свойство Name пусто. Поэтому для них обращение по имени невозможно. Среди рассмотренных способов доступа к полям наиболее быстрым, конечно, является доступ по имени объекта. Его недостатком является жесткая кодировка поля, к которому производится обращение. Если надо, чтобы строка кода в разных ситуациях обращалась к разным полям, то надо использовать или доступ по индексу Fields[i] (тогда индекс i можно сделать переменным), или по имени поля методом FieldByName(s) (строку s можно сделать переменной). Значение поля хранится в свойстве Value. Тип этого свойства — Variant, т.е тип определяется типом поля. Например, Table1->FieldByName(«P_NAME»)->Value — это строка, a Table1>FieldByName(«PRICE»)->Value — это число. Имеется также ряд свойств, переводящих один тип значений в другой. Например, свойство AsString переводит тип любого поля в строку при чтении значения поля, и осуществляет обратный перевод строки в тип поля при записи значения поля. Например, можно написать: EPrice->Text = Table1->FieldByName("PRICE")->AsString; и в окно редактирования EPrice будет занесено в текстовом виде значение в текущей записи поля PRICE, хотя поле PRICE имеет числовой тип. Помимо AsString имеются еще аналогичные свойства Aslnteger, AsFloat AsBoolean, AsDateTime. Свойство Aslnteger осуществляет преобразования между типом данного поля и целым 32-битным числом, свойство AsFloat делает то же самое для действительных чисел с плавающей запятой, свойство AsBoolean — для булевых значений, свойство AsDateTime — для значений дат и времени в принятом в C++Builder формате TDateTime. В некоторых случаях может потребоваться узнать тип данного поля. Это можно сделать из свойства объекта поля DataType, которое может принимать значения: ftUnknown (неизвестное), ftAutoInc (автоматически нарастающее), ftString (строка) и т.д. Методы навигации Наборы данных имеют ряд методов, позволяющих осуществлять навигацию — перемещение по таблице: First Перемещение к первой записи Last Перемещение к последней записи Next Перемещение к следующей записи Prior Перемещение к предыдущей записи MoveTo(int i) Перемещение к концу (при i>0) или к началу (при i<0) на i записей Любое перемещение по набору, находящемуся в состоянии dsEdit, автоматически вызывает метод Post, который пересылает текущую запись, если она была изменена, в базу данных. И после этого уже невозможно применить метод Cancel для уничтожения изменений. Поэтому надо принимать меры, чтобы в наборе данных, находящемся в состоянии dsEdit, перед любым перемещением происходила проверка правильности данных, или перемещаться по набору в каком-то другом состоянии (например dsBrowse). При перемещениях можно совершить ошибку, выйдя за пределы имеющихся записей. Например, если находиться на первой записи и выполнить метод Prior, произойдет выход за начало таблицы. Чтобы контролировать начало и конец таблицы, существуют два свойства: Eof (end-of-file) — конец данных, и Bof (beginning of file) — начало данных. Эти свойства становятся равными true, если делается попытка переместить курсор соответственно за пределы последней или первой записи, а также после выполнения методов соответственно Last и First. Задание: Разработать приложение, позволяющее создавать, редактировать и выполнять пользовательские запросы на языке SQL. Таким образом, приложение как минимум должно иметь: поле для ввода запроса пользователем, область для вывода результатов запроса, кнопку, запускающую запрос на выполнение. В качестве базы данных используйте базу данных под управлением SQL-сервера, созданную в лабораторной работе № 6. В отчет включите листинг программы, а также скриншоты выполнения запросов в разработанном приложении на выборку и модификацию данных.