7. Общие принципы работы с наборами данных
Под набором данных в
Delphi понимается группа записей из одной или нескольких ТБД, доступная для использования через компоненты TTable или TQuery.Как набор данных, рассматривается также компонент
TStoredProc (хранимая процедура в серверной базе данных, возвращающая набор данных). Однако применение компонента TStoredProc ограничено. Во-первых, он может применяться только для серверных (удаленных) БД; во-вторых, только для тех из них, которые поддерживают механизмы хранимых процедур, возвращающих набор данных. Поэтому в дальнейшем при рассмотрении наборов данных, будем ориентироваться в основном на компоненты TTable и TQuery.Если рассматривать иерархию компонентов
Delphi, компоненты TTable и TQuery являются наследниками компонента TDBDataSet, потомка компонента TBDEDataSet, который, в свою очередь, является наследником компонента TDataSet.Компоненты
TTable и TQuery имеют общие свойства, методы и события;это обусловливается тем, что они имеют общих "родителей". Именно эти свойства, методы и события рассматриваются в настоящем разделе. При этом
TTable и TQuery называются общим термином "набор данных" (НД). В литературе по Delphi TTable и TQuery при рассмотрении их общих свойств часто называют типом DataSet, по имени типа их предка.TTable
и TQuery имеют также свойства, методы и события, присущие только TTable или только TQuery. Такие свойства, методы и события для каждого из данных компонентов рассматриваются далее в отдельных главах, посвященных каждому из указанных компонентов.Перед рассмотрением общих возможностей для работы с НД разберем общие черты и различия
TTable и TQuery. TTableTTable содержит записи, источником которых может быть только одна ТБД. Состав записей зависит от того, производится ли в
TTable фильтрация по какому-либо условию. Если нет, то в TTable будут представлены все записи ТБД, ассоциированной с данной TTable; если да, то в TTable попадут только записи, удовлетворяющие условию фильтрации.По той причине, что одна и та же
TTable может в один момент времени содержать неотфильтрованные записи, в другой - отфильтрованные по одному условию и в третий момент времени - записи, отфильтрованные по третьему условию, считают, что НД TTable содержит подмножество множества записей ТБД, которая ассоциирована с данным TTable.Псевдоним БД указывается в свойстве
DatabaseName, имя ассоциированной ТБД - в свойстве TableName. TQueryTQuery содержит записи, источником которых могут являться несколько ТБД, а также агрегированные значения (такие как сумма, минимум, максимум, среднее), просчитанные по полям одной или нескольких таблиц.
НД формируется так: выполняется SQL-запрос, представленный оператором
SELECT (свойство SQL), и в качестве НД возвращаются записи из таблиц-источников, удовлетворяющие определенным условиям (если они имеются).ТБД-источники перечисляются в разделе
FROM оператора SELECT. Условия выборки записей указываются в разделе WHERE. Записи результирующего НД состоят из полей, перечисленных после ключевого слова SELECT (или, если указан символ * - всех полей). Например:SELECT T.NumTeacher, S.NumStudent, T.ExamDate
FROM teachers T, students S WHERE (T.Kurs = S.Kurs)
В данном случае будет возвращен НД, состоящий из записей, источником которых служат ТБД
teachers и students. Эти записи должны удовлетворять условию равенства поля Kurs обеих ТБД. В результирующем НД будет всего 3 поля - NumTeacher, ExamDate из teachers и NumStudent из students.Более подробно об операторе
SELECT и иных SQL-операторах см. соответствующие разделы во второй части книги.Псевдоним БД указывается в свойстве
DatabaseName. В случае, если псевдоним указан, все ТБД в разделе FROM оператора SELECT считаются принадлежащими данной БД; в противном случае для каждой ТБД необходимо указывать маршрут поиска, а если он не указан, ТБД должны располагаться в текущем каталоге.Как и в случае использования
TTable, НД TQuery может содержать полное множество записей какой-либо ТБД. В этом случае, являющемся, вообще говоря, частным, в качестве списка полей следует указать символ "*", выборка должна вестись из одной ТБД и должно отсутствовать условие фильтрации в операторе SELECT.НД могут находиться в одном из 6 состояний:
dslnactive |
НД закрыт. |
dsBrowse |
Состояние по умолчанию для открытого НД. Показывает, что записи просматриваются, но в данный момент не изменяются. |
dsEdit |
НД находится в состоянии редактирования текущей записи (после явно или неявно вызванного метода Edit). |
dslnsert |
НД находится в состоянии добавления новой записи (после явно или неявно вызванного метода Insert или Append). |
dsSetKey |
НД находится в состоянии поиска записи по критерию, заданному методами FindKey, GotoKey, FindNearest или GotoNearest. По окончании поиска НД переходит в состояние dsBrowse. |
dsCalcFields |
Выполняется установление значений вычисляемых полей (по алгоритму, заданному в обработчике события OnCalcFields). В данном режиме изменения в НД вноситься не могут. После выхода из режима НД переходит в предыдущее состояние. |
dsFilter |
Обрабатывается фильтрация записей в НД при свойстве Filtered, установленном в True. Имеет место текущий вызов события OnFilterRecord для определения того, удовлетворяет ли текущая запись условию фильтрации, описанному в обработчике данного события. После выполнения события OnFilterRecord НД переводится в состояние dsBrowse. |
Рассмотрим методы, которые могут переводить БД из одного состояния в другое.
• Inactive—>dsBrowse
НД во время выполнения программы можно открыть методами Table. Open, Query. Open. Во время разработки и во время выполнения НД можно открыть, установив в True свойства Table. Active и Query. Active.• dsBrowse—>Inactive
НД во время выполнения программы можно закрыть методами Table. Close, Query. Close. Во время разработки и во время выполнения НД можно закрыть, установив в False свойства Table.Active и Query.Active.Заметим, что если какая-либо запись на момент закрытия НД находится в режиме редактирования
(dsEdit) или добавления новой записи (dslnsert), применение метода Close не приводит к автоматической выдаче метода Post. Таким образом, НД закрывается, находясь в режимах dslnsert или dsEdit, а не dsBrowse. В этом случае изменения, сделанные в записи, не запоминаются. Для перевода НД из указанных режимов в режим dsBrowse, перед тем как НД закрывается, используйте обработчик события BeforeClose. Заметим, что описанная ситуация будет встречаться в первую очередь для внезапно или принудительно закрываемых НД.• dsBrowse—>dsEdit
Перевести НД в режим редактирования можно методом Edit. После этого значения полей текущей записи можно изменять.• dsEdit—> dsBrowse
Метод Post приводит к запоминанию измененной записи в НД. Метод Cancel отменяет изменения, сделанные в полях записи. Запись не запоминается в НД.• dsBrowse—>dslnsert
Перевести НД в режим вставки можно методами Insert или Append, После этого программе становится доступна пустая запись, полям которой нужно присвоить какие-либо значения. Чтобы полям новой записи присвоить умалчиваемые значения , следует воспользоваться обработчиком события OnNewRecord.•
dslnsert—> dsBrowse Метод Post добавляет новую запись в НД. Если НД не находится в режиме dslnsert, возбуждается исключительная ситуация. Метод Cancel отменяет добавление новой записи в НД. Содержимое полей, назначенных новой записи, теряется.• dsBrowse—>dsSetKey
НД находится в данном состоянии, когда осуществляется поиск записи, удовлетворяющей условию, установленному методом SetKey (и затем, возможно, измененному методом EditKey). Именно эти методы и переводят НД в режим dsBrowse. Поиск записи производится одним из следующих методов: GoToKey, GoToNearest, FindKey, FindNearest. В случае успешного или неуспешного завершения метода поиска, НД переводится в состояние dsBrowse.• dsBrowse—>dsFilter НД находится в данном состоянии всякий раз, когда приложение обрабатывает событие OnFilterRecord при фильтрации записей (при свойстве Filtered = True). При этом НД переводится из состояния dsBrowse в состояние dsFilter. Это предотвращает модификацию НД во время фильтрации. После завершения вызова обработчика события OnFilterRecord НД переводится в состояние dsBrowse. Вызов события OnFilterRecord производится для каждой записи НД при установке свойства Filtered в состояние True.
Получить текущее состояние НД
можно, используя метод State. Он возвращает следующие константы: dslnactive, dsBrowse, dsEdit, dslnsert, dsSetKey, dsCalcFields, dsFilter.Пример
IF Tablel.State = dslnactive THEN Table1.Active := True;Реакция на изменение состояния набора данных.
Событие OnStateChange (компонент DataSource) наступает всякий раз при изменении состояния НД. Следующий пример показывает, как отобразить на экране (в компоненте Label 1) сообщение о текущем состоянии НД:procedure TForm1.DataSourcelStateChange(Sender: TObject);
var S : String;
begin
CASE Table1.State OF
dslnactive S = 'He
активна' ;dsBrowse S = '
Просмотр' ;dsEdit S
= 'Редактирование';dslnsert S = '
Вставка';dsSetKey S =
'Установка ключа' ;dsCalcFields S =
'Вычисляемое поле' ;END; {case}
Label1.Caption := S;
end;
Свойства некоторых компонентов, в первую очередь
Enabled, могут зависеть от состояния НД. Например, если кнопка Button 1 должна быть доступной для нажатия только в режиме dslnsert, допустим такой код:procedure TFormI.DataSourcelStateChange(Sender: Tobject) ;
begin
Button1.Enabled := (Tablel.State = dslnsert);
end;
7.3. Навигация по набору данных
Существует два способа работы с записями в НД.
Способ, основанный на использовании операторов
SQL, предполагает оперирование группами записей. Именно так работают SQL-операторы группового обновления НД UPDATE, INSERT, DELETE и выборки групп записей SELECT. Записи, удовлетворяющие некоторому условию, выдаются группами; даже если условию удовлетворяет только одна запись, считается, что в данном случае группа состоит из одной записи.Второй способ состоит в оперировании единичными записями. Если необходимо изменить, добавить или удалить группу записей, необходимая операция выполняется для каждой из таких записей. Для этого такие записи в НД нужно отыскать, для чего применяются навигационные методы. Они всегда работают с единичной записью и связанны с понятием курсора НД.
Понятие курсора набора данных. Под курсором набора данных понимается указатель текущей записи в конкретном наборе данных. Текущая запись - та запись, над которой в данный момент времени можно выполнять какие-либо операции (удаление, изменение, чтение значений, содержащихся в записи полей).
Существует 5 методов для изменения курсора НД:
Procedure First; |
Устанавливает курсор на первую запись в наборе данных. |
procedure Last; |
Устанавливает курсор на последнюю запись в наборе данных. |
procedure Next; |
Перемещает курсор на следующую запись в наборе данных. |
procedure Prior; |
Перемещает курсор на предыдущую запись в наборе данных. |
function MoveBy(n:Integer): Integer; |
Перемещает курсор на n записей к'концу набора данных (n > 0) или к началу набора (n < 0.) |
7.3.2. Определение начала и конца набора данных
Свойство
property BOF: Boolean;Возвращает
True, если курсор установлен на первую запись в наборе данных.Свойство
property EOF: Boolean;извращает
True, если курсор установлен на последнюю запись в наборе данных.7.3.3. Порядок следования и порядок сортировки записей
Может сложиться впечатление, что первая и последняя записи набора Данных всегда фиксированы, что это физически первая и последняя записи в наборе данных. Это неверно. Во-первых, как уже отмечалось выше, набор данных может содержать часть записей из ТБД. Поэтому набор данных -понятие логическое, а не физическое. Для
TTable последовательность расположения записей в наборе данных определяется используемьш индексом, обуславливающим сортировку. Для TQuery порядок следования записей либо случаен, особенно при использовании в качестве источника более одной ТБД, либо упорядочен в порядке перечисления полей, в разделе ORDER BY.Начиная работать с НД, имеющим одну сортировку записей, мы можем затем переопределить сортировку, и записи "перестроятся" в соответствии с новой сортировкой, т.е. логический порядок их следования изменится.
При изучении вопросов навигации по НД следует говорить прежде всего о Логическом характере следования записей, поскольку физический характер их расположения в конкретном случае неизвестен.
Пусть, например, имеется ТБД, состоящая из 2 полей: Name (Фамилия сотрудника) и Oklad (Оклад). Пусть в ТБД имеется 10 записей, которые вводились следующим образом и. следовательно, в таком порядке и хранятся: Пусть данная ТБД проиндексирована по 2 индексам. Первый - по возрастанию поля Name, второй - по возрастанию поля Oklad.Пусть в наборе данных, связанном с этой ТБД, установлен фильтр на поле
Oklad (см. метод SetRange). Значение в этом поле должно лежать в диапазоне 500..1500. Если порядок прохождения записей определяется индексом по полю Name (Tablel.IndexfieldNames := 'Name'), получим следующий НД (рис. 7.1): Если порядок прохождения записей определяется индексом по полю Oklad. получим следующий НД (рис. 7.2):Как можно заметить, при сортировке по полю
Name метод Last установит курсор на запись со значением поля Name, равным 'Якунин'; при сортировке по полю Oklad - на запись со значением поля Name, равным Name=' Юрьев'. Аналогично, если текущей является 3-я по счету запись от начала НД, для первого случая это будет 'Юрьев', для второго - 'Якунин'. Метод Next переместит курсор для первого случая на запись 'Иванов', для второго - на запись 'Якунин'.7.3.4. Навигация по набору данных вниз
Для выполнения действий, начиная от некоторой стартовой записи и до конца набора данных, используют цикл
WHILE not EOF. Стартовая запись может устанавливаться методом FindKey (см. ниже). Приведем пример для случая, когда стартовая запись - первая в наборе:WITH Tablel do begin First;
WHILE not EOF do begin
{Какие-либо действия}
Next;
END; {while]
END; {with}
7.3.5. Навигация по набору данных вверх
Для выполнения каких-либо действий, начиная от некоторой стартовой записи и до начала набора данных, используют цикл
WHILE not BOF. Стартовая запись может устанавливаться методом FindKey (см. ниже). Приведем пример для случая, когда стартовая запись - последняя в наборе:WITH Tablel do begin Last;
WHILE not BOF do begin
{Какие-либо действия}
Prior;
END; {while}
END; {with}
7.3.6. Спонтанные перемещения по набору данных
Вообще говоря, часто необходимо в зависимости от каких-либо условий "прыгать" по НД взад-вперед. Поэтому распространен вариант одновременного использования
Next, Prior и MoveBy в одном программном блоке. При этом важно помнить о том обстоятельстве, что применение метода Edit, когда изменяется значение индексного поля, по которому в настоящий момент ведется сортировка в НД, может переместить запись вниз или вверх. Например, для приводимой выше таблицы, состоящей из полей Name и Oklad, выполним следующий код, осуществляющий увеличение окладов сотрудников на 1000:Tablel.IndexFieldnames := 'Oklad';
WITH Tablel do begin
First;
WHILE not EOF do begin
Edit;
TablelOklad.Value := TablelOklad.Value + 1000;
Post;
Next;
END;//while
END; //with
Нетрудно убедиться, что указанный фрагмент не будет правильно работать: оклад господина Яковлева (500) после увеличения на 1000 составит 1500, поэтому запись переместится в НД после записи 'Юрьев', в то же время оставаясь текущей. После запоминания измененной записи
(Post) будет предпринята попытка перейти к следующей записи (Next). Однако наша запись, прежде логически первая, после изменения поля Oklad стала логически последней. Поскольку курсор НД находится на последней записи, свойство Tablel.EOF будет автоматически установлено в True. Поэтому метод Next выполнен не будет. На новом шаге цикла WHILE (2-м по счету) произойдет остановка цикла из-за выполнения условия прекращения цикла (рис. 7.3): Поэтому следует всегда придерживаться правила: не изменять значения индексного поля при прохождении набора данных в цикле. Можно предложить способ решения данной проблемы, который состоит в простом переборе записей при отключенной сортировке по исходному индексу или при сортировке по другому индексу. Например, для описанного выше НД следующий код будет правильным (результат работы - на рис. 7.5.):var OldIndexFieldnames : String;
begin
// запомним старое индексное поле
OldIndexFieldnames := Tablel.IndexFieldnames;
// назначим индекс по неизменяемому полю
Tablel.IndexFieldnames := 'Name';
WITH Tablel do begin
First;
WHILE not EOF do begin
Edit;
TablelOklad.Value := TablelOklad.Value + 1000;
Post;
Next;
END;//while
END; //with
// восстановим старое индексное поле
Tablel.IndexFieldnames := OldIndexFieldnames;
IF OldIndexFieldnames = 'Oklad' THEN
TablelOklad.Index := 0;
Поскольку значение ключевого поля не изменяется, порядок следования записей остается прежним.
Рассмотрим другой способ. Он состоит в применении к набору данных временной фильтрации.
ЗАМЕЧАНИЕ. Данный способ приводится лишь для того, чтобы показать, что могут иметь место и другие подходы к разрешению указанной проблемы. Однако лучше не привыкать к подобному "хитроумию", поскольку оно оправдано лишь когда проблему невозможно разрешить другими способами. Способ изменения текущего индекса, рассмотренный выше, проще и безопаснее. Второй способ имеет более узкое применение, поскольку в этом случае индексное поле должно иметь у всех записей одно и то же значение. Рекомендую повторно вернуться к его рассмотрению после того, как вы ознакомитесь с фильтрацией записей в НД при помощи свойства
Filtered, которое описано ниже Пусть в НД для рассмотренного выше примера имеется поле Otdel. Пусть текущий индекс в НД - также построен по полю Otdel. Имеются 2 отдела: Х и Z. Пусть необходимо сменить название отдела Х на Y.Разместим в форме 2 компонента
TEdit - OldOtdelEdit, для указания имени отдела, которое требуется заменить, и NewOtdelEdit, для указания нового имени отдела (рис. 7.6.):Для набора данных
Tablel определим обработчик события OnFilterRecordprocedure TFormI.Table1FilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept := TablelOtdel.AsString = OldOtdelEdit.Text;
end;
Этот обработчик будет фильтровать в НД
Tablel только те записи, у которых поле Otdel содержит старое значение отдела.Поясним, что в примере используется свойство набора данных
property RecordCount: Integer;
Оно возвращает текущее число записей в НД.
Определим обработчик события нажатия кнопки "Все записи - изменить код отдела":
procedure TFormI.Button4Click(Sender: TObject);
begin
// включим фильтрацию по старому имени отдела
Tablel.Filtered := True;
//последовательно перебираем записи
WITH Tablel do begin
First;
WHILE not (RecordCount = 0) do begin
Edit;
//изменяем название отдела
TablelOtdel.Value := NewOtdelEdit.Text;
//после запоминания изменений запись перестает
//удовлетворять условиям фильтрации и "исчезает"
//из набора данных. После выполнения каждого метода
Post//отфильтрованный набор данных уменьшается на одну запись
Post;
END;//while
END; //with
//
отменим фильтрациюTablel.Filtered := False;
end;
Заметим, что в цикле нет перехода к новой записи с помощью метода
Next. Дело в том, что перед началом изменения значения поля Otdel включается фильтрация. После того, как значение поля изменилось, запись перестает удовлетворять условию фильтрации, поскольку фильтрация ведется по старому значению поля Otdel. Такая запись, не удовлетворяющая условию фильтрации, в НД не входит. После того, как все записи изменены, фильтрация отменяется. Отметим также, что условием окончания цикла WHILE сделан факт отсутствия в НД записей (при включенной фильтрации это записи, удовлетворяющие условию фильтрации).Результаты работы показаны на рис. 7.7. и 7.8.
Такой способ часто применим при работе со связанными НД, родительским и дочерним. Заметим, что фильтрация в данном примере обеспечивается свойством
Filtered, общим для TTable и TQuery. Однако можно применять и иные средства фильтрации, например, метод SetRange компонента TTable.7.3.7. Реакция на изменение курсора набора данных
Событие
OnDataChange (компонент DataSource) возникает всякий раз при изменении курсора НД, т.е. при переходе к новой текущей записи. Это событие возникает, когда курсор БД уже находится на новой записи.Событие происходит и в режимах
dslnsert и dsEdit:-
при изменении какого-либо поля;- при первом перемещении с измененного поля на другое поле. Два события компонента типа "набор данных" также происходят при переходе к новой записи:
property BeforeScroll: TDataSetNotifyEventI;
Событие наступает перед переходом на другую запись в наборе данных.
property AfterScroll: TDataSetNotifyEventI;Событие наступает после перехода на другую запись в наборе данных.
7.3.8. Временное отключение визуализации при работе с НД
При выполнении действий с НД, влекущих за собой частое изменение местоположения курсора БД, в визуальном компоненте, показывающем записи (например,
TDBGrid) или текущую запись (TDBEdit и др.), будет возникать эффект "прокрутки" записей. Он не всем нравится. Кроме этого, при смене местоположения курсора БД (т.е. при смене текущей записи НД) необходимо время для отражения произошедших изменений в визуальном компоненте.Для устранения данной проблемы имеются методы
procedure DisableControls; procedure EnableControls;Первый отключает связь с визуальным компонентом, а второй -восстанавливает ее. Например, при последовательном переборе записей НД, произведенном таким образом:
WITH Tablel do begin
DisableControls;
First;
WHILE not EOF do begin
{Какие-либо действия}
Next;
END; {while}
EnableControls;
END; {with}
в таблице компонента
DBGridI не будет видно эффекта прокрутки записей. Наоборот, у пользователя возникнет иллюзия, что курсор БД сразу переустановился с текущей записи набора данных на его последнюю запись.7.4.1. Свойства, запрещающие или разрешающие изменять записи в НД
Свойство
property CanModify: Boolean;набора данных определяет, может ли НД переводиться в состояние
dslnsert и dsEdit (CanModify= True) или не может (CanModify =False). Это свойство зависит от значения свойства Readonly набора данных. Если Read0nly= True, CanModify автоматически переводится в False. Когда Read0nly= False, CanModify может принимать значения как True, так и False, устанавливая таким образом возможность изменения НД в зависимости от каких-либо условий.Свойство
AutoEdit компонента TDataSourse, связанного с данным НД, определяет, возможен ли (True) автоматический перевод НД в состояние dsEdit, или невозможен (False). В последнем случае для изменения НД программа должна вызвать метод Edit . Свойство AutoEdit не влияет на возможность перевода в состояние dslnsert. Для того, чтобы запретить НД переход в режим dslnsert, достаточно либо сделать НД открытым только для чтения (свойство НД ReadOnly = True), либо установить режим НД Readonly = True для полей, входящих в состав первичного ключа. В этом случае корректировка записи будет возможна, за исключением указанных полей.НД автоматически может переводиться в состояние
dslnsert или dsEdit, если пользователь в визуальном компоненте, связанном с НД, выполняет определенные действия. Вид этих действий зависит от визуального компонента, связанного с НД.Например, для перехода в режим
dsEdit в компоненте TDBGrid, связанном с набором данных, достаточно изменить значение любого поля; в компонентах TDBEdit или TDBMemo, связанных с отдельными полями НД, следует изменить значение поля, с которым связаны TDBEdit или TDBMemo; для компонента DBNavigator, связанного с данным НД, нужно нажать соответствующую кнопку и т.д.7.4.2. Изменение текущей записи
Чтобы изменить запись в НД, этот НД нужно перевести методом
Edit из состояния dsBrowse в состояние dsEdit, затем произвести изменение значения одного или нескольких полей записи и использовать метод Post для запоминания измененной записи в НД. Post в данном случае при благополучном исходе переводит НД из состояния dsEdit в состояние dsBrowse.Для отказа от запоминания измененной записи в НД используется метод
Cancel. Он также переводит НД из состояния dsEdit в состояние dsBrowse.Метод
Edit Редактирование записи должно быть разрешено (свойство property ReadOnly: Boolean; должно быть установлено в False). Помимо этого, могут быть запрещены для корректировки отдельные поля записи (когда свойство Readonly соответствующих компонентов TField установлено в True). Метод Edit может вызываться: программно, автоматически, когда пользователь в визуальном компоненте, связанном с НД, выполняет определенные действия. Вид этих действий зависит от визуального компонента.Автоматический перевод набора данных в режим редактирования должен быть разрешен свойством
AutoEdit соответствующего компонента DataSource (значение True).Пример.
Изменение значения поля Oklad в текущей записи набора данных, запоминание изменений. Новое значение оклада вводится пользователем в поле ввода в форме (компонент TEdit с именем Editi). Введенное пользователем значение доступно через свойство Editl.Text. Это свойство строкового типа, поэтому оно должно быть преобразовано в целочисленный вид перед присваиванием его числовому полю Oklad.WITH Tablel. do begin
Edit;
FieldByName('Oklad').Value := IntToStr(Editl.Text);
Post;
END;//with
Метод
SetFieldsМетод
procedure SetFields(const Values: array ofconst);
объединяет функциональность методов
Edit, Post и действий по присваиванию значений полям изменяемой записи. В ходе выполнения метода сначала НД переводится в режим dsEdit. Затем полям записи присваиваются значения, перечисленные в открытом массиве Values При этом первое значение в списке присваивается первому полю, второе - второму и т.д Естественно, что значения в списке должны быть совместимы с теми полями, которым они присваиваются. Например, попытка присвоить полю типа Real символьное значение приведет к возбуждению исключительной ситуацииЕсли в списке число значений меньше числа полей записи, те поля, которым "не хватило" значений, сохраняют свое первоначальное значение. Наоборот, если значений больше, чем нужно для заполнения полей, "лишние" значения теряются.
После успешного присваивания значения полям записи автоматически выполняется метод
Post.Метод
SetFields обычно выполняется после того, как пользователь введет значения в переменные и будет произведен контроль правильности их значенийПример.
Table1.SetFields(['Петров', 'Бухгалтер']) ;7.4.3. Добавление новой записи
Чтобы добавить новую запись в НД, нужно вызвать метод
Insert для перевода НД из состояния dsBrowse в состояние dsEdit Затем производится присваивание значения одному или нескольким полям записи, после чего выполняется метод Post для запоминания новой записи в НД Post при благополучном исходе переводит НД из состояния dsEdit в состояние dsBrowseДля отказа от запоминания новой записи в НД используется метод
Cancel Он также переводит НД из состояния dsEdit в состояние dsBrowse.Метод
Insert При добавлении записи изменение НД должно быть разрешено (свойство Readonly должно быть установлено в False). Помимо этого, могут быть запрещены для изменения отдельные поля записи (когда свойство Readonly соответствующих компонентов TField установлено в True). В этом случае в них нельзя ввести новые значения.Метод
Insert может вызываться: программно, автоматически, когда пользователь в визуальном компоненте, связанном с НД, предпринимает соответствующие действия. Для перехода в режим dslnsert в компоненте TDBGrid достаточно нажать на клавиатуре клавишу Insert или, находясь на последней записи НД, попытаться перейти на нижнюю, несуществующую запись. То же происходит при нажатии соответствующей кнопки связанного с данным НД компонента TDBNavigator,.Пример.
Добавление записиTable1.Insert;
// установка значений полей добавляемой записи
Tablel.Post;
Метод
AppendМетод
procedure Append; аналогичен методу Insert, но он добавляет запись в конец набора данных, в то время как Insert добавляет ее после текущей записи.Для индексированных НД применение метода
Append приводит к тем же последствиям, что и применение метода Insert.Метод
InsertRecordМетод
procedure InsertRecord(const Values: array ofconst); объединяет функциональность методов Insert, Post и действий по присваиванию значений полям новой записи. В функциональном отношении он полностью аналогичен методу SetFields (см. выше).Метод
AppendRecordМетод
procedure AppendRecord(const Values: array of const); отличен от метода InsertRecord только тем, что помещает новую запись не после текущей записи, а вслед за последней записью НД.7.4.4. Запоминание изменений - метод
PostВыполнение метода
Post приводит к запоминанию изменений, сделанных в режиме добавления или изменения записи.Если НД не находится в режиме
dslnsert или dsEdit, применение Post приводит к возбуждению исключительной ситуации. Вызов Post зависит от способа, которым ранее был вызван метод Insert или Edit: программно; автоматически.Post
обычно вызывается автоматически, если пользователь предпринимает соответствующие действия, направленные на запоминание измененной записи в НД. Вид этих действий зависит от визуального компонента, связанного с НД. Например, для компонента TDBGrid, связанного с набором данных, это -переход к другой записи. Для НД, управляемого компонентом TDBNavigator, это - нажатие соответствующей экранной клавиши компонента TDBNavigator. Реже изменения в наборе данных, автоматически переведенном в режим редактирования, запоминаются путем программного вызова метода Post.Метод
Post, независимо от того, вызывается он программно или автоматически, может завершиться неудачно. Причиной этого могут послужить неверные значения в соответствующих полях записи. Например:• поле обязательного заполнения (свойство
Required = True у соответствующего компонента TField) содержит пустое значение;• для ТБД, для которой определен уникальный ключ, возникла ситуация дублирования ключа
(Key Violation), то есть ключевое поле (группа полей) данной записи содержит значение, которое уже хранится в этом поле (группе полей) в другой записи;обработчики событий типа
OnValidate (компонент TField) или BeforePostRecord обнаружили, что какое-либо поле содержит неверное значение, не удовлетворяющее некоторым условиям. В этом случае программно возбуждается исключительная ситуация, которая подавляет выполнение Post.В лучшем случае при возникновении препятствий для выполнения
Post запись переводится в состояние, в котором НД находился до выполнения метода (dslnsert или dsEdit).7.4.5. Отмена сделанных изменений - метод
CancelМетод
Cancel отменяет все изменения, сделанные в записи. Если НД находился в режиме добавления новой записи, запись в НД не добавляется. Если НД находился в режиме изменения записи, изменявшаяся запись в НД не записывается и данные в ней остаются в том состоянии, в котором они находились до перехода в режим dsEdit. Сам НД переводится в режим dsBrowse. Вызов Cancel зависит от способа, которым ранее был вызван метод Insert или Edit: программно; автоматически.Cancel
вызывается автоматически, если пользователь предпримет соответствующие действия, направленные на запоминание измененной записи в НД. Вид этих действий зависит от визуального компонента, связанного с НД. Например, для компонента TDBGrid, связанного с набором данных, это нажатие клавиши Esc. Для НД, управляемого компонентом TDBNavigator, это нажатие соответствующей экранной клавиши компонента TDBNavigator.7.4.6. Оценка изменения записи
Часто бывает необходимо знать, вносились ли в запись изменения в режимах
dslnsert или dsEdit. Это актуально в тех случаях, когда внесение изменений в поля записи зависит от каких-либо условий, которые могут наступать или не наступать в разные моменты работы приложения. Свойство НД property Modified: Boolean; автоматически устанавливается в True, если значение какого-либо поля записи НД было изменено в режимах dslnsert или dsEdit. Методы Post и Cancel переводят свойство в состояние False.Пример. В следующем фрагменте запись будет запомнена в НД, только если в нее вносились изменения:
Table1.Edit;
IP Tablel.Modified THEN Post ELSE Cancel;
7.4.7. Реакция на изменение данных
Событие On UpdateData (компонент DataSource) возникает для измененной (или вновь добавляемой) записи, когда выполнен метод Post, но физическое перезаписывание измененной записи в ТБД еще не произошло.
Событие
On Validate (компонент TField) возникает после любого изменения значения поля, произведенного вручную или программно (это относится и к вводу значения в поле при создании новой записи). Это событие служит для контроля правильности значений поля, если на него накладываются какие-либо ограничения. Событие возникает перед выполнением метода Post, физически записывающего измененную запись в ТБД. В случае несоответствия значения поля накладываемым ограничениям выполнение Post (и, следовательно, физическое запоминание в БД записи с неверным полем) можно предотвратить, используя метод Abort или принудительно возбудив исключительную ситуацию (raise Exception. Create). Например,procedure TForm1.TableKodIzdeliaValidate(Sender: TField);
begin
IF TableKodIzdelia.AsInteger > 1000 THEN raise Exception.Create('Неверное значение кода изделия');
end;
Удаление текущей записи в наборе данных реализуется методом
Delete. Например: Table1.Delete;Удаление записи может производиться: программно; автоматически, если это предусмотрено в том или ином компоненте. Так, в компоненте
TDBGrid нажатие комбинации клавиш Ctrl + Del влечет за собой удаление записи, которое, в соответствии с опциями настройки TDBGrid, может выполняться как с запросом подтверждения, так и без него.Необходимо помнить об одной важной особенности. Записи в различных СУБД могут удаляться 2 способами:
•
пометка записи в ТБД как удаленной. Сама запись физически не удаляется из НД. В зависимости от СУБД новые записи могут записываться на место помеченных как "удаленные" или в конец ТБД. В последнем случае такие ТБД могут "разбухать" до больших размеров, поэтому время от времени для них проводят операцию сжатия, при которой помеченные как удаленные записи физически уничтожаются, а остальные записи "сдвигаются" вверх, заполняя образовавшиеся пустоты в ТБД. немедленное удаление записей из ТБД, вследствие чего последующие записи "сдвигаются" вверх, заполняя образовавшиеся в ТБД пустоты. В Delphi при работе с НД реализован второй метод. После удаления записи все оставшиеся записи "сдвигаются" наверх. При удалении одной записи это может быть несущественным, однако, если нужно удалить несколько записей, это способно внести осложнения. Например, пусть требуется удалить все записи в Tablel. Можно было бы предположить, что данную потребность можно реализовать следующим программным кодом:WITH Tablel do begin
First;
WHILE not EOF do begin
Delete;
Next; //
Ошибка!END;//while
END;//with
Однако в действительности этот код приведет к удалению примерно половины записей в Tablel. Причина этого лежит в том, что когда мы удаляем запись (например, № 3), последующие записи автоматически перемещаются вверх, и поэтому запись, бывшая до удаления следующей (№ 4), становится текущей (№ 3). После выполнения метода Next осуществляется переход к записи № 4. Таким образом, записи удаляются через одну (см. рис. 7.9 и 7.10).Удалив ненужный вызов
Next, мы сотрем все записи:WITH Tablel do begin
First;
WHILE not (RecordCount = 0) do
Delete;
END;//with
Чаще всего нужно удалять не все записи НД а часть записей, удовлетворяющих некоторому условию Рассмотрим два способа
ЗАМЕЧАНИЕ Я рекомендую вернуться к повторному рассмотрению этих способов после того как вы ознакомитесь - с фильтрацией записей в НД при помощи свойства Filtered- с поиском записей в НД при помощи метода
LocateМатериал о них приведен в данномраздече ниже
Во-первых, можно воспользоваться временной фильтрацией удаляемых записей в момент группового удаления (См п 7 3 6)
Второй способ состоит в использовании метода Locate, реализующего поиск необходимой записи по точному соответствию значений некоторых полей
Метод Locate указывает поля, по которым ведется поиск, и значения этих полей, по которым нужно найти записи В нашем случае это поле Otdel и свойство OtdelEdit Text (компонент TEdit), где содержится наименование отдела, по которому нужно удалить все записи в НД Если запись с таким значением поля Otdel найдена, Locate возвращает True, в противном случаеFalse
Как только запись найдена она удаляется, когда Locate возвратит False это значит что все записи с указанным наименованием одела удалены (см рис 7 13 и 7 14)// Обработчик нажатия кнопки 'Удалить все записи с указанным отделом"
procedure TFormI Button4Click(Sender TObject);
begin
WITH Tablel do begin
First;
WHILE Locate('Otdel', OtdeiEdit Text,[]) do Delete;
END; //with
end;
ЗАМЕЧАНИЕ.
Для больших НД, а также если в процессе удаления в НД меняются индексы, определяющие текущую сортировку, рекомендуется перед реализацией удаления отключать связь НД с управляющими компонентами, а по окончании удаления - включать-WITH Tablel do begin
DisableControls;
EnableControls;
END; {with}
Сценарий обновления записей на одной форме с компонентом TDBGridЗаписи набора данных периодически добавляются, изменяются и удаляются Если выполнение указанных действий ложится на пользователя, в приложении должны быть реализованы удобный интерфейс добавления, изменения и удаления записей
Несомненно, действия над набором данных в этом случае тесно связаны с вопросами реализации пользовательского интерфейса в приложении Материал именно такого рода содержится в данном разделе Он будет полезен начинающим разработчикам или тем, кто только начинает изучать
Delphi, поскольку показывает, что соединение пользовательского интерфейса и действий над набором данных не является чем-то исключительно сложнымЕсли Вас не интересует, как можно реализовать в форме интерфейс пользователя для внесения изменений в набор данных, Вы можете пропустить данный раздел
Существует несколько вариантов внесения изменений в набор данных в приложении-
• изменения реализуются при выполнении программного кода Как правило, такой способ внесения изменений в НД используется для транзакционных таблиц, т е таблиц, данные в которых формируются приложением автоматически Например, в транзакционную таблицу "Суммарный отпуск товаров" приложением автоматически вносятся изменения при вводе записей в другую, операционную таблицу БД, "Отпуск товаров"
• изменения в НД вносит пользователь. Реализовать пользовательский интерфейс в этом случае можно одним из следующих способов
• пользователь вносит изменения в НД, пользуясь компонентом TDBGrid. Например, для добавления новой записи в НД он нажимает на клавиатуре кнопку
Insert, после чего в TDBGrid появляется новая, пустая строка; вводит данные в поля новой строки, запоминает новую запись (перемещая курсор на другую запись) или отказывается от запоминания, нажимая на клавиатуре кнопку Esc;• пользователь нажимает одну из трех кнопок "Добавить", "Изменить", "Удалить", которые размещены в той же форме, что и компонент TDBGrid, в котором показываются записи набора данных По нажатию кнопки набор данных переводится в соответствующее состояние и вызывается другая форма, в которую пользователь вводит новые значения полей записи и нажимает кнопку "Запомнить изменения " , "Отменить" или "Подтвердить удаление записи". После этого реализуются необходимые действия, то есть выполняются методы
Post, Cancel или Delete;• пользователь нажимает одну из трех кнопок "Добавить", "Изменить", "Удалить", которые размещены в той же форме, что и компонент
TDBGrid, в котором показываются записи набора данных. По нажатию кнопки набор данных переводится в соответствующее состояние и в этой же форме активизируется панель, на которой расположены поля текущей записи набора данных. Пользователь вводит новые значения полей записи, после чего подтверждает или отменяет добавление или изменение записи, при удалении записи ему, разумеется, ничего вводить не нужно, а нужно лишь удаление подтвердить или отменить. Рассмотрим реализацию последнего сценария. Пусть имеется ТБД Sklady, записи которой содержат наименования складов Предположим, что эта ТБД будет использоваться в приложении как справочник Запись состоит из единственного поля Sklad. По этому полю построен первичный индекс, т е. данное поле может содержать только уникальное значение.Поместим в форму компонент
DBGndl для показа записей из ТБД Sklady Первоначальную высоту TDBGrid определим равной 257 Ниже DBGndl поместим в форме панель PanelToInput Values и определим ее как "невидимую" (свойство Visible = False) В'данной панели расположим компонент DBEdit1, который ссылается на поле Sklad Соответственно, когда панель невидима, невидимы и компоненты, в ней расположенныеПоместим в форму панель
InsertEditDeletePanel и установим ее свойство Visible = True. В панели разместим экранные кнопки для работы с отдельной записью "BcTaBHTb"(InsertButton), "Изменить" (EditButton), "Удалить" (DeleteButton), и кнопку для выхода "Выйти" (ExitButton) с модальным свойством ModalResult = mrOkПоместим в форме панель
PostCancelPanel и сделаем ее невидимой (свойство Visible = False) Разместим в панели экранные кнопки "Запомнить" (PostButton) и "Отменить"(Сапсе1Вийоп) для запоминания сделанных изменений в БД или отказа от запоминания (рис 7.15)Совместим местоположение панелей
PostCancelPanel и InsertEditDeletePanel, сделав их одинаковыми по размеру (рис. 7.16):Напишем следующие обработчики нажатия экранных кнопок:
// константы для обозначения высоты TDBGrid
const
NORMAL_HEIGHT
= 257; // в режиме просмотра записейUPDATE_HEIGHT
= 193; // в режимах добавления и изменения// Обработчик нажатия кнопки "Включить"
procedure TFormI.InsertButtonClick(Sender: TObject);
begin
//
визуализируем панель с кнопками "Запомнить", "Отменить'PostCancelPanel.Visible := True;
// делаем невидимой панель с кнопками "Включить",
// "Изменить" и т.д.
InsertEditDeletePanel.Visible := False;
//
уменьшаем высоту DBGridDBGridl.Height := UPDATE_HEIGHT;
// делаем видимой панель для ввода значения в запись
PanelToInputValues.Visible := True;
// переводим НД в режим добавления записи
Tablel.Insert;
// передаем фокус управления на ввод значения в запись
DBEditI.SetFocus;
end;
// Обработчик нажатия кнопки "Изменить"procedure TFormI.EditButtonClick (Sender: TObject);
begin
// делаем невидимой панель с кнопками "Включить",
// "Изменить" и т.д.
InsertEditDeletePanel.Visible := False;
//
уменьшаем высоту DBGridDBGridl.Height := UPDATE_HEIGHT;
// делаем видимой панель для ввода значения в запись
PanelToInputValues.Visible := True;
// переводим НД в режим редактирования записи
Tablel.Edit;
// передаем фокус управления на ввод значения в запись
DBEditI.SetFocus;
end;
// Обработчик нажатия кнопки "Удалить"
procedure TFormI.DeleteButtonClick(Sender: TObject);
begin
// удаляем запись, в случае подтверждения пользователем
IF MessageDIg
('Подтвердите удаление записи',mtConfirmation,[mbYes,mbNo],0) = mrYes THEN
Tablel.Delete;
// передаем управление на
DBGridDBGridI.SetFocus ;
end;
// Обработчик нажатия кнопки "Запомнить"
procedure TFormI.PostButtonClick(Sender: TObject);
begin
// пытаемся запомнить изменения в ТБД; при возникновении
//исключения полагаем, что его причиной является дублирова//ние ключа. В этом случае сообщаем пользователю и отменяем
//внесение изменений в ТБД
TRY
Tablel.Post;
EXCEPT
on EDBEngineError do begin
MessageDIg('
Такая запись уже есть',mtlnformation, [mbOk],0) ;Tablel.Cancel;
end; {on}
END; {try}
//
увеличиваем высоту DBGridDBGridI.Height := NORMAL_HEIGHT;
// делаем невидимой панель для ввода значения в запись
PanelToInputValues.Visible := False;
// делаем невидимой панель для ввода значения в запись
PostCancelPanel.Visible := False;
// делаем видимой панель с кнопками "Включить", "Изменить" и т.д.
InsertEditDeletePanel.Visible := True;
// передаем управление на
DBGridDBGridI.SetFocus ;
end;
// Обработчик нажатия кнопки "Отменить"
procedure TFormI.CancelButtonClick(Sender: TObject);
begin
Tablel.Cancel;
//
увеличиваем высоту DBGridDBGridI.Height := NORMAL_HEIGHT;
// делаем невидимой панель для ввода значения в запись
PostCancelPanel.Visible := False;
// делаем видимой панель с кнопками "Включить", "Изменить" и т.д.
InsertEditDeletePanel.Visible := True;
// делаем невидимой панель для ввода значения в запись
PanelToInputValues.Visible := False;
// передаем управление на
DBGridDBGridI.SetFocus;
end;
// Обработчик события выхода из формы.
// Если в этот момент НД находится в состоянии,
// отличном от dsBrowse, т.е. в dslnsert, dsEdit,
// он принудительно возвращается в состояние dsBrowse.
procedure TFormI.FormDeactivate(Sender: TObject);
begin
IF Tablel.State 0 dsBrowse THEN Tablel.Cancel;
end;
В режиме просмотра записей
(dsBrowse) видим окно, показанное на рис. 7.17А в режимах добавления новой записи
(dslnsert) или редактирования (dsEdit) видим окно, (рис. 7.18)При нажатии кнопки "Удалить" выдается модальная форма подтверждения удаления (рис. 7.19). Эта форма реализуется функцией
Delphi MessageDlg.Подобно тому, как в книге нужную страницу можно заложить закладкой и впоследствии быстро найти эту страницу, в НД аналогичные действия можно осуществить для записи. Для этой цели НД обладает следующими методами:
function GetBookmark: Tbookmark -
создает для текущей записи объект-закладку и возвращает ссылку на него;procedure GotoBookmark(Bookmark : Tbookmark) -
перемещает курсор БД на запись, определяемую закладкой-параметром;procedure FreeBookmark(Bookmark : Tbookmark)
- освобождает системные ресурсы закладки Bookmark;function Bookmark Valid(Bookmark: TBookmark): Boolean; -
возвращает True, если закладке Bookmark назначено значение, и False - если не назначено;function CompareBookmarks(Bookmarkl, Bookmark!: TBookmark): Integer; -
сравнивает две закладки, Bookmark 1 и Bookmark!, и возвращает: 0 - если закладки идентичны; 1 - если различаются;тип
TBookmark - указатель на экземпляр типа "закладка".ЗАМЕЧАНИЕ Метод
GotoBookmark возбуждает исключительную ситуацию, если передаваемый параметр не указывает на закладку (т.е. если закладка не создана, а мы пытаемся на нее перейти). Примерvar MyBookmark : TBookMark;
{
заложить закладку }MyBookmark := Tablel.GetBookmark;
{ перейти на запись, на которой заложена закладка}
IF Tablel. BookmarkValid. (MyBookmark) THEN
Tablel.GotoBookmark(MyBookmark);
{ освободить ресурсы, выделенные для закладки}
IF Tablel. BookmarkValid(MyBookmark) THEN
Tablel.FreeBookmark(MyBookmark);
Закладки используются в первую очередь тогда, когда :
1. Нужно проделать какие-либо действия над НД, изменяющие местоположение курсора БД;
2. Вернуться к той записи, которая была текущей перед выполнением п. 1.
7.7. Поиск записей в наборах данных
Помимо приводимых ниже средств поиска записей в НД, можно воспользоваться методами
Find, Go ToKey, FindNearest, Go ToNearest компонента TTable. Метод Locatefunction Locate(const KeyFields: string; const Key Values: Variant;
Options: TLocateOptions): Boolean;
Метод
Locate ищет первую запись, удовлетворяющую критерию поиска, и если такая запись найдена, делает ее текущей. В этом случае в качестве результата возвращается True. Если поиск был неуспешен, возвращается False.Параметры:
Список
KeyFields указывает поле или несколько полей, по которым ведется поиск, в виде строкового выражения. В случае нескольких поисковых полей их названия разделяются точкой с запятой.Критерии поиска задаются в вариантном массиве
Key Values так, что i-e значение в Key Values ставится в соответствие i-му полю в KeyFields. В случае поиска по одному полю в Key Values указывается одно значение.Options
позволяет указать необязательные значения режимов поиска:loCaselnsensilive -
поиск ведется без учета высоты букв, т.е. если в Key Values указано 'принтер', а в некоторой записи в данном поле встретилось 'Принтер' или 'ПРИНТЕР', запись считается удовлетворяющей условию поиска;loPartialKey -
запись считается удовлетворяющей условию поиска, если она содержит часть поискового контекста; например, удовлетворяющими контексту 'Ма' будут признаны записи с значениями в искомом поле "Машин ', 'Макаров' и т.д.Locate
отличается от методов FindKey, FindNearest, GoToKey, Go ToNearest (компонент TTable) следующим:• FindKey, FindNearest, GoToKey, Go ToNearest
производят поиск только по полям, входящим в состав текущего индекса TTable; в случае, когда условию поиска удовлетворяет несколько записей, текущей станет логически самая первая из них (в порядке сортировки записей в НД, определяемом текущим индексом);• Locate
производит поиск по любому полю; поле или поля, по которым производится поиск, могут не только не входить в текущий индекс, но и не быть индексными вообще.В случае, если поля поиска входят в какой-либо индекс,
Locate использует этот индекс при поиске. Если искомые поля входят в несколько индексов, трудно сказать, какой из них будет использован. Соответственно, трудно предсказать, какая запись из множества записей, удовлетворяющих критерию поиска, будет сделана текущей - особенно в случае, если поиск ведется не по текущему индексу.При поиске по полям, не входящим ни в один индекс, применяются фильтры
BDE.Пример.
Пусть имеется ТБД "Сотрудники кафедры" с целочисленным TabNum (табельный номер) и строковыми полями FIO (ФИО), Doljnos) (Должность), UchStepen (Ученая степень).Пусть ТБД имеет индексы по полям:
'TabNum' , 'FIO', 'Doljnost;FIO'.Пример
А. Осуществим поиск по полям 'Doljnost; UchStepen' (индексное и неиндексное) при различных текущих индексах в НД. Поисковый контекст -['доцент', 'кхн'] при режиме частичного совпадения значений:procedure TFormX.LocateButtonClick(Sender: TObject);
begin
Tablel.Locate ( 'Doljnost;UchStepen',
VarArrayOf(['
доцент','кхн']),[loPartialKey]) ;end;
Результаты поиска показаны на рис.7.20 - 7.22. Как видно из рисунков, при различных текущих индексах в момент выполнения поиска, результаты поиска также могут быть различными.
Пример Б Осуществим поиск по полю 'FIO' ( входит в два индекса) при различных текущих индексах в НД Поисковый контекст - ['Ма'] при режиме частичного совпадения значенииprocedure TFormX.LocateButtonClick(Sender: TObject);
begin
Tablel.Locate('FIO','Ma', [ioPartialKey]) ;
end;
Этому критерию соответствуют записи с FIO = 'Манишкина А А ', 'Мануйлова В А','Маслаченко В Ф ', ' Массалитин В Ф ' (см рис 7 23 - 7 25) Как видим, результат поиска при различных текущих индексах одинаков
Пример В Пусть заранее неизвестно, по какому полю необходимо производить поиск Тогда поместим в форму компонент
RadioGroup1, в котором перечислим поля поиска, и компонент Edit1 для ввода условий поиска (см рис 7.26)Напишем
такой обработчикprocedure TFormX.ButtonlClick(Sender: TObject) ;
var Pole : Shortstring;
begin
CASE RadioGroupl.Itemlndex OF
0 : Pole := 'TabNum' ;
1 : Pole := 'FIO';
2 : Pole := 'Doljnost;
3 : Pole := 'UchStepen';
END;
IF not Tablel.Locate(Pole,Editl.Text,[loCaselnsensitive, loPartialKey]) THEN ShowMessage('
Запись не найдена');end;
Преимущество показанного способа в том, что мы вместо выполнения нескольких
Locate (для поиска по каждому полю) выполняем один метод Locate независимо от поля, по которому производится поиск Использование методов FindFirst, FindLast, FindNext, FindPriorИзвестно, что набор данных может быть отфильтрован с использованием свойства
Filtered Условие фильтрации задается свойством Filter или описывается в обработчике события OnFliter Re cord Свойство Filtered указывает, выполнять ли фильтрацию (значение True) или нет (значение False) В этом случае в НД показываются все записи, а не только удовлетворяющие условию фильтрацииДля НД, в котором определены условия фильтрации, но сама фильтрация в текущий момент не включена,
Delphi предоставляет интересную возможность Она заключается в том, что в неотфильтрованном в данный момент НД можно обеспечить навигацию только между теми записями, которые удовлетворяют условию фильтрации (оно в текущий момент, когда свойство Filtered = False, не действует)Для этой цели используются методы
FindFirst, FindLast. FindNext, FindPriorУсловие фильтрации можно сделать совпадающим с условием поиска, указанным в параметре./^ Values метода Locate При этом поиск с помощью указанных методов имеет довольно большое преимущество перед поиском с помощью Locate если в Locate можно указывать только значения, то в условии фильтрации можно указывать логические условия, например
Accept := (DataSet['Doljnosf] = '
доцент') AND (DataSet['TabNum'] > 150000);т е. включать в НД только записи о сотрудниках-доцентах, и притом не всех, а только тех из них, у которых табельный номер больше 150000
В случае, если искомая запись найдена, данные методы возвращают
True, в противном случае - False. Аналогичный результат возвращает свойство Foundfunction FindFirst: Boolean; - переходит на первую запись, удовлетворяющую фильтру;
function FindLast: Boolean; -
переходит на последнюю запись, удовлетворяющую фильтру;function FindNext: Boolean; -
переходит на следующую запись, удовлетворяющую фильтру;function FindPrior: Boolean; -
переходит на предыдущую запись, удовлетворяющую фильтру;property Found: Boolean; -
возвращает True, если последнее обращение к одному из методов FindFirst, FindLast, FindNext, FindPrior привело к нахождению нужной записи.Пример. Предоставить пользователю возможность перемещаться на первую, последнюю, следующую, предыдущую запись, удовлетворяющую условию "Содержимое
Edit1 входит как часть ФИО сотрудника". Заметим, что свойство Table1.Filtered = False, т.е. хотя в обработчике события Table1.OnFilterRecord и указано условие фильтрации, в НД показываются все записи, и он остается в неотфильтрованном состоянии (см. рис. 7.27 и 7.28)условие
фильтрацииprocedure TFormX.TablelFilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept := POS(Editl.Text,DataSet['FIO']) > 0;
end;
// нажата кнопка "Первая"
procedure TFormX.FindFirstButtonClick(Sender: T0b;ect) ;
begin
Labell .Caption := ";
IF not Tablel.FindFirst THEN
Labell.Caption
:= 'Нет такой записи';end;
// нажата кнопка "Последняя"
procedure TFormX . FmdLastButtonClick (Sender : TObject) ;
begin
Labell.Caption := ";
IF not Tablel.FindLast THEN
Labell.Caption
:= 'Нет такой записи';end;
// нажата кнопка "Следующая
"procedure TFormX.FindNextButtonClick(Sender: TObject);
begin
Labell.Caption := ";
IF not Tablel.FindNext THEN
Labell.Caption := 'Нет такой записи';
end;
// нажата кнопка "Предыдущая"
procedure TFormX.FindPriorButtonClick(Sender: TOb^ect) ;
begin
Labell.Caption := ";
IF not Tabiel.FindPrior THEN
Labell.Caption
:= 'Нет такой записи';end;
Заметим, что поскольку фильтрация записей с использованием события
OnFilter Record или (и) свойства Filter может применяться только на небольших объемах записей (из-за того, что при этом используется последовательный метод доступа к записям в ТБД), аналогичные ограничения накладываются и на поиск записей с использованием методов FindFirst, FindLast, FindNext, FindPrior. Метод Lookupfunction Lookup(const KeyFields: string; const Key Values: Variant;
const ResultFields: string): Variant;
Метод
Lookup находит запись, удовлетворяющую условию, но не делает ее текущей, а возвращает значения некоторых полей этой записи. Тип результата - Variant или вариантный массив. Независимо от успеха поиска записи, указатель текущей записи в НД не изменяется.Lookup
осуществляет поиск только на точное соответствие критерия поиска и значения полей записи. Такой режим, как loPartialKey метода Locate (поиск по частичному соответствию значений), отсутствует.Параметры:
В
KeyFields указывается список полей, по которым необходимо осуществить поиск. При наличии в этом списке более чем одного поля, соседние поля разделяются точкой с запятой.Key Values
указывает поисковые значения полей, список которых содержится в KeyFields. Если имеется несколько поисковых полей каждому i-му полю в списке KeyFields ставится в соответствие i-oe значение в списке Key Values. При наличии одного поля, его поисковое значение можно указывать в качестве Key Values непосредственно; в случае нескольких полей - их необходимо приводить к типу вариантного массива при помощи Var-Array'Of.В качестве поисковых полей можно указывать поля как входящие в какой-либо индекс, так и не входящие в него; тип текущего индекса не имеет значения. Если поисковые поля входят в какие-либо индексы, их использование производится автоматически; в противном случае используются фильтры
BDE.Если запись в результате поиска не найдена, метод
Lookup возвращает Null, что выявляется при помощи предложенияIF VarType(LookupResults) = varNull THEN ...
В противном случае
Lookup возвращает из этой записи значения полей, список которых указан в ResultFields. При этом размерность результата зависит от того, сколько результирующих полей указано в ResultFields'.•
указано одно поле - результатом будет значение соответствующего типа или Null, если поле в найденной записи содержит пустое значение;указано несколько полей - результатом будет вариантный массив, число элементов в котором меньше или равно числу результирующих полей;
меньше потому, что некоторые поля найденной записи могут содержать пустые значения. Рассмотрим несколько вариантов.
Пример
А. Одно результирующее поле (результат - значение типа Variant) Будем осуществлять поиск в ТБД "Сотрудники" по полю 'FIO'. Поисковое значение будем вводить в Edit I. В качестве результата будем выдавать значение поля "UchStepen' (ученая степень) найденной записи.procedure TFormX.LookuplButtonClick(Sender: TObject);
var
LookupResults : Variant; //
результатbegin
//
осуществить поискLookupResults := Tablel.Lookup('FIO', Editl.Text, 'UchStepen') ;
Label 1.Caption := ";
// содержит ли результат пустое значение или
Null?CASE VarType(LookupResults) OF
varEmpt : Labell.Caption := '
Пустой результат';varNull : Labell.Caption
:= 'Запись не найдена';ELSE
// нет, результат содержит какое-то значение
Labell.Caption := LookupResults;
END; //case
end;
Заметим,что в присваивании
Labell.Caption := LookupResults;
имеет место приведение вариантного типа к строковому. Более подробно о приведении вариантных типов см. описание вариантного типа в документации и встроенной системе помощи
Delphi.Пример Б.
Несколько результирующих полей (результат - вариантный массив)Некоторые сведения по использованию вариантного массива:
Если переменная типа Variant является вариантным массивом, функция VarIsArray(LookupResults) возвращает True.
При работе с переменным числом возвращаемых полей, в конкретном случае верхнюю и нижнюю границы массива
LookupResults можно определить при помощи функций VarArrayLowBound(LookupResults, 1) и VarArrayHigh-Bound( LookupResults, 1).Тип i-го элемента вариантного массива можно определить как
VarType (LookupResults [ i]).Будем осуществлять поиск в ТБД "Сотрудники" по полю '
FIO\ Поисковое значение будем вводить в Editi. В качестве результата будем выдавать значения полей "TabNum,DolJnost,UchStepen' найденной записи (табельный номер, должность, ученая степень).procedure TFormX.LookupButtonClick(Sender: TObject) ;
var
LookupResults : Variant; //
результатbegin
//
осуществить поискLookupResults := Tablel.Lookup('FIO', Editl.Text, '' TabNum;Doljnost;UchStepen') ;
// будем показывать значения результирующих полей в
TLabelLabell.Caption := ";
Label2.Caption := ";
Label3.Caption := ";
// результат - вариантный массив ?
IF VarIsArray(LookupResults) THEN
begin
Labell.Caption := LookupResults[0];
IF LookupResults[1] 0 Null THEN
Label2.Caption := LookupResults [1];
IF LookupResults[2]
о Null THENLabel3.Caption := LookupResults [2];
end // then
ELSE
// результат - не вариантный массив, а единичное значение
CASE VarType(LookupResults) OF
varEmpty : Labell.Caption := '
Пустой результат';varNull : Labell.Caption
:= 'Запись не найдена';END; //case
end;
Если запись не найдена,
VarType (LookupResults) возвращает значение varNull; если поиск по какой-либо причине не был произведен, VarType (LookupResults) возвращает значение varEmpty. Если какое-либо из полей, чьи значения возвращаются в результате поиска в вариантном массиве, содержит пустое значение, соответствующий элемент вариантного массива также будет содержать пустое значение (Null). В этом случае обращение к нему возбудит исключительную ситуацию, поэтому нужна предварительная проверка.7.8. Фильтрация записей в наборах данных
Помимо описываемых ниже средств, для фильтрации данных могут использоваться: методы
SetRange или ApplyRange (и сопутствующие им методы) - в компоненте Table; секция WHERE оператора SELECT языка SQL - в компоненте TQuery. Свойство Filteredproperty Filtered: Boolean;
Свойство
Filtered, установленное в True, инициирует фильтрацию, условие которой записано или в обработчике события OnFilterRecord, или содержится как строковое значение в свойстве Filter. Если установлены разные условия фильтрации и в событии OnFilterRecord, и в свойстве Filter, выполняются оба.Например, если в НД одновременно установлены фильтры
Tablel.Filter = ' [Doljnost] = '
профессор''';procedure TFormX.TablelFilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept := DataSet['UchStepen'] = '
дтн';end;
то установка
Tablel Filtered в True приведет к двум фильтрациям; в результирующем наборе данных будут показаны только записи, у которых поле Doljnost содержит значение 'профессор' и поле UchStepen содержит значение 'дтн'.Установка
Filtered^ False приведет к отмене фильтрации, условия которой указаны в событии OnFilterRecord или (и) свойстве Filter. При этом фильтрация,наложенная на НД методом
SetRange или ApplyRange и ему сопутствующими методами, не нарушаетсяПример Пусть ТБД "Сотрудники" подвергается фильтрации
procedure TFormI.Table1FilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept := DataSet['UchStepen'] = '
доцент';end;
и по нажатию кнопки "
SetRange" показываются только записи, у которых табельный номер больше 150000procedure TFormI.SetRangeClick(Sender: TObject) ;
begin
Tablel.SetRange ( [150000],[900000]) ;
end;
Нажатие клавиши "
CancelRange" снимает фильтрацию по табельному номеру (рис 7 29 - 7 31)procedure TFormI.CancelRangeClick(Sender: TOb^ect);
begin
Tablel.CancelRange;
end;
Последовательность установки фильтров произвольна -
SetRange может применяться после Filtered = True, и наоборот Отмена одного из этих условий фильтрации не приводит к отмене другого способа фильтрации (рис 7 32, 7 33)или
Событие OnFilterRecord
property OnFilterRecord: TFilterRecordEvent;
Событие OnFilterRecord возникает, когда свойство Filtered устанавливается в Tine Обработчик события OnFilterRecord имеет два параметра имя фильтруемого набора данных и var Accept, указывающий условия фильтрации записей в НД В отфильтрованный ИД включаются только те записи, для которых параметр Accept имеет значение True
В условие фильтрации могут входить любые поля НД, в том числе не входящие в текущий индекс, а также не входящие ни в один индекс Возможность фильтрации НД по неиндексным полям, а также полям, не входящим в текущий индекс, выгодно отличает способ фильтрации с использованием события
OnFilterRecord и свойства Filteredот способов фильтрации с использованием методов Set Range, Apply Range и им сопутствующих методов (компонент TTable) Последние, как будет показано в разделе, посвященном компоненту TTable, позволяют производить фильтрацию НД только по индексным полям, входящим к тому же в состав индекса, текущего на момент фильтрации Кроме этого, второй способ часто не позволяет реализовывать сложные логические конструкции при указании условий фильтрацииОднако следует помнить о том, что при указании условий фильтрации НД в обработчике
OnFilterRecord, в нем последовательно перебираются все записи ТБД при анализе их на предмет соответствия условию фильтрации, в то время как методы SetRange, ApplyRange и им сопутствующие методы используют индексно-последовательный метод доступа, т е работают с частью записей в физической ТБД Это делает использование OnFilterRecord предпочтительным для небольших объемов записей и сильно ограничивает применение данного способа фильтрации при больших объемах данныхВсякий раз, когда приложение обрабатывает событие
OnFilterRecord, НД переводится из состояния dsBrowse в состояние dsFilter Это предотвращает модификацию НД во время фильтрации После завершения текущего вызова обработчика события OnFilterRecord, НД переводится в состояние dsBro\\seПример
Отфильтровать ТБД "Сотрудники" согласно условию "Показать всех доцентов"procedure TFormI.TablelFilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept := DataSet['Doljnost] = '
доцент';end;
Прцмер
Отфильтровать ТБД "Сотрудники' по условию 'Показать всех сотрудников с табельным номером, вводимым в Editi, и с вхождением в ФИО символов, вводимых пользователем в Edit2"procedure TFormI.TablelFilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept :- (DataSet['TabNum'] > Editi.Text))
AND (Pos(Edit2.Text,DataSet['FIO']) > 0);
end;
Методы
FindFirst, FindLast, FindNext, FindPrior (см. выше подраздел "Поиск записей в наборах данных") также используют свойство OnFilterRecord, когда выполняют навигацию по НД. Свойство Filterproperty Filter: string;
Свойство
Filter позволяет указать условия фильтрации. В этом случае НД будет отфильтрован, как только его свойство Filtered станет равным True. Синтаксис похож на синтаксис предложения WHERE SQL-оператора SELECT с тем исключением, что: имена переменных программы указывать нельзя, можно указывать имена полей и литералы (явно заданные значения).Можно применять операторы отношения:
< Меньше чем; > Больше чем; >= Больше или равно; <= Меньше или равно; = Равно; <> Не равно
а также использовать логические операторы
AND, NOT и OR:([Doljnost] = '
доцент') AND ([TabNum] > 300000)Строку фильтрации можно ввести во время выполнения (рис. 7.34, 7.35):
//когда проставляется галка в поле компонента
CheckBoxl (то есть//когда
CheckBoxl.Checked =True), пользователь включает//фильтрацию; когда пользователь снимает отметку (то есть когда
// CheckBoxl.Checked =True
), пользователь выключает фильтрациюprocedure TFormI.CheckBoxIClick(Sender: TObject);
begin
Table1.Filter := Editl.Text;
Table1.Filtered := CheckBoxl.Checked;
end;
Однако при этом нужно следить, чтобы введенная строка соответствовала требованиям, предъявляемым к синтаксису строки
Filter.Другим способом мог бы быть обработчик, считывающий значения фильтрации и преобразующий их к формату строки
Filter. На рис. 7.36 и 7.37 показана форма, где значения, по которым осуществляется фильтрация, вводятся в поля компонентов TEdit1. После этого приложение автоматически формирует строку условия фильтрации и заносит ее в свойство Filter. Сформированная строка условия фильтрации для наглядности показывается в форме приложения в компоненте Label3. Свойство FilterOptionsproperty FilterOptions: TFilterOptions;
TFilterOption = (foCaseInsensitive, foNoPartialCompare);
Свойство
FilterOptions позволяет установить режимы фильтрации с использованием свойства Filter. По умолчанию FilterOptions = [ ];foCaseInsensitive -
Фильтрация производится без учета разницы в высоте букв;foNoPartialCompare -
поиск производится на точное соответствие. В противном случае, при фильтре FIO = 'Ма' в отфильтрованный НД будут включены записи, у которых в поле FIO частично входит "Ма". например (если не используется опция foCaseInsensitive), 'Мануйлова' и 'Комарова'.7.8.5. Навигация в неотфильтрованном НД между записями, удовлетворяющими фильтру
Методы
FindFirst, FindLast, FindNext, FindPrior позволяют перемещаться в неотфильтрованном НД (у которого Filtered = False) между записями, удовлетворяющими условию фильтрации. Условие фильтрации задается событием OnFilterRecord или (и) свойством Filter. Действие данных методов таково: они кратковременно переводят НД в отфильтрованное состояние (Filtered = True) без визуализации этой фильтрации в TDBGrid или другом подобном компоненте, находят соответствующую запись и переводят НД в неотфильтрованное состояние (Filtered = False).Если искомая запись найдена, данные методы возвращают
True, в противном случае - False. Аналогичный результат возвращает свойство Found.Более подробно указанные методы рассмотрены в подразделе "Поиск записей в наборах данных" (см. п.7.7).
7.9. Получение информации о полях
7.9.1. Использование компонента
TFieldDefsКомпонент
TFieldDefs содержит информацию о полях, объявленных в составе ТБД, ассоциированной с данным НД.Для НД типа
TTable компонент TFieldDefs содержит информацию обо всех полях, объявленных в структуре ТБД, ассоциированной с данным TTable. He следует путать это свойство с свойством Fields, которое содержит информацию о всех компонентах типа TField, объявленных для данного набора данных с использованием редактора полей.Для НД типа
TQuery компонент TFieldDefs содержит информацию обо всех полях, объявленных в качестве возвращаемых полей в операторе SELECT. Подробнее см. замечание, помещенное в конце данного подраздела. Свойство набора данныхproperty FieldDefs: TFieldDefs;
в качестве результата возвращает указатель на объект типа
TFieldDefs данного НД и, таким образом, с его помощью можно использовать свойства и методыкомпонента
TFieldDefs для определения числа и характеристик полей, объявленных в таблице базы данных, ассоциированной с данным НД. Рассмотрим свойства и методы компонента TFieldDefs.Свойства компонента
TFieldDefs• property Count: Integer;
Возвращает количество компонентов типа TIndexDef, каждый из которых содержит информацию о конкретном поле в составе ТБД.• property Items[Index: Integer]: TFieldDef;
Данное свойство является набором объектов типа TFieldDef, каждый из которых содержит информацию о конкретном поле, объявленном в составе ТБД. Доступ к конкретному объекту осуществляется через указание Items[Index], где Index лежит в диапазоне Q..Count-\.Объект типа
TFiezldDef обладает следующими свойствами:• property DataType: TFieldType;
Свойство DataType возвращает тип поля как значение из перечислимого типа TFieldType:TFieldType = (ftString, ftSmallint, ftlnteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftParadoxOle, ftDBaseOle, ftTypedBinary);
• property FieldNo: Integer;
Свойство FieldNo возвращает физический номер поля, использующийся Borland Database Engine (BDE) для доступа к полю.• property Name: string;
Свойство Name возвращает физическое имя поля в ТБД.• property Required: Boolean;
Данное свойство возвращает True, если поле требует обязательного заполнения каким-либо значением, и False в противном случае.• property Size: Integer;
Данное свойство возвращает размер поля. Он важен для полей типов: ftString, ftBCD, ftBytes, ftVarBytes, ftBlob, ftMemo or ftGraphic. Для полей всех остальных типов размер поля определяется его типом. Для полей BCD возвращается число знаков после десятичной точки.Пример. Считать сведения о полях ТБД, ассоциированной с
Table 1, и поместить в ListBox1 информацию об имени каждого поля, его типе, размере и значении его свойства Required (результат работы приводимого кода на рис.7.38):var i : Integer;
TipPolja : String; //
тип поляTmp : String
; // результат форматированияTmpReq : String
[3];//рабочая переменнаяbegin
ListBoxl.Clear;
Tablel.FieldDefs.Update;
// считываем поля и заносим в
ListBoxl сведения об//их имени, типе, размере и свойстве
RequiredFOR i :=
О ТО Tablel.FieldDefs.Count - 1 do beginCASE Tablel.FieldDefs.Items[i].DataType OF
ftString TipPolja = 'String';
ftSmallint TipPolja = 'Smallint' ;
ftlnteger TipPolja == 'Integer';
ftWord TipPolja = 'Word';
ftBoolean TipPolja = 'Boolean';
ftFloat TipPolja = 'Float';
ftCurrency TipPolja = 'Currency' ;
ftBCD TipPol;a = 'BCD';
ftDate TipPolja = 'Date';
ftTime TipPolja = 'Time';
ftDateTime TipPolja = 'DateTime';
ftBytes TipPolja = 'Bytes' ;
ftVarBytes TipPolja = 'VarBytes' ;
ftAutoInc TipPolja = 'AutoInc';
ftBlob TipPolja = 'Blob';
ftMemo TipPolja = 'Memo';
ftGraphic TipPolja = 'Graphic';
ftFmtMemo TipPolja = 'FmtMemo' ;
ftParadoxOle TipPolja = 'ParadoxOle';
ftDBaseOle TipPolja = 'DBaseOle';
ftTypedBinary TipPolja = 'TypedBinary';
END;
IF Tablel.FieldDefs.Items[i].Required THEN
TmpReq := 'Req'
ELSE
TmpReq := ' - ';
Tmp := Format('%-16s %-10s %-5s %4d', [Tablel.FieldDefs.Items[i].Name, TipPol]a, TmpReq, Tablel.FieldDefs.Items[i].Size]) ;
ListBoxl.Items.Add(Tmp);
END;
Методы компонента
TFieldDefsprocedure Add(const Name: string; DataType: TFieldType; Size: Word; Required: Boolean);
Метод
Add добавляет в список Items новый элемент- поле.Параметр
Name определяет имя нового поля.Параметр
DataType определяет тип поля как одно из значений перечислимого типа TFieldType (см. выше описание свойства TTable. TFieldDefs.Items [Index].DataType).Параметр
Si:e указывает размер поля или 0 в случае, когда размер поля явно определяется его типом (например, ftlnteger определяет целочисленное поле длиной в слово).Параметр
Required определяет, должно ли поле в обязательном порядке содержать какое-либо значение, или не должно.procedure Clear;
Метод Clear очищает FieldDefs. Это необходимо, например, при создании новой ТБД (см. метод TTable. CreateTable.function Find(const Name: string): TFieldDef; Метод Find ищет поле по его имени, определяемому параметром Name. В случае успеха метод возвращает указатель на объект TIndexDefs. Items.
• function Index0f(const Name: string): Integer; Метод Index Of ищет поле по его имени и возвращает индекс объекта TIndexDefs списке Items. У найденного элемента значение свойства Name совпадает с параметром Name метода.
procedure Update;
Метод Update обновляет содержимое свойства TFieldDef текущей информацией о полях в составе ТБД. Он также позволяет заносить в TFieldDef информацию о полях неоткрытого НД.ЗАМЕЧАНИЕ. Для НД типа
TQuery в свойстве FieldDefs будет содержаться информация только о тех полях, которые представлены в списке после оператора SELECT:SELECT P.NN, P.DatePrih, P.Tovar
FROM prihod P
WHERE ...
Тогда свойство
FieldDefs будет содержать информацию только о полях NN, DatePrih, Tovar таблицы Prihod.Это правило действует и для случая двух и более таблиц БД, участвующих в запросе:
SELECT P.NN, P.DatePrih, P.Tovar, P.Kolvo, K.KursUSD
FROM prihod P, KursUSD
КWHERE K.Datel= P.DatePrih
В случае использования в качестве полей результирующего запроса агрегированных функций типа
SUM, а также результатов выражений, имя вычисляемого или агрегированного поля берется по его псевдониму, следующему после ключевого слова as:SELECT P.NN, P.DatePrih, P.Tovar, P.Kolvo, K.KursUSD, P.KolVo * K.KursUSD as Zena
FROM prihod P, KursUSD К
WHERE K.Datel= P.DatePrih
В данном случае вычисляемое поле
P.KolVo * K.KursUSD имеет в результирующем НД имя ' Zena '.Если псеводним вычисляемому или агрегированному полю не присвоен, его имя берется как копия арифметического выражения, по которому значение этого поля вычисляется. Например:
SELECT P.NN, P.DatePrih, P.Tovar, P.Kolvo, K.KursUSD,
P.KolVo * K.KursUSD
FROM prihod P, KursUSD
КWHERE K.Datel= P.DatePrih
Имя вычисляемого по выражению
P.Kolvo * K.KursUSD поля будет P.KolVo * K.KursUSD '.FieldCount и Fields
property FieldCount: Integer;
Свойство
FieldCount возвращает число компонентов TField, определенных в редакторе полей для данного НД. В общем случае число TField не равно числу полей, физически объявленных в ТБД, ассоциированной с данным TTable, поскольку из объявленных полей в качестве TField могут быть добавлены не все, плюс к тому могут быть объявлены, например, вычисляемые поля.В том случае, если с использованием редактора полей ни одно поле из физически объявленных в структуре ТБД не было добавлено в форму в качестве
TField, FieldCount будет возвращать значение, совпадающее с числом физически объявленных в ТБД полей.Для компонента
TQuery всегда будет возвращаться число полей, объявленных в качестве полей возвращаемого результирующего НД в операторе SELECT:SELECT F.polel, F.pole2, S.pole99, ...
FROM first F, second S, ...
При этом не важно, сколько имеется ТБД-источников для SQL-запроса и к какому из них принадлежат возвращаемые поля, поскольку они считаются принадлежащими набору данных, возвращаемому в результате выполнения оператора
SELECT, и ни с чем иным в данном контексте не ассоциируются.property Fields[Index: Integer]: TField;
Свойство Fields есть набор компонентов TField, определенных для НД. К отдельному полю можно обратиться, указав Fields /Index J, где Index лежит в диапазоне O..Count-1.Пример.
Пусть в редакторе полей для Table 1 определена группа полей (компонентов TField). Необходимо записать в ListBox2 имена физических полей и для каждого из них - имя компонента TField, ассоциированного с полем:ListBox2.Clear;
FOR i := 0
ТО Table1.FieldCount - 1 doListBox2.Items.Add(Format('%-16s %-10s', [Tablel.Fields[i].FieldName, Tablel.Fields[i].Name])) ;
Как можно видеть из рис.7.39, использование свойств
FieldDefs и Fields имеет разную природу: если первое выдает информацию о физически объявленных полях в ТБД, второе выдает сведения о логических полях НД, т.е. полях, добавленных в коллекцию объектов типа TField для этого НД:Рис 7.39 Информация о компонентах TField
Заметим, что поле
VychislPole является вычисляемым и физически в структуре ТБД отсутствует. Свойства DefaultFields, CacheBlobs, метод ClearFieldsСвойство
property DefaultFields Boolean; содержит True, если для НД используются динамически создаваемые поля (из структуры записи таблицы БД), и False, если для НД определены постоянные поля (компоненты TField) в редакторе полей.procedure ClearFields;
Метод очищает содержимое полей текущей записи набора данных. Если НД не находится в режиме вставки новой записи или редактирования, возбуждается исключение. В случае успешного выполнения вызывается обработчик события OnDataChange для компонента TDataSource, связанного с НД.property CacheBlobs: Boolean;
Определяет, выделяется ли в памяти буфер для хранения содержимого blob-поля текущей записи НД. Если свойство установлено в
True (значение по умолчанию), буфер выделяется, если False - нет. Буфер необходим, если содержимое blob-поля (например, мемо-поля) показывается в форме для текущей записи НД и должно быстро обновляться при переходе на новую запись.7.9.4. Способы обращения к полям набора данных
function FieldByName(const FieldName: string): TField;
Позволяет обращаться к полю, объявленному в структуре таблицы (
TTable), или к полю, перечисленному в числе прочих в структуре набора данных, возвращаемого оператором SELECT (компонент TQuery). Например:Table1.FieldByName('FIO').Value := '
Иванов И.И.';Аналогичным
целям служит свойство property FieIdValues[const FieldName: string]: Variant;Оно позволяет обращаться к полю по его имени
FieldName, например:Table1.FieldValues['FIO'] := '
Иванов И.И.';НД данных по умолчанию, его имя при обращении к полю можно опускать
Table1['FIO
'] := 'Иванов И.И.';property Fields(Index: Integer]: TField;
Позволяет обращаться к полю НД по его индексу Например:Table1.Fields[2].Value := '
Иванов И.И.';К компоненту TField, созданному с помощью редактора полей, можно также обращаться через его имя. По умолчанию оно формируется из имени набора данных и названия поля:
Table1FIO.Value := '
Иванов И.И.';7.10. Блокировка таблиц в многопользовательском режиме
При работе с таблицами локальных СУБД (
Paradox dBase) в многопользовательском режиме может возникнуть ситуация, когда на период внесения изменений в какую-либо таблицу БД одним пользователем следует блокировать внесение изменений в таблицу со стороны других пользователей. Более жестким ограничением может служить запрет на просмотр содержимого таблицы со стороны других пользователей.Запрет внесения изменений или просмотра другими пользователями НД, ассоциированного с данной таблицей, достигается с помощью метода
procedure LockTable(LockType: TLockType); где параметр LockType определяет вид запрета:ItReadLock -
запретить чтение;ItWriteLock -
запретить записьМожно запретить и чтение, и запись данных, но для этого нужно вызвать метод
LockTable дважды.Попытка внесения изменений в НД, ассоциированный с таблицей БД, для которой запрещена запись данных, равно как и попытка чтения из запрещенной для чтения таблицы , приводят к возбуждению исключения
EDBEngmeError с сообщением 'Table is locked .'.После того, как необходимость запрета пропадет, он должен быть отменен, что осуществляется методом
procedure UnlockTable(LockType: TLockType); где параметр LockType указывает тип снимаемого запрета. Пример. Если таблица БД запрещена для записи в нее данных, в приложениях других пользователей исключение возбуждается при попытке перевести НД, ассоциированный с таблицей, в режим dsEdit (методом Edit), или удалить запись (метод Delete}. Будем анализировать успешность выполнения метода Edit в обработчике события возникновения ошибки при попытке перевести НД в режим редактирования Для этого можно написать такой обработчик события OnEditError.procedure TForm1. Table1EditError(DataSet: TDataSet; E: EDatabaseError;
var Action: TDataAction);
begin
ShowMessage
('Таблица блокирована на запись другим пользователем') ;Action := daAbort;
end;
ЗАМЕЧАНИЕ.
В архитектуре "клиент-сервер" не возникает потребности в реализации программных блокировок. Возможность одного пользователя вносить изменения в данные, уже корректируемые другим пользователем, определяется уровнем изоляции транзакций в приложении (свойство Translsolation компонента TDatabase) и уровнем изоляции транзакций сервера, а также механизмом блокировок сервера. Обычно блокировка накладывается на запись, измененную в рамках незавершенной транзакции. Однако, например, SQL-сервер Borland InterBase не блокирует чтение из записей, которые изменяются другим пользователем в рамках еще не завершенной транзакции В этом случае тот пользователь, чье приложение читает записи, видит записи в их последнем подтвержденном состоянии.7.11. Синхронизация содержимого наборов данных
Записи НД (компонент
TTable, TQuery) размещаются в локальной копии на компьютере, на котором выполняется приложение Обновление локальной копии данных происходит в случае выполнения из данного приложения операции модификации НД (добавления, изменения или удаления записи, т е выполнения методов Post или Delete) Однако возникают случаи, когда содержимое локальной копии данных на конкретном компьютере (то есть, попросту говоря, содержимое НД) должно быть обновлено из физической таблицы БД (или синхронизировано с физической таблицей БД) Это достигается путем выполнения метода procedure Refresh;При этом может встретиться два основных способа применения этого метода. Они рассматриваются ниже.
7.11.1. Синхронизация содержимого наборов данных в одном приложении
Как известно, в приложении может существовать несколько НД, ассоциированных с одной и той же таблицей БД. Например, это может быть компонент
Table1, расположенный в модуле данных (Data Module) приложения, и компонент Table2, расположенный в форме и выполняющий там специфические функции, отличные от функций компонента Table 1. Тогда, если эти компоненты активны во время выполнения приложения, нужно обновлять содержимое одного набора данных в случае обновления другого.Если, например, изменяется
TDataModulel. Table 1, то для синхронизации изменения с содержимым TForm1 .Table2 следует написать такие обработчики событий:procedure TDataModulel.TablelAfterDelete(DataSet: TDataSet);
begin
TFormI.Table2.Refresh;
end;
procedure TDataModulel.Table1AfterPost(DataSet: TDataSet);
begin
TFormI.Table2.Refresh; end;
7.11.2. Синхронизация содержимого наборов данных в разных приложениях
При многопользовательском режиме доступа к БД из нескольких копий одного и того же приложения бывает необходимо обеспечить такую функциональность, чтобы каждый пользователь видел подтвержденные изменения, внесенные другими пользователями.
В клиентских приложениях, работающих в рамках архитектуры "клиент-сервер", подобная функциональность обеспечивается автоматически при уровне изоляции транзакций
(Read Committed) и при условии использования компонента TTable. Компонент TQuery, выполнив запрос на чтение к удаленной БД, показывает записи НД в неизменном виде, независимо от того, изменялось ли после запроса содержимое таблицы БД. Для обновления информации в НД, реализуемом при помощи TQuery, приходится закрывать и повторно открывать компонент TQuery, т.е. повторно выполнять запрос к удаленной БД. При уровне изоляции транзакций Repeatable read пользователь в рамках транзакции видит данные только в том состоянии, в котором они находились на момент старта транзакции.В приложениях, работающих в архитектуре "файл-сервер" (с использованием таблиц локальных СУБД
Paradox, dBase), не происходит обновления в наборе данных (находящегося в режиме dsBrowse) в приложении одного пользователя, после внесения изменения в эту таблицу БД другим пользователем в своемприложении. Отображение изменений производится лишь после выполнения данным пользователем метода
Post или Delete. Если НД находится в состоянии dsBrowse, его обновление можно реализовать периодически, например, с помощью таймера:procedure TFormI.TimerlTimer(Sender: TObject) ;
begin
IF Table1.State = dsBrowse THEN Table1.Refresh; end;
или, если нужно обновлять все НД приложения:
procedure TFormI.TimerlTimer(Sender: TObject);
var DSCnt : Integer;
i : Integer; begin
WITH Session.Databases [0] do begin
DSCnt := DataSetCount
; // Получаем количество открытых НДFOR i
:= О ТО DSCnt - 1 do // Обновляем каждый из нихIF DataSets[i].State = dsBrowse THEN
DataSets[i].Refresh;
END;//with
end;
Если нужно обновлять лишь некоторые из наборов данных, указатели на них можно поместить в список
TStringsListl. Этот список можно создать и наполнить в момент создания формы (но не в момент ее активизации, т.к. форма может активизироваться много раз - после минимизации или после вызова другого приложения). При разрушении формы список удаляется:var
DSList : TStringList;
procedure TFormI.FormCreate(Sender: TObject);
begin
DSList := TStringList.Create; //
Создаем списокDSList.AddOb]ect(' '/Tablel); //
Помешаем в него ссылку на TablelDSList. Add0b;ect (' \Table2); //
и Table2Timeri.Enabled := True; //
Включаем таймерend;
procedure TFormI.TimerlTimer(Sender: TObject) ;
var i : Integer;
begin
WITH DSList do begin
FOR i := 0 TO Count - 1 do
IF (Objects [i] as TDataSet).State = dsBrowse THEN (Objects[i] as TDataSet).Refresh;
END;//with
end;
procedure TFormI.FormDestroy(Sender: TObject);
begin
DSList.Free;
end;
7.12. Обработка ошибок смены состоянии набора данных
В случае неудачи при выполнении методов
Insert, Edit, Delete и Post обработку ошибки можно реализовать в соответствующих обработчиках событий OnEditError (ошибки при выполнении Insert и Edit}, OnDeleteError (ошибки при выполнении Delete} и OnPostError (ошибки при выполнении Post):property OnEditError: TDataSetErrorEven;
property OnDeleteError: TDataSetErrorEven;
property OnPostError: TDataSetErrorEven;
где
TDataSetErrorEvent = procedure(DataSet: TDataSet; E: EDatabaseError;
var Action: TDataAction) of object;
TDataAction = (daFail, daAbort, daRetry);
назначение параметров:
DataSel -
указатель на компонент, в котором произошла ошибка;Е - ссылка на объект-исключение;
Action -
действие:daFail -
выполнение метода, вызвавшего ошибку, отменяется, выводится сообщение об ошибке;daAbort -
выполнение метода, вызвавшего ошибку, отменяется, сообщение об ошибке не выводится;daRetry -
метод, вызвавший ошибку, после выхода из обработчика выполняется заново; при этом в теле обработчика должна быть скорректирована причина ошибки, иначе произойдет зацикливание программы.Пример. Пусть необходимо выдать программное сообщение пользователю и отменить выполнение ошибочного метода, если возникает ошибка при выполнении метода
Insert или Edit (например, таблица заблокирована другим пользователем). Тогда можно использовать такой обработчик события OnEditError:procedure TFormI.TablelEditError(DataSet: TDataSet; E:
EDatabaseError;
var Action: TDataAction);
begin
ShowMessage
('Таблица Сотрудников заблокирована другим ' + 'пользователем') ;Action := daAbort;
end;
7.13. Ограничения на значения полей
Набор данных имеет свойство property Constraints: TCheckConstraints; которое представляет собой коллекцию компонентов TCheckConstraints. Каждый такой компонент определяет ограничение, накладываемое на значение одного или более полей. Число ограничений, созданных для НД, определяется свойством коллекции Constraints property Count: Integer; Доступ к отдельному ограничению с индексом Index осуществляется при помощи свойства property Items|Index: Integer): TCheckConstraint;При этом значение
Index должно находиться в диапазоне 0..Count - 1. На рис. 7.40 показан список ограничений, определенных для НД, как он выглядит при обращении к свойству Constraints набора данных в инспекторе объектов.Каждое ограничение имеет тип
TCheckConstraint. Рассмотрим свойства этого компонента.property CustomConstraint: string;
Содержит текст ограничения на значение поля (полей) в SQL-подобном синтаксисе, например
:Table1.Constraints. .Items[i].CustomConstraint := 'Razrjad > 7 and Razr]ad < 15';
property ErrorMessage string;
Содержит текст сообщения об ошибке. Это сообщение выводится, если пользователь предпримет попытку запомнить запись, поля которой не удовлетворяют данному ограничению (рис. 7.41).property FromDictionary: Boolean;
Указывает источник формирования ограничения - словарь данных (значение True) или непосредственно приложение (False).property ImportedConstraint string;
Используется для запоминания SQL-текста ограничения, импортированного из SQL-сервера или словаря данных.