понедельник, 28 июля 2008 г.

Подробно о компонентах. TQuery.

В прошлом уроке мы рассмотрели копонент TTable. TQuery во многом похож на него, но во многом и различается. Причем различий больше. Так и опишем, сначала общее, затем различия.

Оба компонента имеют TDataSet среди своих предков. Этим и определяются общие черты. Так все что касается большинства свойств, всех описаных в предыдущем уроке событий, методов доступа к данным здесь также применимо.

Например, возьмем программe, которую в 4 уроке мы создали с помошью одной только мышки. Установим на форму компонент TQuery. Свойство Database установим равным DBDemos. Свойство SQL равным Select * From biolife. Если вы не знакомы с языком запросов SQL, пока пишите как видите, мы рассмотрим его подробно в ближайших уроках. Все, свойство Active в true, с этим компонентом закончили. Теперь достаточно в DataSource свойству DataSet присвоить имя нашего TQuery (Query1, если вы его не переименовали) чтобы наша программа заработала через TQuery вместо TTable. Все очень просто.

Но, конечно не для таких простых случаев предназначается компонент TQuery. Точнее не только для таких простых случаев. Он нам пригодится там, где не хватает возможностей TTable. А именно:

  1. Когда необходимо получить данные из нескольких таблиц так, как будто это одна таблица.
  2. Когда необходимо прямо во время работы программы менять источники данных, переключаться на другие таблицы.
  3. Удобно пользоваться им в тех случаях, когда отфильтровывается множество данных. При этом он фильтрует знаительно быстрее, чем TTable с использованием обработчика события OnFilterRecord.
  4. При создании клиент-серверных приложений, с ним удобнее правильно организовавать блокировки данных.
  5. Он позволяет выполнять сложные запросы, что гораздо больше чем просто выборка строк из таблицы.
  6. Он позволяет производить не только выборку данных, но и модификацию, создание и удаление таблиц, раздачу прав доступа - вся мощь SQL в ваших руках.
  7. Наконец самое главное, запрос выполняется на сервере. Сервер получает запрос, выбирает данные в соответствии с ним, и отправляет их вам. Вы врядли почувствуете разницу при создании простых небольших приложений, но она будет весьма значительна при попытке получить три строки по сети из базы в 100 000 записей. Особенно если сеть не слишком быстрая. Впрочем и при создании небольших локальных приложений разница существует.

Как и в TTable, свойства, которые видны в Object Inspector лишь вершина айсберга. Большая часть полезностей доступна программным путем. И здесь опять множество отличий. Например, перечислим способы получить данные из поля:

  1. A := Query1Field1.Value;
  2. A := Query1.FieldByName('Field1').Value;
  3. A := Query1.FieldByName('Field1').AsString; // AsInteger, AsFloat и т.д.
  4. A := Query1.FieldValues['Field1'];
  5. A := Query1.['Field1'];
  6. A := Query1.Fields.Fields[1].Value;
  7. A := Query1.Fields.Fields[1].AsString; // AsInteger, AsFloat и т.д.

Здесь есть тонкость, заключающаяся в том, что запросы часто меняются программно. Это накладывает некоторые ограничения на использование этих способов доступа к данным. Например первым пользоваться надо понимая, что если вы программно измените запрос, который выполняет выборку, то полей с такими именами в результируещем наборе может и не оказаться, или у них окажутся другие типы. В этом случае программа создаст исключительную ситуацию. При использовании способов 2-5 надо понимать, что если ваш запрос получает данные с нескольких таблиц, то в них могут оказаться поля с одинаковыми именами. А при использовании последних двух способов - что поля могут поменять порядок. Впрочем вы не подумайте, я не пугаю, я только указываю на потенциальные ошибки и призываю быть внимательными.

Так как же пользоваться этими способами? Как обычно, учитывая несколько простых правил.

  • Если вы выбрали первый способ - необходимо чтобы в вашем измененном запросе сохранились все те поля, которые и были до изменений. То есть скорее всего вы ограничитесь изменением условий выборки.
  • При выборке из нескольких таблиц, если появляются, например, 2 поля с одинаковыми именами (Field1), Delphi даст им имена Field1 и Field1_1. Т.е. поле, которое содержится в таблице, упомянутой первой сохранит свое имя, а второе - изменит.
  • Обращаться к полю по имени, на мой взгляд, не совсем удобно. Не наглядно, и через некоторое время можно забыть, какое поле подразумевалось. Такой подход имеет смысл при переборе всех полей в цикле, или когда пользователь сам выбирает таблицу, и вы не знаете имен полей, с которыми придется работать. При изменении запроса важно сохранить не только все поля, как в предыдущем случае, но и их последовательность.

Все стандартные методы перемещения по выборке (First, Last, Next, Prior) действуют и здесь. Аналогично и с поиском - Locate, Lookup, FindKey, FindNearest и пр. А вот при изменении опять отличия. Дело в том, что изменять можно данные не любого запроса. Для того, чтобы запрос позволял вносить изменения в выбранные данные, он должен удовлетворять следующим условиям:

  • Запрос должен представлять собой именно выборку, т.е. оператор select. У вас ничего не выйдет, если запрос представляет собой вызов хранимой процедуры или что-нибудь типа Create Table.
  • Запрос должен выбирать данные всего из одной таблицы.
  • Выходные данные не сортируются, т.е. в запросе нет Group by и Order by
  • В запросе не используются агрегатные функции, т.е. функции, вычисляющие значения для групп записей (sum, count, min, max, avg и др.)
  • Данные не кешируются, т.е. значение свойства CashedUpdate равно false.
  • Необходимо установить свойство RequestLive в true.
  • Следует перед попыткой изменить данные проверить свойство CanModify. Если оно равно false - у вас ничего не выйдет.
  • Могут быть и другие ограничения, все зависит от используемой вами СУБД и ваших прав доступа.

Если наш запрос - изменяемый, то прекрасно работают методы Edit, Insert, Delete, Post, Cancel - как в TTable. А если не изменяемый - выйти из положения нам поможет компонент TUpdateSQL, который мы рассмотрим на следующем уроке. Пока вам достаточно просто знать, что выход есть.

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

Это все были похожести. Теперь отличия.

Главным отличием компонента TQuery, как уже было сказано, является возможность на прямо во время выполнения программы изменять как источник данных, так и условия выборки. При этом может меняться как весь запрос, так и отдельные его параметры. Применяются оба способа, в зависимости от того, что вам надо.

Так замена параметров обычно применяется при изменении условия фильтрации. Рассмотрим подробно этот метод.

Для начала вам в самом запросе необходимо указать, что вы собираетесь использовать параметры. Для этого изменим текст запроса (свойство SQL):

select * from biolife where biolife.&amp;amp;quot;Length (cm)&amp;amp;quot;<br /> between :min and :max те переменные, которые начинаются с двоеточия и есть параметры. Теперь надо указать их тип. Для этого найдем свойство params TQuery и установим для каждого из наших параметров Value.Type равным Integer.Теперь внесем изменения в форму. Для этого нам понядобятся два TEdit и два TLabel. Как именно они нам понадобились - видно на рисунке Демонстрация работы с параметрами

Для обоих TEdit надо добавить обработчики событий OnChange со следующим содержимым:

procedure<br /> TForm1.Edit1Change(Sender: TObject); var temp, rez: integer; begin val (Edit1.Text, rez,<br /> temp); Query1.ParamByName('min').Value := rez; Query1.Open; end; procedure<br /> TForm1.Edit2Change(Sender: TObject); var temp, rez: integer; begin val (Edit2.Text, rez,<br /> temp); Query1.Active := false; Query1.ParamByName('max').Value := rez; Query1.Open; end; К параметрам, как и к полям можно обращаться по разному. Так, как написано в примере или так:Query1.Param[0].value или вот такQuery1.Param[0].AsIntegerсуть от этого не меняется.

Для большой базы данных такой не следует заново активизировать выполнение запроса при каждом изменении - это будет тормозить. Но у нас небольшая таблица, все работает достаточно быстро.

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

with tempQuery do begin Clear; Add ('select into smallFishes * from biolife'); add ('<br /> where biolife.&amp;amp;quot;Length (cm)&amp;amp;quot; &amp;lt;30'); ExecSQL; end; // with

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

В начале я удаляю старый текст запроса, если он там был. (Clear). Затем создаю новый (Add). Для Delphi не имеет значения, на сколько строк я разобью свой запрос, я мог это сделать и в одной строке. А разбил на две просто для наглядности. Когда новый запрос создан, я активизирую его.

Существуют несколько способов активизации запроса:

  1. Присвоить Active = true;
  2. Вызвать метов ExecSQL;
  3. Вызвать метод Open;

Есть и другие, связанные с вызовом Prepare для того, чтобы BDE "приготовилась" для выборки. Но о них как-нибудь в другой раз. Вызов Open Borland рекомендует для запросов выбирающих данные, а вызов ExecSQL - для запросов модифицирующих данные. Как показывает мой опыт, в большинстве случаев не имеет значения, какой из трех показанных выше способов вы выберете. Но для того, чтобы быть уверенным, что все сработает, следует все-таки следовать рекомендациям разработчика.

У TQuery есть еще много особенностей. Но их мы будем рассатривать по мере того, как будем с ними сталкиваться. Для написания большинства приложений достаточно и тех, что мы рассмотрели. В следующем уроке мы рассмотрим как можно сделать сложный запрос по нескольким таблицам и с сортировками изменяемым.

Комментариев нет: