
- •«Современные методы разработки программ». Этапы решения задачи
- •Постановка задачи.
- •Техническое задание
- •Разработка структуры базы данных
- •Написание кодов
- •Отладка и внедрение
- •База данных
- •Поля таблицы
- •Ключи и индексы.
- •Способы доступа к данным.
- •Связь между таблицами.
- •Создание базы данных
- •Псевдонимы базы данных
- •Создание таблицы
- •Задание полей
- •Задание свойств таблицы (Table properties)
- •Свойство Validity Checks – проверка правильности значений.
- •Свойство Table Lookup – таблица просмотра
- •Свойство Table Language – язык таблицы
- •Изменение структуры таблицы с помощью Database Desktop
- •Изменение в реестре для корректного отображения русского языка
- •О бзор компонентов Delphi, используемых для связи с бд.
- •Форма модуль данных Data Module
- •Компонент Database
- •Компонент Table
- •Свойства, которые доступны программным путем
- •Методы компонента tTable
- •События компонента tTable.
- •К омпонент Query
- •Свойство sql
- •Query и Параметры
- •К омпонент DataSource
- •Свойства
- •События
- •Компонент dbNavigator
- •Компоненты визуализации и управления данными со страницы Data Control Компонент dbGrid
- •События
- •Другие компоненты визуализации
- •П ример простейшего приложения с использованием компонента Table
- •Основы языка sql. Общие сведения.
- •Отбор данных из таблиц. Оператор выбора Select
- •Совокупные характеристики
- •Вложенные запросы.
- •Объединение таблиц.
- •Соединение union
- •Модификация записей Вставка записи
- •Values(‘Орлов’,’Александр’,’Иванович’,’м’,’23.04.1981’,’Гродно’)
- •Редактирование записи
- •Удаление записи
- •Операции с таблицами Создание таблицы
- •Изменение структуры таблицы
- •Удаление таблицы
- •Операции с индексами
- •Создание индекса
- •Удаление индекса
События компонента tTable.
Рассморим самые для нас интересные (рис.6).
BeforeEdit, BeforeDelete, BeforePost Названия говорят сами за себя. Вызываются сразу перед соответствующим событием. Однако следует проявлять осторожность - не следует выполнять действия, которые могут повторно вызвать этот же метод. Т.е. нельзя в обработчике BeforePost выполнять сохранения изменений, т.к. это повторно вызовет этот же метод. Стандартная ошибка начинающих. Или вызвать метод Edit в обработчике BeforePost, не учтя, что после внесения изменений они будут сохраняться, что повлечет повторный вызов BeforePost, который вызовет Edit... до бесконечности.
AfterEdit, AfterDelete, AfterPost Аналогично предыдущим, названия говорят сами за себя. Вызываются сразу после соответствующего события. Те же предостережения.
BeforeScroll, AfterScroll Вызываются при каждом перемещении между записями.
OnCalcFields При установленном AutoCalcFields вызывается для каждой записи. Именно в обработчике этого события и происходит вычисление значений вычисляемых полей. Удобно, но надо помнить о:
Если вычислений будет слишком много, программа будет заметно тормозить.
Если внутри этого обработчива выполнить переход на другую запись, этот же метод будет вызван для той записи, и так далее, что может выйти боком.
OnFilterRecord Возвращает true или false, что указывает, обращать внимание на эту запись или нет. Слишком объемные вычисления могут замедлить программу.
OnPostError, OnEditError и т.п. Позволяют вам самим обработать исключительную ситуацию. Что-то исправить, или хотя бы вывести сообщение об ошибке на понятном пользователю языке.
К омпонент Query
Компонент Query имеет большинство свойств (рис.7) и методов, совпадающих с Table. Поскольку в ряде случаев компоненты Table и Query в приложениях взаимозаменяемы, то возникает естественный вопрос – какой же из них использовать?
При работе с локальными базами данных чаще используется Table. С его помощью проще не только просматривать таблицу базы данных, но и модифицировать записи, удалять их, вставлять новые записи. Но при работе с серверными базами данных компонент Table становится малоэффективным. В этом случае он создает на компьютере пользователя временную копию серверной базы данных и работает с этой копией. Естественно, что подобная процедура требует больших ресурсов и существенно загружает сеть. Этот недостаток отсутствует в компоненте Query. Если запрос SQL сводится к просмотру таблицы (запрос SELECT), то результат этого запроса (а не сама исходная таблица) помещается во временном файле на компьютере пользователя. Если же запрос SQL связан с какими-то изменениями содержания таблицы, то никаких временных таблиц не создается. BDE передает запрос на сервер, там он обрабатывается,, и в приложение возвращается информация о том, успешно ли завершена соответствующая операция. Благодаря такой организации работы эффективность Query при работе в сети становится много выше, чем эффективность Table.
Таким образом, вместо компонента Table лучше использовать компонент Query в случаях:
Когда необходимо получить данные из нескольких таблиц так, как будто это одна таблица.
Когда необходимо прямо во время работы программы менять источники данных, переключаться на другие таблицы.
Когда отфильтровывается множество данных. При этом он фильтрует знаительно быстрее, чем Table с использованием обработчика события OnFilterRecord.
При создании клиент-серверных приложений, с ним удобнее правильно организовывать блокировки данных.
Он позволяет выполнять сложные запросы, что гораздо больше, чем просто выборка строк из таблицы.
Он позволяет производить не только выборку данных, но и модификацию, создание и удаление таблиц, раздачу прав доступа - вся мощь SQL в ваших руках.
Наконец самое главное, запрос выполняется на сервере. Сервер получает запрос, выбирает данные в соответствии с ним, и отправляет их вам. Вы вряд ли почувствуете разницу при создании простых небольшх приложений, но она будет весьма значительна, при попытке получить три строки по сети из базы в 100 000 записей. Особенно если сеть не слишком быстрая. Впрочем, и при создании небольших локальных приложений разница существует.
Как и в Table, свойства, которые видны в Object Inspector (рис.7) лишь вершина айсберга. Большая часть свойств и методов доступна программным путем. И здесь опять множество отличий. Например, перечислим способы, получить данные из поля:
A := Query1.FieldByName(' Имя_поля ').AsString; // AsInteger, AsFloat, AsDate и т. д.
A := Query1.FieldByName('Имя_поля').Value;
A := Query1.FieldValues[' Имя_поля '];
A := Query1.Fields.[ Номер_поля].Value;
A := Query1.Fields.[Номер_поля].AsString; // AsInteger, AsFloat и т.д.
Например:
Editfam.Text:= QueryOsndan.FieldByName('Fam').AsString;
Editfam.Text:= QueryOsndan.FieldByName('Fam').Value;
Editfam.Text:= QueryOsndan.FieldValues['Fam'];
Editfam.Text:= QueryOsndan.Fields[2].Value;
Editfam.Text:= QueryOsndan.Fields[2].AsString;
Здесь есть тонкость, заключающаяся в том, что запросы часто меняются программно. Это накладывает некоторые ограничения на использование этих способов доступа к данным. При использовании последних двух способов, надо понимать, что поля могут поменять порядок.
Так как же пользоваться этими способами? Как обычно, учитывая несколько простых правил.
При выборке из нескольких таблиц, если появляются, например, 2 поля с одинаковыми именами (Field1), Delphi даст им имена Field1 и Field1_1. Т.е. поле, которое содержится в таблице упомянутой первой, сохранит свое имя, а второе - изменит.
Обращаться к полю не по имени, на мой взгляд, не совсем удобно. Не наглядно, и через некоторое время можно забыть, какое поле подразумевалось. Такой подход имеет смысл при переборе всех полей в цикле, или когда пользователь сам выбирает таблицу, и вы не знаете имен полей, с которыми придется работать. При изменении запроса важно сохранить не только все поля, как в предыдущем случае, но и их последовательность.
Все стандартные методы перемещения по выборке (First, Last, Next, Prior) действуют и здесь.
First - перемещение к первой записи
Last - перемещение к последней записи
Next - перемещение к следующей записи
Prior - перемещение к предыдущей записи
MoveTo(i:integer) - перемещение к концу (при i>0) или к началу (при i<0) на i записей.
Чтобы контролировать начало и конец таблицы, существуют два свойства: EOF (end of file)- конец данных, и Bof (beginning of file) – начало данных.
Пример как добавить в компонент Memo все фамилии из таблицы:
Memo1.Clear;
Query1.First;
While not Query1.Eof do
Begin
Memo1.Lines.Add(Query1.fieldbyname('fam').asstring);
Query1.Next;
End;
Одна из важнейших для пользователя операций с базами данных – поиск записей по некоторому ключу. Как и у компонента Table можно пользоваться методами:- Locate, Lookup, FindKey, FindNearest. Приведу описание метода Locate:
Function Locate(const KeyFields: string; KeyValues: Variant; Options: TLocateoptions):Boolean;
Например
Query1.Locate (‘fam’,Edit1.Text, [LoCaseInsensitive,LoPartialKey]);
Где LoCaseInsensitive – игнорирование регистра, LoPartialKey – частичное совпадение
А вот при изменении опять отличия. Дело в том, что изменять можно данные не любого запроса. Для того, чтобы запрос позволял вносить изменения в выбранные данные, он должен удовлетворять следующим условиям:
Запрос должен представлять собой именно выборку, т.е. оператор select. У вас ничего не выйдет, если запрос представляет собой вызов хранимой процедуры или что-нибудь типа Create Table.
Запрос должен выбирать данные всего из одной таблицы.
Выходные данные не сортируются, т.е. в запросе нет Group by и Order by
В запросе не используются агрегатные функции, т.е. функции, вычисляющие значения для групп записей (sum, count, min, max, avg и др.)
Данные не кешируются, т.е. значение свойства CashedUpdate равно false.
Необходимо установить свойство RequestLive в true.
Следует перед попыткой изменить данные проверить свойство CanModify. Если оно равно false - у вас ничего не выйдет.
Могут быть и другие ограничения, все зависит от используемой вами СУБД и ваших прав доступа.
Если наш запрос - изменяемый, то прекрасно работают методы Edit, Insert, Delete, Post, Cancel - как в TTable. А если не изменяемый - выйти из положения нам поможет компонент TUpdateSQL.
События у компонента TQuery такие же как и у компонента TTable.