Глава 20
Реализация SQL в Delphi
До настоящего момента мы обсуждали проблемы разработки локальных приложений БД. Теперь же обратимся к вопросам разработки программ в архитектуре клиент/сервер. В чем же состоит различие между этими типами приложений?
Было бы неверно думать, что локальные приложения БД работают только на одном компьютере и в этом состоит их главная особенность. На самом деле такая программа вполне может функционировать в локальной сети и обслуживать нескольких клиентов одновременно.
Основное же отличие между локальными и клиент/серверными приложениями заключается в четком разграничении функций приложения и их распределении по различным компьютерам. При этом говорят о серверной и клиентской частях приложения. На сервер ложится обязанность по управлению базой данных и обслуживанию многочисленных клиентских запросов, а клиент обслуживает отдельного пользователя и "умеет" связываться с сервером, отправлять запросы и получать результаты.
Таким образом, архитектура клиент/сервер обеспечивает одновременное обслуживание множества пользователей. Естественно, что применяются они на больших промышленных предприятиях, в крупных компаниях и корпорациях и т. д.
Архитектура клиент/сервер предъявляет очень высокие требования к серверной части, которая строится на основе специальных программных комплексов, которые так и называются серверами БД или серверами SQL, так как при работе с ними используется специальный язык запросов к базам данных SQL (Structured Query Language).
Язык SQL был разработан специалистами компании IBM на рубеже семидесятых годов и очень быстро приобрел широкую популярность, став промышленным стандартом де факта. Международная организация по стандартам ISO (International Standarts Organization) и Американский национальный институт стандартов ANSI (American National Standarts Institute) опубликовали ряд стандартов языка. В настоящее время самым современным является стандарт 1992 года (SQL 92).
В настоящее время распространена классификация приложений баз данных в соответствии с уровнями обработки данных. В соответствии с ней простые однопользовательские программы, работающие с маломощными локальными СУБД, являются одноуровневыми. В таких приложениях вся обработка данных ведется самой программой, а СУБД лишь обеспечивает доступ к таблицам.
Двухуровневые приложения соответствуют архитектуре клиент/сервер и организуют обработку как на стороне сервера БД, так и на стороне клиентского приложения. В зависимости от распределения функций обработки данных, различают системы клиент/сервер с "тонким" или "толстым" клиентом.
Далее следуют многоуровневые приложения (подробнее об этом см. часть V).
Созданные в Delphi приложения баз данных для взаимодействия с данными используют возможности BDE, который представляет собой еще один уровень обработки. Однако так как BDE обеспечивает выполнение сервисных операций, то при классификации приложений баз данных BDE обычно не учитывается.
Для создания эффективных приложений клиент/сервер необходимо уметь использовать разнообразные специализированные инструменты. Главное место среди них принадлежит языку запросов SQL и компоненту доступа к данным TQuery, который обеспечивает применение запросов в приложениях баз данных.
Итак, в этой главе рассматриваются следующие вопросы.
Основы SQL
Язык SQL предназначен исключительно для описания запросов к базам данных и поэтому отличается от других языков программирования высокого уровня.
Во-первых, этот язык предназначен для манипулирования данными из реляционных БД, поэтому логика языка опирается на четкую табличную структуру данных, что позволило использовать достаточно простой набор основных команд (простой объект работы — простое средство работы).
Например, простейший запрос с использованием оператора select требует от СУБД найти и вернуть приложению все записи из таблицы COUTRY.DB:
SELECT * FROM Country
При этом все необходимые для выполнения запроса операции должна выполнить СУБД, а на долю приложения остается только определение самого запроса и отображение результата.
Во-вторых, в языке SQL реализована трехзначная логика, то есть помимо значений True и False логические переменные могут принимать значение unknown, которое соответствует пустой ячейке таблицы или значению Null.
В-третьих, SQL — это непроцедурный язык. Запрос определяет, что нужно сделать с данными и передается для выполнения СУБД, он не является последовательностью инструкций.
Все операторы языка разбиты на три основные категории:
Операторы двух первых категорий часто объединяют в рамках DDL.
Кроме указанных, существует еще несколько групп операторов, которые управляют транзакциями, соединениями, сеансами и т.д. Но эти группы не являются определяющими.
Теперь рассмотрим основные возможности языка SQL при создании запросов. Примеры и описания запросов позволят читателю быстро освоить основные возможности языка, для более полного изучения SQL следует воспользоваться специализированным изданием.
Команда SELECT
Как уже говорилось выше, при формировании запросов основное место отводится команде select, которая обеспечивает отбор данных в соответствии с заданным критерием.
После оператора select приводится перечень имен полей, по которым требуется выполнить поиск. Поля разделяются запятой. Если необходимо выбрать все поля из таблицы, используется знак *. После оператора from задается имя таблицы, которой адресован запрос, при этом расширение файла таблицы указывать не обязательно.
SELECT Name, Capital, Continent FROM Country
В одном запросе можно обращаться к полям нескольких таблиц, в этом случае имена таблиц перечисляются через запятую.
При необходимости разделения текста разных запросов используется точка с запятой.
В запросе можно использовать дополнительные условия отбора. Для этого применяется оператор where, после которого можно задавать ограничения на отбираемые поля:
SELECT Name, Capital, Continent
FROM Country
WHERE Area > 1000000
В дополнительных условиях можно применять не только запрашиваемые поля, но и любые другие из данной таблицы. Допускается использование арифметических операторов сравнения, логических операторов and, or, not. В качестве параметров можно вводить числа, строки, даты, логические переменные. Строки и даты должны заключаться в кавычки. При необходимости введения ограничений по нескольким полям применяются логические операторы:
SELECT Name, Capital, Continent FROM Country
WHERE (Area > 1000000 AND Populiation > 1000000) OR (Capital = 'Brasilia')
При отборе также можно использовать специальные операторы языка, облегчающие формирование условий. Например, оператор between обеспечивает отбор значений, лежащих между двумя крайними величинами:
SELECT Name, Capital, Continent
FROM Country
WHERE Area BETWEEN 100000 AND 800000
Оператор like позволяет осуществлять отбор строковых значений по наличию в них некоторой подстроки:
SELECT Name, Capital, Continent
FROM Country
WHERE Name LIKE 'C%'
Такой запрос отберет все записи, в которых название государства начинается с буквы С. Знак процента обозначает место остальной части строки.
При создании запросов можно использовать агрегатные функции, которые выполняют действия сразу над всем полем и возвращают единственное значение..
Запрос
SELECT SUM(Area)
FROM Country
возвращает только одно число — общую площадь территории американских государств.
Агрегатные функции запрещается использовать после оператора where.
Агрегатные функции можно совмещать с другими полями, однако при этом требуется использовать оператор group by. Сам по себе этот оператор группирует записи по значениям тех полей, которые указаны после group by.
SELECT Name, Capital, Continent
FROM Country
GROUP BY Continent
В этом запросе записи будут расположены не в соответствии со значениями текущего индекса, а сгруппированы по значениям поля Continent (континент) — сначала идут государства Северной, затем Южной Америки.
При совместном использовании полей и агрегатных функций оператор group by должен объединять все поля, которые не охвачены агрегатными функциями. Тогда для каждой группы записей, в которой значения полей в group by равны, выполняется агрегатная функция:
SELECT Continent,
SUM(Area) FROM Country
GROUP BY Continent
Этот запрос возвращает всего две записи, содержащие названия двух континентов и общую площадь государств на этих континентах.
При необходимости ограничения группирующего запроса вместо оператора where желательно использовать оператор having:
SELECT Continent,
SUM(Area)
FROM Country
GROUP BY Continent
HAVING AREA > 1000000
В запросах после оператора where можно использовать подзапросы, результат выполнения которых присваивается какому-либо полю:
SELECT Name, Capital, Continent
FROM Country
WHERE Area = (SELECT MAX(Area) FROM Country)
В SQL можно использовать псевдонимы полей и таблиц. Псевдонимы обычно используются в запросах к нескольким таблицам или в объединениях.
SELECT O.OrderNo, С.Company FROM Orders 0, Customer С WHERE O.CustNo = C.CustNo
В данном случае в запросе использованы псевдонимы для двух таблиц.
Псевдонимы понадобились для идентификации одноименных полей двух таблиц. В целом запрос реализует объединение таблиц и возвращает номера заказов и наименования покупателей.
Вместо псевдонимов можно использовать и имена таблиц:
SELECT Orders.OrderNo, Customer.Company
FROM Orders, Customer
WHERE Orders.CustNo = Customer.CustNo
Псевдонимы можно применять и для отдельных полей:
SELECT Name N, Capital С FROM Country
Команда UPDATE
Для изменения данных в таблицах используется команда update
UPDATE Country
SET Population = 27500000
WHERE Name = 'Canada'
Данный запрос, благодаря оператору where, изменяет значение поля в единственном поле. Если запрос отбирает несколько записей, то замена осуществляется во всех записях.
Команда INSERT
Для добавления к таблице новой записи используется команда insert, в которой перечисляются поля и их значения. Если в новой записи в таблицу добавляются значения только для некоторых полей, то после оператора insert перечисляются имена соответствующих полей:
INSERT INTO Country (Name, Capital, Area, Population) VALUES ('Panama', 'Panama', 101178, 2345000)
Если в таблицу добавляется целая запись, то имена полей можно опустить:
INSERT INTO Country VALUES ('Panama', 'Panama', ", 101178, 2345000)
Команда DELETE
Удаление записей из таблицы осуществляется командой delete. Для удаления всех записей синтаксис команды выглядит так:
DELETE FROM Country
Для выборочного удаления записей можно использовать оператор where:
DELETE FROM Country WHERE Continent = 'North America'
Команда CREATE TABLE
Для создания таблиц в рамках DDL имеется команда create table, в которой необходимо перечислить названия полей новой таблицы и их типы данных:
CREATE TABLE Country (
Name CHAR(24),
Capital CHAR(24),
Cont inent CHAR(24),
Area FLOAT,
Population FLOAT )
При использовании этой команды необходимо учитывать, какие типы данных применяются в конкретной СУБД. В примере использованы типы данных сервера InterBase.
Гетерогенные запросы
BDE позволяет создавать запросы сразу к нескольким разнотипным таблицам различных баз данных. Например, можно запросить несколько полей из локальной БД Paradox и удаленного сервера Informix 7.3. Такие запросы называются гетерогенными.
При создании таких запросов разрешается использовать только операторы и выражения стандарта SQL 92, без специфических расширений языка SQL, имеющихся на каждом сервере.
Для идентификации таблиц в таком запросе используются псевдонимы соответствующих баз данных. При этом свойство DatabaseName компонента должно оставаться незаполненным.
Гетерогенные запросы создаются с использованием следующего синтаксиса:
SELECT W.ItemName, D.ItemNo
FROM ":IBLocal:Items" W, ":DBDEMOS:Items" D
WHERE W.IteniNo=D.IternNo
Создавая соответствующие псевдонимы BDE, в гетерогенных запросах можно использовать данные из любых серверов, которые можно подключить через SQL Links.
Как работает запрос
Теперь настало время разобраться с основными принципами работы запросов в приложениях баз данных Delphi. Главную роль в подготовке и диспетчеризации запросов SQL играет BDE. Непосредственную обработку запросов выполняет специальный элемент архитектуры процессора БД — система обработки запросов, которая идентифицирует набор данных запроса, выполняет проверку синтаксиса и, в зависимости от установленных значений параметров BDE, передает локальный вариант запроса стандартному драйверу соответствующей БД или адресует запрос серверу БД через систему драйверов SQL Links.
Инициатором запроса выступает приложение. Для создания и выполнения запросов предназначен компонент TQuery, который содержит текст запроса, умеет выполнять его и инкапсулирует набор данных с результатом выполнения запроса. Этот набор данных можно использовать так же, как и любой другой набор данных, созданный при помощи компонента TTable.
После получения команды на выполнение запроса компонент TQuery инициализирует процесс подготовки запроса к выполнению, который включает несколько этапов.
Основная задача подготовки запроса — установить связь между СУБД, которая будет выполнять запрос, и набором данных соответствующего компонента TQuery. Если это удалось сделать, то определяется способ выполнения запроса — локальный доступ через стандартный драйвер или передача текста запроса для выполнения серверу. При этом становится ясным, будет ли результат выполнения запроса редактируемым. После этого задаются значения для переменных параметров запроса.
Если запрос выполняется локально, то через стандартный драйвер он передается соответствующей СУБД, которая выполняет его. По установленной при подготовке запроса связи результат передается в набор данных приложения.
Если запрос адресован серверу SQL, то подразумевается, что он имеет специфический синтаксис, присущий данному серверу. Поэтому вся специальная подготовка параметров запроса осуществляется на стороне сервера. BDE лишь обеспечивает передачу запроса и возврат результата выполнения набору данных приложения.
Если запрос имеет простую структуру, то его подготовка и выполнение может быть проведена ускоренно в один этап за счет использования специальной функции API BDE DbiQExecDirect.
Еще один способ выполнения запросов для серверов SQL — прямое обращение к функциям API соответствующего сервера. Это самый быстродействующий, но и самый трудоемкий для разработчика способ.
Особенности реализации SQL в Delphi
Приложения Delphi обращаются к данным при помощи BDE, при этом способы доступа к данным различаются в зависимости от типа базы данных. К локальным БД Paradox, dBASE, MS Access, FoxPro BDE обращается при посредстве стандартных драйверов. Данные от серверов SQL поступают благодаря использованию специальной системы драйверов SQL Links. Важнейшую роль при обработке и отправлении запроса играет составная часть процессора БД — система обработки запросов.
Локальные СУБД не используют язык SQL в качестве основного при работе с данными. Тем не менее, BDE при помощи соответствующего стандартного драйвера транслирует поступающие от приложений запросы в понятный для локальной СУБД вид и принимает ответы. Так как запрос к любой локальной БД выполняется одним механизмом, то существует и единый синтаксис SQL для работы с такими данными (разработчики имеют право расширять действующий стандарт языка). Этот вариант носит название локальный SQL и является подмножеством стандарта SQL 92.
Все серверы БД, работающие с BDE через SQL Links, являются серьезными промышленными системами и работают на собственных расширениях языка. В этом случае BDE просто передает запрос серверу, никак не транслируя его. Естественно, что разработчик приложения для работы с таким сервером БД должен владеть этим вариантом SQL.
Рассмотрим теперь особенности локального SQL. Как подмножество SQL 92, локальный SQL содержит операторы, которые делятся на две категории: DML и DDL. На применение операторов накладываются некоторые ограничения.
Команда create table создает таблицу с двумя ограничениями:
CREATE TABLE "Items.db" ( ItemNo AUTOINC,
ItemName CHAR (25) ,
ItemKod CHAR(5),
PRIMARY KEY(ItemNo)
)
Команда ALTER TABLE модифицирует структуру существующей таблицы с использованием операторов add и drop, которые можно использовать в произвольном порядке с разделением запятой.
ALTER TABLE "Items.db"
DROP ItemKod,
ADD ItemKod CHAR[25]
Команда drop table уничтожает таблицу, особенностей применения нет.
DROP TABLE "I terns.db"
Команда create index создает индекс. Для таблиц Paradox можно создавать только вторичные индексы.
CREATE INDEX ItemNameIdx ON "Items.db" (ItemName)
Команда drop index уничтожает индекс. При удалении первичного индекса таблицы Paradox используется ключевое слово primary.
DROP INDEX "Items.db" PRIMARY
При удалении любого другого индекса задается его имя.
DROP INDEX "Items.db" ItemNameIdx
Команда create view создает просмотр на основе полей одной или нескольких таблиц при помощи запроса с оператором select. Оператор check option допускается, оператор cascaded/local не допускается. При этом можно создавать новые просмотры на основе полей существующих.
CREATE VIEW CountryView (Fl, F2) AS SELECT * FROM Country
Просмотры представляют собой временные наборы значений полей таблиц в оперативной памяти. В основе просмотра лежит оператор select, который использует информацию о его структуре. Просмотр не является временной таблицей в памяти, так как не содержит собственных данных.
Синтаксис операторов языка управления данными полностью соответствует стандарту SQL 92.
При работе с агрегатными функциями обратите внимание, что работает только оператор count (), оператор count использовать нельзя.
При работе с функциями преобразования строк не применяются операторы
COLLATE,CONVERT И TRANSLATE.
Для того чтобы в результате выполнения запроса получить редактируемый набор данных ("живой" результат запроса), необходимо соблюдать следующие правила:
Перечисленные ограничения порождены не трудностями обработки запросов в приложениях Delphi и BDE, а объективными причинами. Например, как внести изменения в набор данных, одна запись которого появилась в результате суммирования нескольких реальных записей таблицы БД? (Такое бывает при использовании операторов group by, distinct, sum и т. д.) То есть выполнение ограничений попросту позволяет избежать неопределенности с точной адресацией каждой ячейки набора данных по отношению к ячейке реальной таблицы БД.
Нарушающий эти ограничения запрос будет прекрасно работать, но его набор данных можно только просмотреть.
Создание и выполнение запросов
Главную роль в подготовке и диспетчеризации запросов SQL играет BDE. Непосредственную обработку запросов выполняет специальный элемент архитектуры процессора БД — система обработки запросов, которая идентифицирует набор данных запроса, выполняет проверку синтаксиса и, в зависимости от установленных значений параметров BDE, передает локальный вариант запроса стандартному драйверу соответствующей БД или адресует запрос серверу БД через систему драйверов SQL Links.
Инициатором запроса выступает приложение. Для создания и выполнения запросов предназначен компонент TQuery, который содержит текст запроса, умеет выполнять его и инкапсулирует набор данных с результатом выполнения запроса. Этот набор данных можно использовать так же, как и любой другой набор данных, созданный при помощи компонента TTable.
После получения команды на выполнение запроса компонент TQuery инициализирует процесс подготовки запроса к выполнению, который включает несколько этапов.
Основная задача подготовки запроса — установить связь между СУБД, которая будет выполнять запрос, и набором данных соответствующего компонента TQuery. Если это удалось сделать, то определяется способ выполнения запроса — локальный доступ через стандартный драйвер или передача текста запроса для выполнения серверу. При этом становится ясным, будет ли результат выполнения запроса редактируемым. После этого задаются значения для переменных параметров запроса.
Если запрос выполняется локально, то через стандартный драйвер он передается соответствующей СУБД, которая выполняет его. По установленной при подготовке запроса связи результат передается в набор данных приложения.
Если запрос адресован серверу SQL, то подразумевается, что он имеет специфический синтаксис, присущий данному серверу. Поэтому вся специальная подготовка параметров запроса осуществляется на стороне сервера. BDE лишь обеспечивает передачу запроса и возврат результата выполнения набору данных приложения.
Если запрос имеет простую структуру, то его подготовка и выполнение может быть проведена ускоренно в один этап за счет использования специальной функции API BDE DbiQExecDirect.
Еще один способ выполнения запросов для серверов SQL — прямое обращение к функциям API соответствующего сервера. Это самый быстродействующий, но и самый трудоемкий для разработчика способ. Разработчики Delphi создали набор специализированных компонентов для доступа к данным сервера InterBase. Эти компоненты работают с API InterBase и позволяют добиться впечатляющего увеличения производительности приложения по сравнению со стандартными способами работы (подробнее об этом см. главу 24).
Рис. 20.1. Главная форма проекта DemoQuery
В качестве примера использования компонента TQuery рассмотрим простой редактор запросов SQL (рис. 20.1). Компонент типа тмето предназначен для ввода текста запроса. При щелчке на кнопке RunBtn текст запроса передается в свойство sql компонента TQuery. Свойство sql предварительно очищается. При выполнении запроса необходимо предусмотреть защиту от возможных ошибок. Для этого используется блок try.. .except.
Листинг 20.1. Секция Implementation главного модуля роекта DemoQuery implementation
{$R *.DFM}
procedure TMainForm.BitBtnIClick(Sender: TObject);
begin try with Query 1 do begin if Active then Close;
SQL.Clear;
SQL.Assign(Memol.Lines) ;
Open ;
end;
except on E:EDBEngineError do ShowMessage('Ошибка выполнения запроса');
end;
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction) ;
begin
if Queryl.Active then Queryl.Closed; end;
end.
Как видно из листинга программы, компонент TQuery, инкапсулирующий запрос, работает примерно так же, как и компонент TTabie, за исключением того, что необходимо задать текст запроса.
Компонент TQuery
Компонент TQuery, как и компонент TTabie, предназначен для создания и управления набором данных. Отличие заключается только в способе получения данных. Для компонента TTabie требуется настроить свойство DatabaseName, которое определяет базу данных, и свойство TabieName, которое определяет таблицу БД — непосредственный источник данных. Для компонента TQuery также задается свойство DatabaseName, а для определения источника данных служит свойство sql, которое содержит текст запроса. По существу, это тоже имя таблицы (в запросе оно есть всегда), только с дополнительными условиями отбора записей.
Например, компонент TTabie, связанный с таблицей COUNTRY.DB, и компонент TQuery, имеющий следующее значение для свойства sql:
SELECT * FROM Country
имеют абсолютно идентичные наборы данных и предоставляют разработчику сходные средства для работы с этим набором.
Иерархия предков этих двух компонентов также совпадает: TDataSet, TBDEDataSet, TDBDataSet. Компонент TQuery Представляет собой мощный и гибкий инструмент реализации и поддержки основных и вспомогательных функций приложения. С его помощью можно легко и эффективно решить такие задачи, которые с помощью компонента TTabie решаются гораздо сложнее или не решаются вообще. При этом не следует забывать, что столь выдающиеся преимущества обеспечивает не сам компонент, а язык SQL. Компонент TQuery является только средством доступа к богатейшим возможностям языка.
Компонент TQuery незаменим в многоуровневых приложениях, так как специально предназначен для выполнения запросов SQL, которые являются основным рабочим инструментом в серверах БД.
В локальных приложениях также существует множество задач, которые удобнее решать при помощи TQuery. Но не так уж редко встречаются ситуации, когда применение компонента TTabie более оправдано.
Работа с полями в компоненте TQuery ведется так же, как и в компоненте TTabie. Объекты полей могут быть статическими и динамическими (гл. 17). Вычисляемые поля могут создаваться при помощи Редактора полей или путем создания вычисляемого выражения в тексте запроса. В последнем случае вычисляемым полям желательно присваивать псевдонимы: SELECT Name, 'Плотность населения на 1 км. кв., Area/Population SI FROM Country .
В этом запросе заданы два вычисляемых поля. Отношение площади территории страны к численности населения рассчитывается при выполнении запроса для каждой записи. Поле со статическим текстом также относится к вычисляемым.
Так как структура возвращаемых запросом полей может изменяться, то экземпляры класса TFieidDef (гл. 16), хранящие информацию о свойствах полей в Tquery, создаются по необходимости при запуске приложения. Компонент TTable создает все классы для описания полей в любом случае, поэтому в приложениях клиент/сервер табличный компонент может открываться существенно медленнее, чем запрос.
Благодаря своим предкам, компонент TQuery имеет возможность использовать применительно к записям своего набора данных механизмы фильтрации, закладок, поиска. Это придает компоненту дополнительную гибкость (табл. 20.1).
Отображение данных для компонента TQuery осуществляется также через компонент TDataSource.
Таблица 20.1. Свойства и методы компонента TQuery
Объявление |
Тип |
Описание |
Свойства |
||
property Constrained: Boolean; |
Pb |
При значении True запрещает внесение в набор данных таких значений, которые не соответствуют условиям отбора запроса. Применимо для локальных БД |
property DataSource: TDataSource; |
Pb |
Ссылается на компонент TDataSource, из набора данных которого задаются значения параметров |
property Local: Boolean; |
Ro |
Значение True означает, что запрос обращается к локальной таблице |
Property ParamCheck: Boolean; |
Pb |
При значении True параметры .запроса обновляются при изменении свойства sql во время выполнения |
Property ParamCount: Word; |
Ro |
Возвращает число параметров в запросе |
Property Params [Index: Word] TPa rams; |
Pb |
Индексированный список объектов TParams, каждый из которых соответствует одному параметру запроса |
Property Prepared: Boolean; |
Pu |
Возвращает результат выполнения операции подготовки запроса к выполнению |
Property RequestLive: Boolean; |
Pb |
При значении False результат запроса нельзя редактировать, независимо от того, редактируемый результат или нет. При значении True результат запроса можно редактировать, но только если он "живой" |
Property RowsAffected: Integers- |
Ro |
Возвращает число модифицированных записей набора данных с момента последнего выполнения запроса |
Property SQL: Tstrings; |
Pb |
Содержит текст запроса |
Property SQLBinary: PChar; |
Pu |
Внутреннее свойство для обеспечения работы cBDE |
property StmtHandle: HDBIStmt; |
Ro |
Возвращает экземпляр объекта, соответствующего запросу в BDE. Используется при прямом вызове функций BDE |
property Text:'PChar; |
Ro |
Указатель на символьный массив, содержащий передаваемый в BDE текст запроса |
property UniDirectional: Boolean; |
Pb |
Определяет тип используемого курсора данных |
Методы |
||
procedure ExecSQL; |
Pu |
Выполняет запрос без открытия набора данных |
procedure GetDetailLinkFields (MasterFields, DetailFielda: TList); override; |
Pu |
Заполняет списки параметров метода экземплярами объектов полей двух таблиц запроса, находящихся в отношении "один ко многим" |
function ParamByName (const Value: string): TParam; |
Pu |
Возвращает ссылку на экземпляр объекта параметра с именем, переданным в параметре Value |
procedure Prepare; |
Pu |
Готовит запрос к выполнению |
procedure UnPrepare; |
Pu |
Освобождает ресурсы, занятые при подготовке запроса к выполнению |
Текст запроса определяется свойством sql, для задания которого применяется простой редактор, открывающийся при щелчке на кнопке свойства в Инспекторе объектов (рис. 20.2).
Для управления текстом запроса во время выполнения приложения можно использовать возможности класса Tstrings.
Рис. 20.2. Редактор свойства SQL компонента Tquery
Выполнение запроса
Выполнение запроса возможно тремя способами.
Если запрос возвращает результат в набор данных, то применяется метод Open или свойство Active, которому присваивается значение True. После выполнения запроса открывается набор данных компонента. Закрывается такой запрос методом close или тем же свойством Active (листинг 20.1).
Если запрос не возвращает результат в набор данных (например, использует операторы insert, delete, update), то используется метод ExecSQL. После выполнения запроса набор данных компонента не открывается. Попытка использовать для такого запроса метод open или свойство Active приведет к ошибке создания указателя на курсор данных.
Для того чтобы разрешить редактирование набора данных запроса, необходимо свойству RequestLive присвоить значение True. Это свойство устанавливается, но не работает для запроса, результат которого не модифицируется из-за самого запроса.
Для подготовки запроса к выполнению предназначен метод prepare. Хотя эта операция выполняется BDE автоматически, вполне может возникнуть ситуация, когда разработчик будет вынужден использовать этот метод (хотя бы при оптимизации работы многоуровневых приложений).
Метод unprepare освобождает занятые при подготовке запроса системные ресурсы.
Результат выполнения этих двух операций отражается в свойстве Prepared, значение True говорит о том, что запрос подготовлен для выполнения.
Свойство uniDirectional управляет типом курсора для набора данных. При значении True курсор может перемещаться только в одном направлении — вперед от первой записи к последней — это однонаправленный курсор. При значении False курсор имеет две степени свободы — это двунаправленный курсор.
Дело в том, что часть серверов SQL поддерживает только однонаправленные курсоры. Это связано с особенностями индексирования полей в таблицах. BDE, за счет использования кэширования данных, обеспечивает функционирование двунаправленных курсоров в приложениях Delphi для таких таблиц. Но это требует затрат дополнительных ресурсов на создание кэша записей. Кроме того, перемещение по записям к началу такой таблицы (например, в компоненте TDBGrid) ощутимо медленнее.
Свойство uniDirectional позволяет разработчику управлять этим механизмом.
Параметры запроса
Свойство params дает разработчику возможность изменять условия отбора запроса в зависимости от текущей ситуации. Свойство представляет собой набор изменяемых параметров запроса. Например, параметры можно использовать для передачи в запрос диапазона дат, для которых необходимо сделать отбор в базе данных. Подробнее о применении параметров в запросах см. ниже.
Параметры запроса играют важную роль в тех запросах, где значения ограничений отбора данных по запросу могут изменяться. Совокупность параметров описывается свойством params, которое представляет собой индексированный массив объектов трагат. Каждый объект соответствует одному параметру.
Рассмотрим следующий запрос SQL:
SELECT SaleDat, OrderNo
FROM Orders
WHERE SaleDat>= '01.08.98' AND SaleDat<='31.08.98'
В нем осуществляется отбор номеров заказов, сделанных в августе. Теперь вполне естественно было бы предположить, что пользователю может понадобиться получить подобный отчет за другой месяц или за первые десять дней августа.
В этом случае можно поступить так:
procedure TForml.FormCreate(Sender: TObject);
begin
with Queryl do
begin
SQL[0]:= 'SELECT PartDat, ItemNo, ItemCount, InputPrice';
SQL[1]:= 'FROM Parts';
SQL[2]:= 'WHERE PartDat>= ''01.08.98'' AND PartDat<=" 31.08.98''';
end;
end;
procedure TFormI.RunBtnClick(Sender: TObject);
begin with Queryl do
begin
if Active then Close;
SQL[2] := 'WHERE PartDat>= '+chr(39)+DatelEdit.Text+chr(39)+ ' AND PartDat<='+chr(39)+Date2Edit.Text+chr(39);
Open;
end;
end;
При создании формы в методе FormCreate задается текст запроса. Для этого используется свойство sql. При щелчке на кнопке RunBtn, в соответствии с заданными в однострочных редакторах DatelEdit и Date2Edit датах, изменяется текст запроса. Метод FormCreate приведен только для того, чтобы обозначить первоначальный текст запроса, этот текст вполне можно задать в свойстве sql.
Но для решения подобных задач можно использовать готовый механизм, имеющийся в компоненте TQuery. Это параметры запроса. В данном случае текст запроса будет выглядеть следующим образом:
SELECT PartDat, ItemNo, ItemCount, InputPrice
FROM Parts
WHERE PartDat>= :PD1 AND PartDat<= :PD2
Рис. 20.3. Специализированный редактор параметров запроса
Двоеточие перед именами pdi и PD2 означает, что это параметры. Имя параметра выбирается произвольно. В списке свойства params первым идет тот параметр, который расположен первым по тексту запроса.
После ввода в свойстве sql текста запроса для каждого параметра автоматически создается объект трагат. Эти объекты доступны в специализированном редакторе, который вызывается при щелчке на кнопке свойства params в Инспекторе объектов (рис. 20.3). Для каждого параметра требуется установить тип данных, который должен соответствовать типу данных соответствующего поля.
Теперь для задания текущих ограничений по дате поступления можно использовать свойство Params:
procedure TFormI.RunBtnClick(Sender: TObject);
begin with Queryl do begin if Active then Close;
Params[0].AsDateTime := StrToDate(DatelEdit.Text);
Params[1].AsDateTime := StrToDate(Date2Edit.Text);
Open;
end;
end;
При щелчке на кнопке RunBtn при помощи параметров в запрос передаются текущие значения ограничений дат.
Если во время выполнения текст запроса изменяется, то для автоматического обновления значений параметров необходимо свойству paramcheck компонента TQuery присвоить значение True.
Значения параметров запроса можно задать и из другого набора данных. Для этого применяется свойство Datasource. Указанный в свойстве компонент TDataSource должен быть связан с набором данных, значения полей которого требуется передать в параметры. Названия параметров должны соответствовать названиям полей этого набора данных, тогда свойство Datasource начнет работать. При перемещении по записям набора данных текущие значения одноименных параметрам полей автоматически передаются в запрос.
Для иллюстрации работы этого свойства рассмотрим простой пример, главная форма которого представлена на рис. 20.4. Этот проект не имеет ни одной строки написанного вручную программного кода.
Верхняя сетка отображает данные из таблицы Orders базы данных DBDEMOS и связана через компонент Orderssource типа TDataSource с компонентом запроса ordersQuery. Текст запроса выглядит так:
SELECT * FROM Orders
Рис. 20.4. Главная форма проекта DemoQueryParams Нижняя сетка отображает данные о покупателе и через компонент
CustSource типа TDataSource связана с запросом CustSource, свойство SQL
которого имеет следующий вид:
SELECT * FROM Customer WHERE CustNo=:CustNo
Обратите внимание, что название параметра соответствует названию поля номера покупателя из таблицы Orders.
Свойство DataSource компонента custQuery указывает на компонент OrdersSource.
Как только оба набора данных открываются, текущее значение из поля CustNo набора данных orders автоматически передается в параметр запроса компонента CustQuery.
Благодаря этому, для двух наборов данных реализована связь "один к одному" по полю номера поставщика.
И в завершение разговора о параметрах запросов рассмотрим свойства и методы класса Tparams (табл. 20.2), составляющего свойство Params, и класса трагат, инкапсулирующего отдельный параметр (табл. 20.3).
Таблица 20.2. Свойства и методы класса Tparams
Объявление |
Тип |
Описание |
Свойства |
||
property Items [Index: Word]: TParana- |
Ro |
Индексированный список экземпляров объектов параметров |
property ParamValues [const ParamName: string]: Variant- |
Pu |
Содержит значения для параметра с именем ParamName. Имена нескольких параметров разделяются точкой с запятой, тогда свойство возвращает массив вариантов |
property Count: Integer; |
Ro |
Возвращает число параметров |
Методы |
||
procedure AddParam (Value: TParam) ; |
Pu |
Добавляет в список новый параметр |
procedure AssignValues (Value: TPa rams) ; |
Pu |
Передает в список новые значения для параметров |
type TParamType = (ptUnknown, ptinput, ptOutput, ptInputOutput, ptResult) ; |
Pu |
Создает и добавляет в список новый параметр с именем ParamName и типом ParamType, связанный с полем типа FIdType |
function CreateParam (FIdType: TFieldType; const ParamName: string; ParamType: TParamType): TParam |
|
|
function FindParam (const Value: string): Tparam; |
Pu |
Возвращает экземпляр объекта параметра с именем Value. В случае неудачного поиска возвращает Nil |
procedure GetParamList (List: TList; const ParamNames: string); |
Pu |
Заполняет список List объектами параметров, имена которых перечислены в ParamNames |
function IsEqual (Value: TParams): Boolean; |
Pu |
В случае совпадения параметров данного списка со списком Value возвращает True |
function ParamByName (const Value: string): TParam; |
Pu |
Возвращает экземпляр объекта параметра с именем Value. В случае неудачного поиска генерирует исключение |
Свойства |
||
function ParseSQL (SQL: String; DoCreate: Boolean): Strings- |
Pu |
В тексте запроса, находящегося в строке sql, заменяет все параметры вопросительными знаками. Возвращает исправленную строку запроса. Если DoCreate = True, то для каждого параметра создается объект, и он заносится в список |
procedure RemoveParam (Value: TParam); |
Pu |
Удаляет из списка параметр Value |
Тип параметра определяется множеством трагатТуре, которое имеет следующие значения:
Таблица 20.3. Свойства и методы класса трагат
Объявление |
Тип |
Описание |
||
Свойства
|
||||
property Bound: Boolean; |
Pb |
Возвращает True, если параметр не имеет значения. Если свойству присвоить значение False, параметр теряет значение |
||
property DataType: TFieldType; |
Pb |
Содержит тип связанного с параметром поля |
||
property IsNull: Boolean; |
Ro |
Если значение поля равно Null, возвращает True |
||
type TParamType = (ptUnknown, ptinput, ptOutput, ptinputOutput, ptResult) ; property ParamType: TParamType; |
Pb
|
Определяет тип параметра
|
||
property Text: string; |
Pu |
Содержит значение параметра в строковом формате |
property Value: Variant; |
Pb |
Содержит значение параметра |
||
Методы |
||||
procedure Assign (Source: TPersistent) ; override; |
Pu |
Переносит название и значение параметра Source в данный параметр |
||
procedure AssignField (Field: TField) ; |
Pu |
Переносит название, тип данных и значение из поля Field в данный параметр |
||
procedure AssignFieldValue (Field: TField; const Value: Variants- |
Pu |
Переносит тип данных из поля Field и значение value в данный параметр |
||
procedure Clear; |
Pu |
Присваивает параметру значение Null. При этом свойство Bound получает значение True |
||
procedure GetData (Buffer: Pointer) ; |
Pu |
Переносит в буфер Buffer значение параметра в формате, соответствующем его типу данных |
||
function GetDataSize: Integer; |
Pu |
Возвращает число байт, необходимых для хранения значения параметра |
||
procedure LoadFromFile (const FileName: string; BlobType: TBIobType; |
Pu |
Загружает из файла значение типа BLOB |
||
procedure LoadFromStream (Stream: TStream; BlobType:TBIobType; |
Pu |
Загружает из потока значение типа BLOB |
||
procedure SetBlobData (Buffer: Pointer; Size: Integer) ; |
Pu |
Копирует из буфера Buffer значение параметра в формате BLOB |
||
procedure SetData (Buffer: Pointer); |
Pu |
Копирует из буфера Buffer значение параметра |
Кроме перечисленных в таблице свойств, класс трагагд обладает целой группой свойств для преобразования типа данных значения параметра. Это свойства AsBCD, AsBlob, AsBoolean, AsCurrency, AsDate, AsDateTime, AsFloat, Aslnteger, AsMemo, AsSmallInt, AsString, As-Time, AsWord. Работают они так же, как аналогичные свойства в классе TField (гл. 17).
Статические и динамические запросы
Обычно текст запроса определяется во время разработки приложения и вводится в свойство sql компонента TQuery при помощи простейшего специализированного редактора (рис. 20.2), который вызывается щелчком на кнопке свойства в Инспекторе объектов. Такие запросы называются статическими. Преимущество этих запросов в том, что для них можно сразу же проверить правильность синтаксиса SQL и сформировать статические поля для набора данных компонента.
Текст запроса можно определять и во время выполнения, используя для этого стандартные средства класса TStrings, так как свойство sql есть экземпляр данного класса. Запросы, текст которых задается или изменяется во время выполнения, будем называть динамическими. Их преимущество в гибкости и многоплановости — ведь один компонент TQuery, расположенный в модуле данных, может работать в нескольких формах, для этого достаточно вовремя изменить текст запроса.
Для замены всего текста можно использовать методы clear и Add, а если требуется изменить одну строку, можно воспользоваться индексом этой строки (см. выше метод FormCreate). Перед началом редактирования текста запроса необходимо отключить набор данных компонента.
Наличие в запросе параметров не означает, что запрос является динамическим, так как текст запроса не меняется. Если в старой редакции текста запроса параметры были, а в новой нет, то список параметров запроса обнуляется автоматически. Если параметры в тексте появились, то объекты параметров создаются также автоматически.
Резюме
Приложения в архитектуре клиент/сервер являются многоуровневыми распределенными системами и обеспечивают одновременную работу с данными многих пользователей. Основу таких приложений составляет сервер БД или сервер SQL.
Запросы SQL являются основным инструментом, используемым для доступа к данным и управления данными в многоуровневых приложениях баз данных. В Delphi реализована полноценная поддержка самого современного стандарта SQL 92.
Зная способы использования запросов в приложениях Delphi, разработчик получает инструмент для создания программной логики многоуровневых приложений баз данных. В приложениях запросы можно использовать при помощи компонента TQuery, который инкапсулирует набор данных, также как и компонент TTabie. Оба этих компонента наследуют от одних и тех же предков — классов доступа к данным.