Сегодня будет продолжен рассказ о компоненте TOraQuery.
В прошлом выпуске мы научились выполнять любые запросы. В большинстве
случаев нужно будет выбирать информацию, отвечающую каким либо
условиям. Для примера, можно выбрать только людей, фамилия которых
начинается на А: select fam, name, otch, birthday from clientphis where fam like
'А*' Если нужно найти людей с фамилией, начинающейся на Г, то можно
конечно написать такой запрос select fam, name, otch, birthday from clientphis where fam like
'Г*' Как наверное заметили, у нас получилось два одинаковых запроса,
которые отличаются только условием отбора записей. А ведь таких
условий в процессе работы программы может генерироваться несколько
сотен и для Оракла это будут уникальные запросы, что
отрицательно скажется на производительности и масштабируемости всей
системы.
В этой ситуации на помощь приходят параметрические запросы. Выше
указанные запросы можно преобразовать в параметрический так: select fam, name, otch, birthday from clientphis where fam like
:fam Обратите внимание на выражение ":fam" - эта конструкция
определяет параметр с именем fam. Имя параметру можно давать любое, а
не только по имени поля, но в определенных ситуациях действуют
некоторые правила задания имен параметров. К рассмотрению этих правил
мы вернемся в будущем.
Перед выполнением такого запроса нужно обязательно установить
значения параметров. Для этого можно использовать или коллекцию
Params, которая содержит список всех параметров запроса (нумерация
начинается с 0) или метод ParamByName, который работает с указанным по
имени параметром.
Модифицируем последний наш пример так, чтобы выбирать людей с
фамилией, начинающейся на введенные нами символы.
1. Изменим запрос у OraQuery1 на select fam, name, otch, birthday from
clientphis where upper(fam) like upper(:fam)
2. Перепишем обработчик OnClick кнопки:
procedure TForm1.Button1Click(Sender: TObject);
begin
//закрываем датасет
OraQuery1.Close;
//устанавливаем значение параметра
OraQuery1.ParamByName('fam').AsString := Edit1.Text + '*';
//или установить значение параметра можно так:
//OraQuery1.Params[0].AsString := Edit1.Text + '*';
//выполняем запрос
OraQuery1.Execute;
end;
Как видите, нет ничего сложного. Удобство параметров особенно
проявляется тогда, когда нужно работать с датами, так как при этом не
нужно приводить дату к формату, принимаемому сервером.
Если Вы дочитали до этих строк, то наверное подумали, что параметры
можно использовать не только для задания значений выражений, но и
также для определения полей и таблиц, например, было бы неплохо
выполнить такой запрос: select * from :table - для выборки данных из любой таблицы
или select * from table order by :ordfield - для задания сортировки по
любому полю.
К сожалению, непосредственно такие запросы ни один сервер не может
выполнить и сразу будет выдавать ошибку. Можно конечно динамически
формировать запрос, но ODAC имеет более красивое решение, а именно
макросы. Использовать макросы также легко, как и параметры. Для
задания макроса используется символ & (а не :) и для работы с
макросами есть метод MacroByName/Macros (сравните с
ParamByName/Params).
Рассмотрим работу с макросами на конкретном
примере:
OraQuery1.Close;
OraQuery1.SQL.Text := 'select fam, name, otch, birthday from
clientphis order by @sort'
OraQuery1.MacroByName('sort').Value := Edit1.Text;
OraQuery1.Execute;
Теперь в строке редактирования можно указать имя поля, по которому
будет идти сортировка.
Также макросы можно использовать для динамического задания условий
и т.д. без непосредственного формирования нужного sql-кода,
например:
MyQuery.SQL.Text := 'SELECT * FROM Dept WHERE DeptNo > 20 &Cond1';
MyQuery.Macros[0].Value := 'and DName is NULL';
MyQuery.Execute;
в этом случае на сервер уйдет следующий запрос:
SELECT * FROM Dept WHERE DeptNo > 20 and DName is NULL
Как могли заметить, с помощью параметров такой запрос нельзя
сконструировать. Но что делать, если нужно периодически отключать
условие 'and DName is NULL'? Можно конечно макросу присвоить пустую
строку, но ODAC содержит более красивое и элегантное решение -
достаточно у макроса сбросить свойство Active (по умолчанию оно
установлено): MyQuery.Macros[0].Active:= False;
в этом случае на сервер уйдет запрос SELECT * FROM Dept WHERE
DeptNo > 20
В использовании макросов и параметров нет ничего заумного, так что
смело берите эти возможности на вооружение. И не забывайте, что Оракл
очень любить параметрические запросы.
Настало время про связь master/detail. Что это за связь я
рассказsвать не буду, так как это выходит за рамки статьи. Просто
расcкажу, какими способами в ODAC'е ее можно организовать.
Если обратили внимание на словосочетание какими способами,
то уже догадались, что ODAC поддерживает несколько способов
организации связи master/detail. Рассмотрим их более подробно (на
примере демонстрационной базы, которая ставится вместе с Ораклом).
Обычно связь master/detail организуется для таблиц, которые связаны
на уровне СУБД отношением foreign key/primary key. Для организации связи master/detail нам понадобятся два компонента
TOraQuery и один компонент TDataSource (TOraDataSource):
var
Master, Detail: TOraQuery;
MasterSource: TDataSource;
begin
// Создаем master датасет
// Можно также воспользоваться компонентом на форме
// Как Вы помните по предыдущим выпускам, ODAC автоматически
// подключает компоненты к OraSession.
Master := TOraQuery.Create(Self);
Master.SQL.Text := 'SELECT * FROM Department';
// Создаем detail датасет
// Обратите внимание что для detail датасета мы указываем
// параметрический запрос
Detail := TOraQuery.Create(Self);
Detail.SQL.Text := 'SELECT * FROM Employee WHERE DepNo = :DepNo';
// Связываем Detail с Master с помошью MasterSource
MasterSource := TDataSource.Create(Self);
MasterSource.DataSet := Master;
// именно здесь и устанавливается связь
Detail.MasterSource := MasterSource;
// Сначала открываем основной запрос
// и только потом - дочерний
Master.Open;
Detail.Open;
end;
Примечание. С помощью дизайнера форм можно создать связь
master/detail не написав ни одной строчки кода (!!!), нужно всего лишь
"бросить" на форму соответствующие компоненты и настроить нужные
свойства. В худшем случае нужно в коде открыть в правильном порядке
эти датасеты.
Несколько важных пояснений. Имя параметра в дочернем запросе
(detail датасет) должно соответствовать имени поля в основном запросе.
Значение для параметра второго запроса автоматически берется из поля
основного запроса. При скроллинге основного датасета, дочерний запрос
автоматически будет выполнятся с нужным параметром, поэтому если
дочерний запрос будет "тяжелым", то будет наблюдаться некотороя
"заторможенность" интерфейса. При добавлении новой записи в дочернию
таблицу (запрос) в поле с foreign key автоматически вставляется значение
primary key из текущей записи основной таблицы (запроса).
ODAC поддерживает еще один способ организации master/detail с
использованием свойств MasterFields/DetailsFields:
var
Master, Detail: TOraQuery;
MasterSource: TDataSource;
begin
// Создаем master датасет
Master := TOraQuery.Create(Self);
Master.SQL.Text := 'SELECT * FROM Department';
// Создаем detail датасет
// Сравните запрос с первым способом
Detail := TOraQuery.Create(Self);
Detail.SQL.Text := 'SELECT * FROM Employee';
// Устанавливаем связь
Detail.MasterFields := 'DepNo'; // primary key в основной таблице Department
Detail.DetailFields := 'DepNo'; // foreign key в дочерней таблице Employee
// Указываем основоную таблицу
MasterSource := TDataSource.Create(Self);
MasterSource.DataSet := Master;
Detail.MasterSource := MasterSource;
// Сначала открываем основной запрос
// и только потом - дочерний
Master.Open;
Detail.Open;
end;
Во втором случае имена полей, по которым устанавливается связь,
указываются не в дочернем запросе, а в специальных свойствах. Если Вы
внимательно читали предыдущий выпуск, то могли бы подумать что
дочерний запрос будет возвращать все записи, а не только те, что нам
нужны. Однако это не так. Во время выполнения программы ODAC
самостоятельно изменит запрос на SELECT * FROM Employee WHERE DepNo =
:DepNo и будет работать как в первом случае.
Какой вариант организации связи master/detail использовать -
решать Вам. Я в силу привычки использую первый способ. В следующем
выпуске я продолжу рассказ про OraQuery.