Загрузка...

Лабораторная работа 4. Оператор выбора Select. Компонент Query.


Теоретическая часть. Использование оператора SELECT
1.1. Отбор записей из таблицы

Откройте новое приложение C++Builder, перенесите на форму компонент Query со страницы библиотеки Data Access и установите его свойство DatabaseName равным dbP — базе данных Paradox, с которой мы имели дело. В некоторых случаях полезно изменить этот псевдоним на ib — аналогичную базу данных InterBase. 
Поместите на форму компонент DataSource и в его свойстве DataSet задайте Queryl. Поместите также на форму компонент DBGrid и в его свойстве DataSource задайте DataSourcel.
Теперь ваше текстовое приложение для экспериментов с языком SQL готово. Операторы SQL вы можете писать в свойстве SQL компонента Queryl, а чтобы увидеть результаты выполнения написанного оператора, вам надо будет устанавливать значение свойства Active компонента Queryl в true. Это надо будет делать после записи каждого нового оператора.
Теперь начнем рассмотрение оператора Select. Одна из форм этого оператора имеет синтаксис:

SELECT <список имен полей> FROM <таблица>
WHERE <условие отбора> ORDER BY <список имен полей>;

Элементы оператора WHERE и ORDER BY не являются обязательными. Элемент WHERE определяет условие отбора записей: отбираются только те, в которых условие выполняется. Элемент ORDER BY определяет упорядочивание возвращаемых записей.
<таблица> — это не компонент Table, а именно та таблица базы данных, из которой осуществляется отбор, например, Pers.
Начнем подробное рассмотрение данного оператора со списка полей после ключевого слова Select, содержащего имена тех полей таблицы, которые будут возвращены. Имена разделяются запятыми. Например, оператор

SELECT Fam, Nam, Par, Year_b FROM Pers

указывает, что следует вернуть поля Fam, Nam, Par и Year_b из таблицы Pers. 3апишите его в свойстве SQL компонента Queryl, установите значение свойства Active компонента Queryl в true и посмотрите результаты.
Если указать вместо списка полей символ «*» — это будет означать, что требуется вернуть все поля. Например, оператор

SELECT * FROM Pers

означает выбор всех полей.
В списке могут быть не только сами поля, но и любые выражения от них с арифметическими операциями +, -, *, /. После выражения может записываться псевдоним выражения в форме: AS <псевдоним>. В качестве псевдонима может фигурировать любой идентификатор, на который потом можно будет при необходимости ссылаться. Указанный псевдоним будет при отображении результатов фигурировать в заголовке таблицы.
Такими простыми средствами создаются аналоги вычисляемых полей, которые при использовании компонента Table требовали гораздо больших усилий и написания обработчика события OnCalcFiclds. Впрочем, в обработчике можно было написать сколь угодно сложную программу вычисления, что в данном случае затруднительно. Правда, как мы увидим позже, компонент Query, работающий в CHBuilder с SQL, позволяет создавать вычисляемые поля и с помощью обработчика события OnCalcFields.
Приведем пример использования выражения:

SELECT Fam, Nam, (2000-Year_b) AS Age FROM Pers

Этот оператор создает поле Age, вычисляемое по формуле (2000-Year_b).
Теперь рассмотрим форму представления условия отбора, задаваемого после ключевого слова WHERE. Это условие определяет критерий, по которому отбираются записи. Оператор Select отбирает только те записи, в которых заданное условие истинно. Условие может включать имена полей (кроме вычисляемых), константы, логические выражения, содержащие арифметические операции, логические операции and, or, not и операции отношения:
равно
> больше
>= больше или равно
< меньше
<= меньше или равно
!= не равно
Like наличие заданной последовательности символов
between … and диапазон значений
in соответствие элементу множества
Первые шесть операций очевидны. Например, оператор

SELECT Fam FROM Pers WHERE Sex=false and Year_b > I960

отберет записи, относящиеся к женщинам, родившимся после 1960 года. Операция Like имеет синтаксис:
<поле> LIKE ‘<последовательность символов>’
Эта операция применима к полям типа строк и возвращает true, если в строке встретился фрагмент, заданный в операции как <последовательность символов>. Заданным символам может предшествовать и их может завершать символ процента «% », который означает — любое количество любых символов. Если символ процента не указан, то заданная последовательность символов должна соответствовать только целому слову. Например, условие

Fam LIKE ‘A%’

означает, что будут отобраны все записи, начинающиеся с заглавной русской буквы «А» (операция Like различает строчные и прописные символы). Условию

Fam LIKE ‘Иванов%’

будут удовлетворять фамилии «Иванов» и «Иванова», а условию
Fam LIKE ‘ %ван%’

кроме этих фамилий будет удовлетворять, например, фамилия «Иванников».

Операция between … and имеет синтаксис:
<поле> between <значение> and <значение>

и задает для указанного поля диапазон отбираемых значений. Например, оператор

SELECT Fam, Year_D FROM Pers WHERE Year_D BETWEEN 1960 AND 1970

отберет записи сотрудников в заданном диапазоне возраста (включая граничные значения 1960 и 1970).
Операция In имеет синтаксис:

<поле> in (<множество>)

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

SELECT Fam, Year_b FROM Pers WHERE Fam IN ( ‘Иванов ‘,’ Петров ‘, ‘Сидоров )

отберет записи сотрудников с заданными фамилиями, а оператор

SELECT Fam, Year_b FROM pers WHERE Year _ b IN (1950,1960)

отберет записи сотрудников указанных годов рождения.
Элемент оператора Select, начинающийся с ключевых слов ORDER BY, определяет упорядочивание (сортировку) записей. После этих ключевых слов следует список полей, определяющих сортировку. Можно указывать только поля, фигурирующие в списке отобранных (в списке после ключевого слова SELECT). Причем эти поля могут быть и вычисляемыми.
Если в списке сортировки указано только одно поле, то сортировка производится по умолчанию в порядке нарастания значений этого поля. Например, оператор

SELECT Dep, Fam, Year_b FROM Pers ORDER BY Year_D

задает упорядочивание возвращаемых значений по нарастанию года рождения. Если желательно располагать результаты по убыванию значений, то после имени поля добавляется ключевое слово DESC:

SELECT Dep, Fam, Year_b FROM Pers ORDER BY Year_b DESC

Если в списке после ORDER BY перечисляется несколько полей, то первое из них — главное и сортировка проводится прежде всего по значениям этого поля. Записи, имеющие одинаковое значение первого поля упорядочиваются по значениям второго поля и т.д. Например, оператор

SELECT Dep, Fam, Year_b FROM Pers ORDER BY Dep, Fam

сортирует записи прежде всего по отделам (значениям поля Dep), а внутри каждо го отдела — по алфавиту. Оператор

SELECT Dep, Fam, Year_D, Sex FROM Pers ORDER BY Dep, Sex, Fam

сортирует записи по отделам, полу и алфавиту.

1.2. Совокупные характеристики

Оператор Select позволяет возвращать не только множество значений полей, но и некоторые совокупные (агрегированные) характеристики, подсчитанные по всем или по указанным записям таблицы. Одна из функций, возвращающих такие совокупные характеристики, соunt(<условие>) — количество записей в таблице, удовлетворяющих заданным условиям. Например, оператор

SELECT count(*) FROM Pers

подсчитает полное количество записей в таблице Pers. А оператор

SELECT count (*) FROM Pers WHERE Dер=’Цех’

выдаст число записей сотрудников цеха 1.
Оператор, использующий ключевое слово DISTINCT (уникальный), выдаст число неповторяющихся значений в указанном поле. Например, оператор

SELECT count (DISTINCT Dep) FROM Pers

вернет число различных подразделений, упомянутых в поле Dep таблицы Pers.
Функции min(<поле>), max(<поле>), avg(<пoлe>), sum(<поле>) возвращают соответственно минимальное, максимальное, среднее и суммарное значения указанного поля. Например, оператор

SELECT min (Year_ b) , max(Year_b), avg(Year_b) FROM Pers

вернет минимальное, максимальное и среднее значение года рождения, а оператор

SELECT min (2000-Year_b), max (2000-Year_b), avg (2000-Year_b)
FROM Pers WHERE Dep=’Бухгалтерия’

выдаст вам аналогичные данные, но относящиеся к возрасту сотрудников бухгалтерии..
В операторе Select вы можете указывать не только суммарные характеристи-
ки, но и любые выражения от них. Например, оператор

SELECT 1999 — (min(Year_b) + max(Year-b)) /2 FROM Pers
WHERE Dep=’Бухгалтерия’

выдаст моду (среднее между максимальным и минимальным значениями) возраста сотрудников бухгалтерии. Здесь надо обратить внимание на то, что оператор вернет округленное до целого значение моды, поскольку в выражении использованы целые числа и поэтому осуществляется целочисленное деление. Если же вы же операторе замените делитель «2» на «2.», т.е. укажете его как действительное значение, то и результат будет представлен действительным числом. При использовании суммарных характеристик надо учитывать, что в списке возвращаемых значений после ключевого слова SELECT могут фигурировать или поля (в том числе вычисляемые), или совокупные характеристики, но не могут фигурировать и те, и другие. Это очевидно, так как оператор может возвращать или множество значений полей записей, или суммарные характеристики по таблице, но не может извращать эти несовместимые друг с другом данные. Поэтому нельзя, например, записать оператор

SELECT Fam, max(Year_b) FROM Pers

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

SELECT Dep, count (*) FROM Pers GROUP BY Dep

вернет таблицу, в которой будет 2 столбца: столбец с названиями отделов, и столбец, в котором будет отображено число сотрудников в каждом отделе.
При группировании записей с помощью GROUP BY можно вводить условия отбора записей с помощью ключевого слова HAVING. Например, если переписать уведенный выше оператор следующим образом:

SELECT Dep, count(*) FROM Pers GROUP BY Dep HAVING Dep <> ‘Бухгалтерия’

таблице будут строки, относящиеся ко всем отделам, кроме бухгалтерии.
Впрочем, не все базы данных поддерживают синтаксис HAVING. При использовании базы данных dbP (Paradox) компонент Query не работает с HAVING. Для базы данных InterBase никаких проблем с HAVING не возникает.

1.3. Вложенные запросы

Результаты, возвращаемые оператором Select, можно использовать в другом операторе Select. Причем это относится и к операторам, возвращающим совокупные характеристики, и к операторам, возвращающим множество значений. Например, в предыдущем разделе нам не удалось узнать фамилию самого молодого сотрудника. Теперь это можно сделать с помощью вложенных запросов:

SELECT Fam, Year_b FROM Pers
WHERE Year_b = (SELECT max(Year_b) FROM Pers)

В этом операторе второй вложенный оператор SELECT max(Year_b) FROM Pers возвращает максимальный год рождения, который используется в элементе WHERE основного оператора Select для поиска сотрудника (или сотрудники), чей год рождения совпадает с максимальным.
Вложенные запросы могут обращаться к разным таблицам. Пусть, например, мы имеем две аналогичных по структуре таблицы Pers и Pers1, относящиеся к paзным организациям, и хотим в таблице Pers найти всех однофамильцев сотрудников другой организации. Чтобы проверить работу с несколькими таблицами, вы можете открыть в Database Desktop свою таблицу Pers, выполнить команду Table | Restructure, ничего не меняя в структуре, щелкнуть на кнопке Save as и сохранить ту же таблицу в том же каталоге, где была исходная таблица Pers, но под новым именем Persl. Если хотите, можно в ней внести записи, отличные от таблицы Pers, чтобы эти таблицы чем-то различались. Впрочем, можно все это и не делать, заменив в приведенных ниже примерах таблицу Persl таблицей Pers, т.е. работая одной таблицей. Смысл операторов все равно будет понятен.
Итак, вернемся к задаче определения всех однофамильцев в этих двух таблицах. Это можно сделать оператором

SELECT * FROM Pers WHERE Fam IN (SELECT Fam FROM Persl)

Вложенный оператор Select Fam From Persl возвращает множество из таблицы Persl, а конструкция WHERE основного оператора Select отбирает из фамилий в таблице Pers те, которые имеются в множестве фамилий из Persl.
При работе в условии WHERE с множествами записей можно использовать ключевые слова: All и Any. All означает, что условие выполняется для всех сей, a Any — хотя бы для одной записи. Например, оператор

SELECT * FROM Pers WHERE Year_b >= ALL (SELECT Year_b FROM Persl)

ищет сотрудников в Pers, которые не старше любого сотрудника в Persl. Кстати, если в этом операторе заменить Persl на Pers, то получим список самых молодых сотрудников организации, который мы получали ранее другим способом. А оператор

SELECT * FROM Pers WHERE Year_b > ANY (SELECT Year_b FROM Persl)

ищет сотрудников в Pers, которые моложе хотя бы одного сотрудника в Persl.

1.4. Объединения таблиц

В запросе можно объединить данные двух или более таблиц. Пусть, например, вы хотите получить список сотрудников всех производственных подразделений, В таблице Pers мы имеем список сотрудников с указанием в поле Dep подразделения, в которых они работают. А в таблице Dep мы имеем список всех подразделений поле Dep и характеристику каждого подразделения в поле Prosv (true, если подразделение производственное). Тогда получить список сотрудников всех производственных подразделений можно оператором:

SELECT Pers.* FROM Pers, Dep
WHERE (Pers.Dep = Dep.Dep) AND (Dep.Proisv = true)

В нем мы обращаемся сразу к двум таблицам Pers и Dep, которые перечислены УКключевого слова FROM. Поэтому каждое имя поля предваряется ссылкой на таблицу, к которой оно относится. Впрочем, это надо делать только для полей, имя которых повторяется в разных таблицах (поле Dep). Перед полем Proisv ссылку на таблицу можно опустить. В конструкции WHERE условие Pers.Dep=Dep.Dep ищет запись в таблице Dep, в которой поле Dep совпадает с полем Dep текущей записи таблицы Pers. А условие Dep.Proisv=true отбирает те записи, в которых в таблице Dep найденному подразделению соответствует поле Proisv = true.
В операторах, работающих с несколькими таблицами, обычно каждой таблице и псевдоним, сокращающий ссылки на таблицы, а иногда придающий им некоторый смысл, вытекающий из данного применения. Псевдоним таблицы может записываться в списке таблиц после слова FROM, отделяясь от имени таблицы пробелом, мер, приведенный выше оператор может быть переписан следующим образом:

SELECT P.* FROM Pers P, Dep D
WHERE (P.Dep = D.Dep) AND (D.Proisv = true)

В этом примере таблице Pers дан псевдоним Р, а таблице Dep — D. Конечно, эти псевдонимы действуют только в данном операторе и не имеют никакого отношения к псевдонимам баз данных, которые мы постоянно используем.
Возможно самообъединение таблицы. В этом случае одной таблице даются два псевдонима. Пусть, например, мы хотим найти всех ровесников в организации. Это можно сделать оператором

SELECT P1.Fam, P2.Fam, Pl.Year_b FROM Pers P1, Pers P2
WHERE (Pl.Year_b = P2.Year_b) AND (P1.Fam != P2.Fam)

В этом примере для таблицы Pers мы ввели два псевдонима: P1 и P2. В конструкции WHERE мы ищем в этих якобы разных таблицах записи с одинаковым годом рождения. Второе условие P1.Fam != P2.Fam нужно, чтобы сотрудник не отображался в результатах как ровесник сам себя. Правда, приведенный оператор выдает в результате по две записи на каждую пару ровесников, сначала, например, «Николаев — Андреев», а потом «Андреев — Николаев». Чтобы исключить такое дублирование можно добавить еще одно условие — Pl.Fam < P2.Fam:

SELECT Pl.Fam, P2 . Fam, Pl.Year_b FROM Pers P1, Pers P2
WHERE (Pl.Year__b = P2.Year_b) AND (Pl.Fam != P2.Fam)
AND (Pl.Fam < P2.Fam)

Дополнительное условие упорядочивает появление фамилий в P1 и P2 и исключает дублирование результатов.
До сих пор мы рассматривали объединения, основанные на однозначном соответствии записей двух таблиц, когда каждой записи в первой таблице находилась соответствующая ей запись во второй таблице. Возможны и другие виды объединения, которые выдают записи независимо от того, есть ли соответствующее поле во второй таблице. Это внешние объединения (outer join). Их три типа: левое, правое и полное. Левое объединение (обозначается ключевыми словами LEFT OUTER JOIN … ON) включает в результат все записи первой таблицы, даже те, для которых не имеется соответствия во второй. Правое объединение (обозначается ключевыми словами RIGHT OUTER JOIN … ON) включает в результат все записи второй таблицы, даже если им нет соответствия в записях первой. Полное объединение (обозначается ключевыми словами FULL OUTER JOIN … ON) включает в результат объединение записей обеих таблиц, независимо от их соответствия.
Пусть, например, у вас есть таблица сотрудников некоей компании Pers и есть таблица Chef, в которой занесены данные на членов совета директоров этой компании, В число членов совета входят и сотрудники компании, и посторонние лица. Для определенности положим, что в таблице Pers имеются записи на сотрудников
«Иванов» и «Петров», причем Петров является членом совета, а Иванов- нет. В таблице Chef имеются записи на членов совета «Петров» и «Сидоров», при Сидоров — не сотрудник компании. Тогда оператор

SELECT * FRОМ Pers LEFT OUTER JOIN Chef ON Pers.Fam = Chef..Fam

выдаст результат вида:

Поля таблицы Pers

Поля таблицы Chef

Иванов …………….
Петров
…………….
Петров
………………

Оператор задал левое объединение таблицы Pers (она указана после ключевого слова FROM) с таблицей Chef (она указана после ключевых слов LEFT OUTER JOIN). Условие объединения указано после ключевого слова ON и заключается в совпадении фамилий.
Как показано, результат включает все поля и таблицы Pers, и таблицы Сhef. Число строк соответствует числу записей таблицы Pers. В строках, относящихся к записям, для которых в Chef не нашлось соответствие, поля таблицы Chef остаются пустые.
Оператор правого объединения

SELECT * FROM Pers RIGHT OUTER JOIN Chef ON Pers.Fam = Chef.Fams

выдаст результат вида:

Поля таблицы Pers

Поля таблицы Chef

Петров …………….. Петров ……………

Сидовов
…………….

Число строк соответствует числу записей таблицы Chef. В строках, относящихся к записям, для которых в Pers не нашлось соответствие, поля таблицы Pers остаются пустые.
Оператор полного объединения

SELECT * FROM Pers FULL OUTER JOIN Chef ON Pers.Fam = Chef.Fam

выдаст результат вида:

Поля таблицы Pers

Поля таблицы Chef

Иванов ……………….
Петров ……………….. Петров …………….
Сидоров …..………..

В нем к строкам, относящимся к таблице Pers, добавлены строки, относят к таблице Chef, для которых не нашлось соответствия в таблице Pers.

2. Компонент Query

Мы рассмотрели основные операторы SQL. Теперь посмотрим, как этот язык
можно использовать в приложениях. Для этого существует компонент набора данных класса TQuery. Он имеет большинство свойств и методов, совпадающих с
Table, и компонент Query может во многих случаях включаться в приложения
вместо Table. Дополнительные преимущества Query — возможность формировать
запросы на языке SQL.
Рассмотрим коротко сравнительные характеристики Table и Query. При работе с локальными базами данных чаще используется Table. С его помощью проще
не только просматривать таблицу базы данных, но и модифицировать записи, удалять их, вставлять новые записи. Однако, при работе с серверными базами данных
компонент Table становится мало эффективным. В этом случае он создает на компьютере пользователя временную копию серверной базы данных и работает с этой
копией. Естественно, что подобная процедура требует больших ресурсов и существенно загружает сеть.
Этот недостаток отсутствует в компоненте Query. Если запрос SQL сводится к просмотру таблицы (запрос Select), то результаты этого запроса (а не сама исходная таблица) помещается во временном файле на компьютере пользователя. Правда, в отличие от набора данных, создаваемого Table, это таблица только для чтения и не допускает каких-то изменений. Впрочем, это ограничение можно обойти, но в дальнейшем будет показано, как это можно делать. Если же запрос SQL связан с какими-то изменениями содержания таблицы, то никаких временных таблиц не создается. BDE передает запрос на сервер, там он обрабатывается и в приложение возвращается информация о том, успешно ли завершена соответствующая операция. Благодаря такой организации работы эффективность Query при работе в сети становится много выше, чем эффективность Table. К тому же язык SQL позволяет формулировать сложные запросы, которые не всегда можно реализовать в Table. С другой стороны при работе с локальными базами данных эффективность Query заметно ниже эффективности Table. Замедление вычислений получается весьма ощутимым.
Исходя из этого краткого обзора возможностей Table и Query, можно заключить, что в серверных приложениях обычно целесообразнее использовать компонент Query, а при работе с локальными базами данных — компонент Table. Чтобы ознакомиться с Query, откройте в C++ Builder новое приложение и поместите на форму компоненты Query, DataSource, DBGrid. В свойстве DataSet компонента DataSourcel задайте Query1, а в свойстве DataSource компонента IDBGridl задайте DataSourcel. Таким образом, мы создали обычную цепочку: набор данных (Queryl), источник данных (DataSourcel), компонент визуализации и управления данными (DBGridl). А теперь займемся интересующим нас компонентом Query.
Основное свойство компонента Query — SQL, имеющее тип TStrings. Это список строк, содержащих запросы SQL. В процессе проектирования приложения обычно необходимо, как будет показано ниже, сформировать в этом свойстве некоторый предварительный запрос SQL, который показал бы, с какой таблицей или таблицами будет проводиться работа. Но далее во время выполнения приложения свойство SQL может формироваться программно методами, обычными для класса TStrings: Clear — очистка, Add — добавление строки и т.д.
Настройку компонента Query в процессе проектирования можно производить вручную, как это делается со всеми компонентами, или с помощью специального Визуального Построителя Запросов. Его вызов производится щелчком правой кнопки мыши на компоненте Query и выбором из всплывающего меню раздела SQL Builder. Ручная настройка предпочтительнее.
Прежде, чем приступать к ручной настройке, в свойстве DataBaseName компонента Query надо задать, как это делается и для компонентов Table, базу данных, с которой будет осуществляться связь. База данных задается выбором из выпадающего списка псевдонимов, или указанием полного пути к каталогу или файлу (в зависимости от используемой СУБД).
Предупреждение:
Не устанавливайте свойство DataSource — это свойство имеет отношение к приложениям с несколькими связанными таблицами и в других случаях не устанавливается.
Свойства TableName, которое было в компоненте Table, в Query нет, т.к. таблица, с которой ведется работа, будет указываться в запросах SQL. Поэтому прежде всего надо занести в свойство SQL запрос, содержащий имя таблицы, с которой вы хотите работать.
Предупреждение:
Прежде, чем начинать детальную настройку компонента Query, надо сформировать в его свойстве SQL запрос, в котором указывается таблица и перечисляются параметры, если они используются в приложении. Пока такой запрос в SQL отсутствует, дальнейшая настройка Query невозможна. Запрос, заносимый в SQL вначале проектирования, носит чисто служебный характер. В дальнейшем вы можете его программно заменить на любой другой запрос.
Запрос, заносимый вами в SQL в начале проектирования, может иметь, например, следующий вид:

Select * From Pers

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

Select * From Pers, Dep

После того, как соответствующий запрос написан, можете установить свойство Active компонента Query в true. Если все выполнено правильно, то вы увидите в компоненте DBGridl информацию из запрошенных таблиц.
Можете запустить свое приложение на выполнение и посмотреть его в работе. Оно предоставляет вам возможность просматривать записи, но, к сожалению, не позволяет их редактировать. Это связано с тем, что запрос Select возвращает таблицу только для чтения. Впрочем, в таком простом приложении, как наше, это легко исправить. Достаточно установить в компоненте Query1 свойство RequestLive в true. Это позволяет возвращать как результат запроса изменяемый, “живой” набор данных, вместо таблицы только для чтения. Точнее, установка RequestLive в true делает попытку вернуть “живой” набор данных. Успешной эта попытка будет только при соблюдении ряда условий, в частности:
• набор данных формируется обращением только к одной таблице;
• набор данных не упорядочен (в запросе не используется ORDER BY);
• в наборе данных не используются совокупные характеристики типа Sum, Count и др.;
• набор данных не кэшируется (свойство CashedUpdates равно false).
В нашем примере все эти условия соблюдаются. Так что можете установить RequetLive в true и пользователь сможет редактировать данные, удалять записи, вставлять новые записи.
Ваше приложение можно усовершенствовать так же, как вы делали это раньше с приложением на основе Table. Для управления отображением данных, как и в компоненте Table, имеется уже известный вам Редактор Полей (Field Editor). Вызвать его можно или двойным щелчком на Query, или щелчком правой кнопки мыши на Query и выбором Fields Editor из всплывающего меню. С Редактором Полей вы уже знакомы. В нем вы можете добавить имена получаемых полей (щелчок правой кнопкой мыши и выбор раздела меню Add), задать заголовки полей, отличающиеся от их имен, сделать какие-то поля невидимыми (Visible), не редактируемыми (Read only), в логических полях можете задать высвечиваемые слова (да; нет), задать формат высвечивания чисел, создать вычисляемые поля, поля просмотра, задать диапазоны значений и многое другое.

Динамические запросы и параметры Query

Все запросы SQL, которые мы до сих пор рассматривали — это так называемые статические запросы. В них фиксировано все: имена таблиц, поля, константы в выражениях и т.п. Но помимо таких статических запросов SQL допускает и динамические запросы, использующие параметры. Причем параметры можно применять вместо имен таблиц, имен полей и их значений. Значения этих параметров передаются извне и тем самым, не изменяя текст самого запроса, можно менять возвращаемый им результат.
Параметры задаются в запросе с двоеточием, предшествующим имени параметра:
:<имя параметра>
Например, вы можете записать в запросе Select элемент WHERE в виде:

WHERE Year_b <= :PYear

В этом случае вы сравниваете год рождения не с какой-то константой, а со значением параметра, который вы назвали PYear.
Теперь обратимся к компоненту Query. Если вы введете в его свойство SQL запрос, содержащий параметры, например:

Select * From Pers Where (Year_b>:Pyear) AND (Dep=:Dep)

а потом щелкнете в Инспекторе Объектов на свойстве Params, вам откроется диалоговое окно со списком объектов — указанных вами в запросе параметров PYear и Dep (см. рис. 1.1). В этом списке вы можете выделять по очереди параметры и в Инспекторе Объектов устанавливать их свойства. Это свойства:
DataType тип данных параметра (int, string и т.п.)
Name имя параметра
ParamType тип параметра (используется при обращении к процедурам, хранимым на сервере)
Value значение параметра по умолчанию
Туре — подсвойство Value тип значения по умолчанию

Рис. 1.1. Окно задания атрибутов параметров

После того, как вы установили свойства всех параметров, можете использовать их при программировании приложения.
Программный доступ к параметрам во время выполнения приложения осуществляется аналогично доступу к полям набора данных. Свойство Params является указателем на массив параметров типа ТРагаm, к элементам которого можно обращаться по индексу через его свойство Items[Word Index]. Последовательность, в которой располагаются параметры в массиве, определяется последовательностью их упоминания в запросе SQL.
Значения параметров, как и значения полей, определяются такими свойствами объектов — параметров, как Value, AsString, AsInteger и т.п. Например, оператор

for (int i=0; i < Queryl->Params->Count; i++)
if (Queryl->Params->Items[I]->IsNull &&
Queryl->Params->Items[I]->DataType == ftInteger)
Queryl->Params->Items[I]->AsInteger = -1;

задаст значение “-1” всем целым параметрам, которым до этого не было присвоено значение. В нем использовано свойство Count — число параметров, свойство Is Null, равное true, если параметру не задано никакое значение, свойство DataType, указывающее тип параметра, и свойство AsInteger, дающее доступ к значению параметра как к целому числу. Другой пример: операторы

Queryl->Pairams->Items[0]->AsInteger = 1950;
Queryl->Params->Items[1]->AsString = «Бухгалтерия»;

задают значения первому (индекс 0) и второму (индекс 1) параметрам компонента Query1, в свойстве SQL которого записан приведенный ранее оператор Select с параметрами :PYear и :Dep. Поскольку в этом операторе параметр :PYear упоминается первым, то его индекс равен 0.
Кроме свойства Items у Params есть еще одно свойство — ParamValues. Оно представляет собой массив значений параметров типа Variant. В качестве индекса в это свойство передается имя параметра или несколько имен, разделяемых точками с запятой. Например, операторы

Queryl->Params->ParatnValues[«PYear»] = 1950;
Queryl->Params->ParamValues[«Dep»] = «Бухгалтерия»;

задают те же значения параметрам, что и приведенные выше. Преимуществом является то, что при записи этих операторов не надо помнить индексы параметров. Другим преимуществом свойства ParamValues является возможность задать значения сразу нескольким параметрам. Например:

Variant par[] = {1950,»Бухгалтерия»);
Queryl->Params->ParamValues[«PYear;Dep»] = VarArrayOf(par,1);

Другой способ обращения к параметрам, при котором не надо помнить их индексы — использование метода .ParamByName компонента Query. Например, операторы

Queryl->ParamByName(«PYear»)->AsInteger = 1950;
Queryl->ParamByName(«Dep»)->AsString = «Бухгалтерия»;

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

2.1. Основные свойства Query, связывание таблиц

Большинство свойств Query аналогичны свойствам Table, рассмотренным ранее. Объекты полей создаются автоматически для тех полей, которые перечислены в операторе SQL. Программный доступ к этим полям осуществляется так же, как в Table, с помощью свойства Fields (например, Queryl->Fields[0]) или методом FieldByName (например, Queryl->FieldByName(«Dep»)). Можно также создавать объекты полей с помощью Редактора Полей, вызываемого двойным щелчком на Query или из меню, всплывающего при щелчке на Query правой кнопкой мыши. В этом случае доступ к объекту поля можно осуществлять также и по его имени (например, QuerylDep).
Предупреждение
При использовании для Query Редактора Полей надо добавлять в нем все поля, перечисленные в операторе SQL. Иначе поля, не добавленные в Редакторе Полей, не будут доступны. Доступ к полям по имени возможен только в случае, если объекты полей были созданы с помощью Редактора Полей.
Для доступа к значениям полей используются те же их свойства Value, AsString, AsInteger и т.п., что и в Table. Точно так же, как в Table, можно осуществлять навигацию по набору данных, устанавливать фильтры, ограничивать вводимые значения полей, кэшировать изменения.
Из свойств, отличных от Table, остановимся на свойстве DataSource. Это свойство позволяет строить приложения, содержащие связанные друг с другом таблицы. Рассмотрим, как это делается. Построим приложение , включающее в себя таблицу Dep, содержащую список отделов и их характеристику, в качестве головной таблицы и таблицу персонала Pers, содержащую в поле Dep имя отдела, в котором работает каждый сотрудник. При выборе записи в таблице Dep в таблице Pers отбираются только записи, относящиеся к данному отделу.
Откройте новое приложение. Перенесите на форму компоненты Query1, DataSourcel, DBGridl и соедините их обычной цепочкой: в DBGrid1 задайте свойство DataSource равным DataSourcel, а в DataSourcel задайте свойство DataSet равным Queryl. Компонент Queryl настройте на таблицу Dep. Для этого установите свойство DatabaseName (например, dbP), а в свойстве SQL напишите оператор

Select * From Dep

Установите свойство Active в true и убедитесь, что все работает нормально: в DBGridl должно отобразиться содержимое таблицы Dep.
Создайте другую аналогичную цепочку, перенеся на форму компоненты Query2, DataSourcel, DBGrid2, и свяжите ее с таблицей Pers запросом

Select * From Pers

в компоненте Query2. Установите свойство Active компонента Query2 в true и в DBGrid2 должно отобразиться содержимое таблицы Pers.
Вы можете запустить приложение и убедиться, что оно работает, но таблицы независимы. Теперь давайте свяжем эти таблицы. Делается это следующим образом. Измените текст запроса в свойстве SQL вспомогательного компонента набора данных Query2 на

Select * From Pers Where (Dep=:Dep)

В этом запросе вы указываете условие отбора: значение поля Dep должно быть равно параметру :Dep. В предыдущем разделе было рассказано, как вводить в запрос и использовать параметры. Но в данном случае не надо определять этот параметр с помощью редактора параметров, вызываемого из свойства Params компонента Query2. Вместо этого в свойстве DataSource компонента Query2 надо сослаться на DataSource1 — источник данных, связанный с таблицей Dep. Это скажет приложению, что оно должно взять значения параметра :Dep из текущей записи этого источника данных. А поскольку имя параметра совпадает с именем поля в источнике данных, то в качестве значения параметра будет взято текущее значение этого поля. Таким образом, вспомогательная таблица, запрашиваемая в Query2, оказывается связанной с головной таблицей, запрашиваемой в Queryl.
После изменения содержимого свойства SQL свойство Active компонента Query2 сбросится в false. Установите его в true и запустите приложение. Вы увидите, что при перемещении по первой таблице во второй отображаются только те записи, которые относятся к отделу, указанному в текущей записи первой таблицы.

2.2. Основные методы компонента Query

К основным методам Query можно отнести методы открытия и закрытия соединения с базой данных.
Метод Close закрывает соединение с базой данных, переводя свойство Active в false. Этот метод надо выполнять перед изменением каких-то свойств, влияющих на выполнение запроса или на отображение данных. Например, при изменении параметров запроса в свойстве SQL надо сначала методом Close закрыть соединение, связанное с прежним запросом, а потом уже выполнять новый запрос.
Метод Open открывает соединение с базой данных и выполняет запрос, содержащийся в свойстве SQL. Но этот метод применим только в том случае, если запрос сводится к оператору Select. Если же запрос содержит другой оператор, например, Update или Insert, то при выполнении Open будет генерироваться исключение EDatabaseError. Для осуществления любого другого запроса, кроме Select, используется метод ExecSQL. Он подготавливает выполнение данного запроса, если подготовка не осуществлена заранее, и затем выполняет его. Подготовка выполнения требует определенных затрат времени. Поэтому для ускорения взаимодействия с базой данных полезно перед первым выполнением запроса выполнить метод Prepare.
При изменении текста в свойстве SQL методы Close и Prepare вызываются автоматически.
Наличие двух методов выполнения запроса — Open и ExecSQL ставит вопрос: “Как же поступить, если запрос сформирован пользователем или программой и его характер неизвестен?”. Неизвестный запрос, сформированный в некоторой переменной ssql типа строки, можно выполнить с помощью следующего кода:

try
{
Queryl->SQL->Clear ();
Queryl->SQL->Add(ssql);
Queryl->0pen () ;
}
catch (EDatabaseError&)
{
Queryl->ExecSQL () ; ‘
}

В блоке try осуществляется попытка выполнить с помощью метода Open. А если эта попытка завершается генерацией исключения, т.е. если запрос состоит из оператора, отличного от Select, то генерируется исключение EDatabaseError, которое перехватывается в разделе catch, и выполняется метод ExecSQL.
В некоторых случаях приложению требуется получить список имен полей таблицы, связанной с Query. Это может быть сделано методом GetFieldNames, который загружает список имен полей в любую переменную типа TStrings, передаваемую в него в качестве аргумента. Например, оператор

Queryl->GetFieldNames(ComboBoxl->Items);

загружает в выпадающий список ComboBoxl имена полей таблицы, связанной с Queryl.

2.3. Кэширование изменений, совместное применение Query и UpdateSQL

Метод ExecSQL осуществляет немедленную модификацию таблицы. Однако нередко удобнее было бы кэшировать изменения (хранить их временно в памяти), а после того, как все изменения и проверки сделаны, переслать их в базу данных или, по решению пользователя, отменить все сделанные исправления.
Это делается так же, как и для компонента Table: устанавливается в true свойство CachedUpdates компонента Query и применяются методы ApplyUpdates для записи изменений в базу данных, метод CancelUpdates для отмены изменений и метод CommitUpdates для очистки буфера кэша.
Но режим кэширования позволяет сделать большее — он позволяет подключить в приложение компонент UpdateSQL. Этот компонент, расположенный на странице библиотеки Data Access, позволяет модифицировать наборы данных, открытые в режиме только для чтения. Это особенно важно для наборов данных, открываемых Query с запросом Select, поскольку Select создает таблицу только для чтения.
Давайте построим приложение, демонстрирующее режим кэширования. Возьмите то же самое приложение (рис. 9.47), только замените в нем компонент Table1 на компонент Queryl и везде в тексте тоже проведите соответствующую замену Tablel на Queryl. В свойстве SQL компонента Queryl запишите “Select * from Pers” и установите свойство CachedUpdates в true.
Запустите свое приложение, и вы увидите, что оно, увы, не работает. Отредактировать запись или вставить новую запись невозможно. А из кнопок навигатора доступны только кнопки навигации. Причина всего этого была указана ранее — Query с запросом Select создает таблицу только для чтения.
Давайте исправим ваше приложение. Поместите на него компонент UpdateSQL со страницы библиотеки Data Access. Чтобы связать его с приложением, установите в компоненте Queryl свойство UpdateObject равным имени введенного компонента UpdateSQLl. Это имя вы можете выбрать из выпадающего списка в свойстве UpdateObject.
Теперь можно задавать свойства компонента UpdateSQLl. Этих свойств, кроме Name и Tag, всего 3: DeleteSQL, InsertSQL и ModifySQL. Они содержат соответственно запросы, которые должны выполняться при удалении, вставке или модификации записи. Эти запросы можно записать обычным образом, вручную. А можно воспользоваться редактором UpdateSQL, который вызывается двойным щелчком на UpdateSQL или при выделенном UpdateSQL вызывается из всплывающего меню. Окно этого редактора имеет вид, представленный на рис. 1.2. Первая страница Options редактора UpdateSQL, представленная на рисунке, содержит два окна Key Fields и Update Fields.

Рис. 1.2. Окно редактора UpdateSQL

В левом окне Key Fields надо выделить поля, по которым программа будет распознавать модифицируемую или удаляемую запись. Можно выделить все поля, что гарантирует максимально возможную надежность распознавания, но можно ограничиться выбором нескольких наиболее важных полей. При работах с очень большими таблицами это сэкономит время выполнения запроса. Для выбора совокупности полей можно нажать клавишу Ctrl и, не отпуская ее, выбрать курсором мыши нужные поля.
В правом окне Update Fields надо выделить поля, значения которых будут задаваться при модификации или вставке записи. Обычно целесообразно выделить в этом окне все поля, хотя, конечно, могут быть случаи, когда не все поля надо задавать. В частности, нельзя задавать вычисляемые поля, если они имеются в таблице.
После того, как вы выделили в этих окнах поля, нажмите кнопку Generate SQL. После этого вам будет показана вторая страница SQL редактора UpdateSQL, на которой вы можете просмотреть сгенерированные запросы для модификации (Modify), вставки (Insert) и удаления (Delete) записи. После щелчка на ОК эти запросы перенесутся в свойства DeleteSQL, InsertSQL и Modify SQL, где вы их можете дополнительно отредактировать.
При всех выделенных полях запрос ModifySQL будет иметь вид:

Update Pers
Set
Num = :Num,
Dep = :Dep,
Fam = :Fam,
Nam = :Nam,
Par = :Par,
Year_b = :Year_b,
Sex = :Sex,
Charact = :Charact,
Photo — :Photo
Where
Num = :Old_Num And
Dep = :OLD_Dep and
Fara = :OLD_Fam and
Nam = :OLD_Nam and
Par = :OLD_Par and
Year_b = :OLD_Year_b and
Sex = :OLD_Sex and
Charact = :OLD_Charact and
Photo = :OLD_Photo

В нем в разделе Set указана установка всех полей в значения, задаваемые соответствующими параметрами с именами, тождественными именам полей. В этот раздел включаются те поля, которые вы выделили в окне Update Fields редактора UpdateSQL. Заполнение этих параметров при выполнении соответствующих команд приложения вам не потребуется: все это сделают автоматически методы компонента UpdateSQL. В разделе Where содержатся условия, по которым идентифицируется модифицируемая запись. В этих условиях используются параметры с именами, тождественными именам полей, но с префиксом OLD_. Эти параметры — прежние значения соответствующих полей, которые были получены компонентом до модификации записи. В условия Where включены те поля, которые вы выделили в окне Key Fields редактора UpdateSQL.
Естественно, что в зависимости от приложения вы можете заменить эти автоматически сгенерированные имена параметров на другие или вообще использовать
запросы без параметров. Только имейте в виду, что значения автоматически сгенерированных параметров в дальнейшем будут автоматически загружаться из объектов-полей, а если вы от них откажетесь, то и соответствующие значения должны будете задавать сами.
Предупреждение
Если вы измените имена параметров в запросах SQL компонента UpdateSQL, вам придется программно задавать значения этих параметров. Если же вы оставите автоматически сгенерированные имена, то в дальнейшем значения параметров будут автоматически загружаться из объектов-полей. Так что изменять имена параметров неразумно.
Для того, чтобы запросы компонента UpdateSQL срабатывали, все объекты полей, имена которых в них используются, должны быть или автоматически сгенерированы (т.е. без применения в компоненте Query Редактора Полей), или введены в Редакторе Полей. В противном случае при попытке выполнить запрос вам будет выдано сообщение: “Field … is of an unknown type” (поле … неизвестного типа) и запрос не выполнится.
Предупреждение
В запросах компонента UpdateSQL должны фигурировать только те поля, которые введены вами в Редакторе Полей компонента Query, или вы не должны пользоваться в Query Редактором Полей.
Посмотрим на приведенный выше сгенерированный запрос ModifySQL с точки зрения нашего приложения. Прежде всего очевидно, что из условия запроса where надо удалить поля Charact и Photo, так как сравнение полей такого типа бессмысленно. Вообще для нашей таблицы достаточно оставить сравнение только по полю Num, поскольку это поле обеспечивает уникальность каждой записи. Кроме того из раздела set надо исключить поле Num, так как оно типа Autoincrement, а значения полей этого типа устанавливать нельзя: они автоматически нарастают с каждой новой записью. Имеет также смысл исключить поля Charact и Photo, так как они в нашем приложении не отображаются. Таким образом, запрос ModifySQL имеет смысл оставить в виде:

Update Pers
Set
Dep = :Dep,
Fam = :Fam,
Nam = :Nam,
Par = :Par,
Year_B = :Year_B,
Sex = :Sex
Where
Num = :OLD_Num

Запрос в свойстве DeleteSQL строится по такому же принципу. Его можно записать в виде
Delete From Pers
Where
Num = :OLD_Num

Запрос InsertSQL, построенный при выделении всех полей в окне рис. 10.2, имеет вид:
Insert Into Pers
(Num, Dep, Fam, Nam, Par, Year_b, Sex, Charact, Photo)
Values
(:Num, :Dep, :Fam, :Nam, :Par, :Year_b, :Sex, :Charact, : Photo)

Его, очевидно, тоже надо изменить, убрав задание поля Num, поскольку оно увеличивается автоматически и не может изменяться приложением. Имеет смысл убрать также поля Charact и Photo, так как они не фигурируют в нашем приложении. В итоге запрос приобретет следующий вид:

Insert into Pers
(Dep, Fam, Nam, Par, Year_b, Sex)
Values
(:Dep, :Fam, :Nam, :Par, :Year_b, :Sex)

Осталось несколько изменить по сравнению с приложением на основе Table обработчик события OnClick кнопки Фиксация. Он должен иметь вид:

Queryl->ApplyUpdates ();
Queryl->ComfnitUpdates () ;
Queryl->Close (); Queryl->0pen();
modif = false;

По сравнению с тем, что было раньше, в него добавлены операторы закрывания (Close) и открывания (Open) соединения с базой данных компонента Query1. Это желательно сдедать, чтобы в Queryl отобразилось новое соcтояние базы данных после внесенных в нее изменений. Если не предусмотреть этого, то, например, будет невозможно вставить в сеансе работы новую запись, подтвердить изменение, а затем удалить эту запись. Дело в том, что если мы не обновили данные в Queryl, то в этих данных отсутствует вставленная в данном сеансе работы запись. И, следовательно, ее не удастся удалить (можете проверить это в эксперименте).
Запустите теперь приложение, и вы увидите, что оно стало работать так же, как работало ранее с компонентом Table. Можно редактировать записи, удалять, вставлять новые записи, управлять кэшированием.
В данном случае все получилось так просто, поскольку соответствующие методы работы с UpdateSQL автоматически выполняются компонентами DBNavigator и DBGrid. В некоторых других приложениях эти методы надо вызывать явно. Поэтому коротко ознакомимся с ними.

Метод

Apply(Db::TUpdateKind UpdateKind)

обеспечивает загрузку значений всех необходимых параметров и выполнение одного из запросов компонента UpdateSQL. Какого именно — определяется параметром UpdateKind, который может принимать значения ukModify, uklnsert или ukDelete, что соответствует выполнению запроса, хранящегося в свойствах ModifySQL, InsertSQL и DeleteSQL.
Например, оператор

UpdateSQLl->Apply(ukModifу)

вызовет выполнение запроса, содержащегося в свойстве ModifySQL.
Если нужный запрос SQL не содержит параметров, то эффективнее вызвать такой метод компонента UpdateSQL, как ExecSQL:

ExecSQL(Db: :TUpdateKind UpdateKind)

который тоже обеспечивает выполнение указанного запроса, но без предварительной загрузки значений параметров. Наконец, метод

SetParams(Db::TUpdateKind UpdateKind)

обеспечивает загрузку значений параметров указанного запроса SQL без его выполнения. Таким образом, метод Apply — это просто последовательное выполнение методов SetParams и ExecSQL.

2.4. Пример формирования произвольных запросов SQL

Вы можете попробовать использовать компонент Query вместо Table в любом из приложений. Целесообразность замены Query на Table, как было сказано, зависит от того, работаете ли вы с локальными базами данных, или с удаленным сервером. Но есть приложения, в которых пользователю разрешается формировать любые запросы к базе данных. В таких приложениях без языка SQL и, соответственно, без компонентов Query не обойтись. Рассмотрим на чисто демонстрационном примере, как строить подобные приложения.
Общий вид приложения, которое мы с вами попробуем построить, приведен на рис. 1.3.

Рис. 1.3. Пример приложения, позволяющего пользователю формировать произвольные запросы к базе данных

Приложение позволяет пользователю сформировать различные запросы Select к таблице Pers. Выпадающий список Поле (типа TComboBox, в программе назван CBFilds) заполнен именами полей, которые пользователь может выбирать из него при формировании запроса. Выпадающий список Операции, знаки (типа TComboBox, в программе назван СВОр) заполнен символами допустимых операций, функций и знаков, которые пользователь также может выбирать при формировании запроса. Окно, расположенное ниже кнопок, является компонентом типа ТМеmо (в программе названо MSQL). Это окно служит для отображения формируемого запроса SQL. Ниже расположена таблица DBGridl типа TDBGrid, которая через компонент DataSource связана с компонентом Query типа TQuery. Этот компонент и выполняет сформированные пользователем запросы.
Кнопка Новый запрос начинает формирование запроса, посылая в MSQL текст “Select ”. Затем пользователь может вводить имена полей или непосредственно в текст, или, чтобы не ошибиться, выбирая их имена из списка Поле. При вводе совокупных характеристик или вычисляемых полей пользователь может брать необходимые символы и ключевые слова из списка Операции, знаки. При желании пользователь может щелкнуть на кнопке Условие (в запрос вводится элемент WHERE), на кнопке Порядок (в запрос вводится элемент ORDER BY) или кнопке Группировка (в запрос вводится элемент GROUP BY) и сформировать условия отбора, сортировки, группирования. После того, как запрос сформирован, пользователь выполняет его щелчком на кнопке Выполнить и в окне отображаются результаты запроса.

Ниже приведен полный текст данного приложения.
// Перечислимый тип, определяющий режим работы
// в каждый момент времени

enum TRegim {RNone, RFields, RWhere, ROrder, REnd) Regim;
//——————————————————————
void_fastcall TForml::ADDS(String s)
{
// Добавление в конец последней строки в Memo новой строки s
MSQL->Lines->Strings[MSQL->Lines->Count-l] =
MSQL->Lines->Strings[MSQL->Lines->Count-l] + s;
}
//——————————————————————
void_fastcall TForml::CBFieldsChange(TObject *Sender)
{
if ((Regim == REnd)||(MSQL->Lines->Count < 1))
ShowMessage(
«Начните новый запрос или вводите оператор вручную»);
else ADDS(» «+CBFields->Text) ;
}
//——————————————————————
void_fastcall TForml::FormCreate(TObject *Sender)
{
// Загрузка в выпадающий список имен полей таблицы
Query->GetFieldNames(CBFields->Items);
CBFields->Items->Insert(0, «*») ;
CBFields->ItemIndex = 0;
CBOp->ltemIndex = 0;
Regim = RNone;
}
//———————————————————
void_fastcall TForml::BbeginClick(TObject *Sender)
{
MSQL->Clear();
MSQL->Lines->Add(«Select «);
Regim = RFields;
}
//——————————————————————
void_fastcall TForml::BexecClick(TObject *Sender)
{
if (Regim == RNone)
{
ShowMessage(«Вы не ввели запрос»);
return;
}
if (Regim == RFields)
ADDS(» FROM PERS»);
Regim = REnd;
Query->SQL->Assign (MSQL->Lines) ;
Query->0pen () ;
}
//——————————————————————
void_fastcall TForml::BWhereClick(TObject *Sender)
{
if (Regim == RFields)
ADDS(» FROM PERS»);
ADDS(“ WHERE»);
Regim = RWhere;
}
//——————————————————————
void _fastcall TForml::CBOpChange(TObiect *Sender)
{
if ((Regim == REnd) || (MSQL->Lines->Count < 1))
ShowMessage(«Начните новый запрос или вводите оператор вручную»);
else ADDS(» «+CBOp->Text);
}
//——————————————————————
void fastcall TForml::BOrderClick(TObject *Sender)
{
if (Regim == RFields)
ADDS(» FROM PERS»);
ADDS(» ORDER BY»);
Regim = ROrder;
//——————————————————————
void_fastcall TForml::BGroupClick(TObject *Sender)
{
if (Regim == RFields)
ADDS(» FROM PERS»);
ADDS(» GROUP BY»);
Regim = ROrder;
}

Сделаем некоторые комментарии к этому тексту. В начале вводится переменная Regim перечислимого типа:

enum TRegim {RNone, RFields, RWhere, ROrder, REnd) Regim;

возможные значения которой определяют, в каком режиме в данный момент находится приложение. В начале значение переменной Regim задается равным RNone. Обработчики щелчков всех кнопок проверяют значение переменной Regim и в зависимости от результатов этой проверки производят те или иные операции. Кроме того они изменяют значение этой переменной, сообщая приложению, какие операции выполняет пользователь. Например, обработчик щелчка кнопки Выполнить в процедуре BexecClick проверяет Regim и, если значение этой переменной оказывался RNone, это означает, что пользователь, не нажав до этого ни одной кнопки, разу щелкнул на кнопке Выполнить. В этом случае пользователю выдается замечание «Вы не ввели запрос» и обработка события прерывается.
Далее в приложении вводится процедура ADDS, которая добавляет заданную текстовую строку в конец текста, содержащегося в MSQL. Это сделано просто во избежание повтора входящего в процедуру оператора во многих местах кода. Конечно, при этом в заголовочном файле модуле добавляется описание класса соответствующее объявление этой процедуры:

void_fastcall ADDS(String s);

Процедура FormCreate является обработчиком события OnCreate формы. В этой процедуре производится загрузка списка CBFields значениями имен полей в таблице. Это очень легко делается методом GetFieldNames, который загружает список имен полей в любую переменную типа Tstrings, передаваемую в него в качестве аргумента.

Загрузка...