Лекция #9. Основы языка SQL, создание запросов к удаленным БД. Автор: Пучкова Д.М. План: 1. 2. 3. 4. Введение. Основные операторы SQL. Компонент Query. Примеры приложений с использованием Query. П.1. Введение. Язык SQL (Structured Query Language – язык структурных запросов) был создан Microsoft в конце 70-х годов и получил через некоторое время широкое распространение. Он позволяет формировать сложные запросы к БД. Запрос – это вопрос к БД, возвращающий запись или множество записей, удовлетворяющих вопросу. Существует несколько стандартов языка SQL: SQL ANSI, SyBase SQL Server и Microsoft SQL. Interbase, Oracle и многие другие серверы в основном придерживаются стандарта ANSI, но любой разработчик вносит в него свои усовершенствования. Далее изложение будет основываться на диалекте, принятом в локальном сервере InterBase, с которыми познакомимся позднее. Впрочем, поскольку речь идет только об основных операторах языка, расхождение в их синтаксисе между различными диалектами невелико. Чтобы действительно изучить SQL, необходимо обратиться к документации ой системы, с которой производится работа. Delphi позволяет приложениям при помощи запросов SQL использовать данные: таблиц Paradox и dBase – используется синтаксис локальной SQL; локального сервера InterBase – полностью поддерживает соответствующий синтаксис; удаленных серверов SQL через драйверы SQL Links. Общие правила синтаксиса SQL очень просты. Язык SQL не чувствителен к регистру. Если используется программа из нескольких операторов SQL, то в конце каждого оператора ставится «;». Если используется всего один оператор, то «;» в конце не обязательна. Комментарий может записываться в стиле Delphi: /*<комментарий>*/ или {<комментарий>}. Вот, собственно, и все правила. П.2. Основные операторы SQL. Основные операторы SQL известны из курса «Базы данных». Перечислим их: SELECT – оператор выбора; INSERT – вставка новой записи в таблицу; UPDATE – редактирование записей; DELETE – удаление записей; CREATE – создание новой таблицы; DROP TABLE – удаление таблицы; ALTER TABLE – модификация структуры таблицы; DROP INDEX – удаление существующего индекса; ALTER INDEX <имя_индекса> DEACTIVATE – деактивация существующего индекса; 1 ALTER INDEX <имя_индекса> ACTIVATE – активация существующего индекса. Существуют и другие операторы. Д.З.: повторить основные операторы SQL к контрольной. П.3. Компонент Query. Рассмотрим, как можно использовать язык SQL в приложениях. Для этого существует компонент набора данных класса TQuery. Он имеет большинство свойств и методов, совпадающих с Table, и компонент Query может во многих случаях включаться в приложения вместо Table. Дополнительные возможности Query – возможность формировать запросы на языке SQL. В ряде случаев Table и Query в приложениях взаимозаменяемы. При работе с локальными БД чаще используется компонент Table. С его помощью можно не только просматривать БД, но и модифицировать записи, удалять их, вставлять новые записи. При работе с сервером БД компонент Table становится малоэффективным. В этом случае он создает на компьютере пользователя временную БД и работает с этой копией. Такая процедура требует больших ресурсов и существенно загружает сеть. Этот недостаток отсутствует в компоненте Query. Если запрос SQL сводится к просмотру таблицы (запрос Select), то результат запроса (а не сама исходная таблица) помещается во временном файле компьютера пользователя. Правда, это таблица только для чтения и не допускает изменений. Но это ограничение возможно обойти. Если запрос SQL связан с некоторыми изменениями содержимого таблицы, то никаких временных таблиц не создается. BDE передает запрос на сервер, там он обрабатывается и в приложение возвращается информация о том, успешно ли завершена соответствующая операция. Таким образом, эффективность Query при работе в сети становится выше эффективности Table и не позволяет сформировать некоторые сложные запросы языка SQL. С другой стороны, при работе с локальной БД эффективность Query заметно ниже эффективности Table (замедление вычислений). Таким образом, Query целесообразно использовать в серверных приложениях, а Table – при работе с локальными БД. Для ознакомления с Query, откроем в Delphi новое приложение, разместим на форму компоненты Query, DataSource, DBGrid. В свойстве DataSet компонента DataSource1 задать Query1, а в свойстве DataSource компонента DBGrid1 задать DataSource1. Таким образом, мы создали обычную цепочку: набор данных (Query1), источник данных (DataSource1), компонент визуализации и управления данными (DBGrid1). Основное свойство Query – SQL, имеющее тип TString. Это список строк, содержащих SQLзапросы. В процессе проектирования приложения обычно необходимо, как будет далее показано, сформировать в этом свойстве некоторый предварительный запрос SQL, который показал бы, с какой таблицей или таблицами будет производиться работа. Далее, во время выполнения приложения, свойство SQL может формироваться программно методами, обычными для класса TString: Clear – очистка, Add – добавление строки и т.д. Настройку компонента Query в процессе проектирования можно производить вручную, как с остальными компонентами, или с помощью специального Визуального Построителя Запросов. 2 1. Ручная настройка Query. В свойстве DataBaseName компонента Query необходимо задать (как это делается и для компонента Table) БД, с которой будет осуществлена связь. БД задается выбором из выпадающего списка псевдонимов, или указанием полного пути к каталогу или файлу (в зависимости от используемой СУБД). * Не устанавливается свойство DataSource (это свойство имеет отношение к приложению с несколькими связанными таблицами и в других случаях не устанавливается). Свойства TableName в Query нет, т.к. таблица, с которой ведется работа, будет указываться в запросах SQL. Поэтому, прежде всего, необходимо занести в свойство SQL запрос, содержащий имя таблицы, с которой будет производиться работа. ** Прежде, чем начинать демонстрационную настройку Query, необходимо сформировать в его свойстве SQL запрос, в котором указывается таблица и перечисляются параметры, если они используются в приложении. Пока такой запрос в SQL отсутствует, дальнейшая настройка Query невозможна. Запрос, заносимый в SQL, носит чисто служебный характер. Далее его можно заменить на любой другой запрос. Запрос, заносимый в SQL в начале проектирования, может иметь, например, следующий вид: SELECT * FROM T1; После этого система поймет, с какой таблицей будет проводиться работа, и можно будет настроить поля в Query. Если работа будет производиться с несколькими таблицами, их можно указать в запросе: SELECT * FROM T1, T2; Когда соответствующий запрос написан, можно установить свойство Active компонента Query в TRUE. Если все выполнено верно, то увидим в компоненте DBGrid1 информацию из запрошенных таблиц. Для управления отображением данных, как и в компоненте Table, имеется Редактор Полей (Field Editor). Вызывается или двойным щелчком на Query, или щелчком правой кнопки мыши на Query и выбором Fields Editor из всплывающего меню. В Редакторе Полей можно добавить имена нужных полей (щелчок правой кнопкой мыши и выбор раздела «Add»), задать заголовки полей, отличающиеся от их имен, сделать некоторые поля невидимыми (visible), не редактируемыми (ReadOnly), в логических полях можно задать отображаемые поля (да/нет), задать формат отображения чисел, создать вычисляемые поля, поля просмотра, задать диапазон значений и т.д. 2. Визуальный построитель запросов SQLBuilder. Настройку Query можно проводить не вручную, а при помощи визуального построителя запросов. Вызов его производится щелчком правой кнопки мыши на компоненте Query и выбором «SQL Builder». В выпадающих списках «DataBase» и «Table» можно выбрать БД и одну или несколько таблиц, с которыми будем работать. В списке полей таблицы можно установить индикаторы тех полей, которые требуется выбрать. Если передумали использовать выбранную таблицу, щелкнуть на ней правой кнопкой мыши и выбрать «Remove». На странице «Selection» в нижней части окна можно задать названия полей, которые будут фигурировать при выводе результата и задать выражения для новых вычисляемых полей. В левой части панели страницы пишем заголовки полей, а в правой – выражения. 3 На странице «Criteria» можно задать критерии отбора записей, на странице «Sorting» установить ключи упорядочивания. Команда «QueryShow SQL» или соответствующая быстрая кнопка позволяет сформировать запрос и отредактировать его. Команда «QueryRun SQL» или соответствующая быстрая кнопка позволяет просматривать результат сформированного запроса. При завершении работы с построителем запросов будет задан вопрос: «Save changes to query?» При положительном ответе на этот вопрос сформированный запрос будет помещен в свойство SQL компонента Query. Завершив работу с визуальным построителем запросов, пользователь возвращается в свое приложение. Как можно увидеть, в свойстве DataBaseName компонента Query появится выбранная БД, а в свойстве SQL – сформированный запрос. Можно установить ActiveTRUE и посмотреть результат. Предпочтение все же отдается ручной настройке в силу ограниченности визуального построителя запросов. П.4. Динамические запросы. Все запросы SQL, которые мы до сих пор рассматривали – это статические запросы. В них фиксировано все: имена таблиц, поля, константы в выражениях и т.д. Помимо статических запросов SQL допускает и динамические запросы, использующие параметры. Параметры можно применять вместо имен таблиц, имен полей и их значений. Значения этих параметров передаются извне и, тем самым, не изменяя текст самого запроса, можно менять возвращаемый им результат. Параметры задаются в запросе с двоеточием, предшествующим имени параметра: : <имя_параметра> Например, возможно записать в запросе Select элемент Where в виде: WHERE F6<=:Year В этом случае происходит сравнение года рождения со значением параметра Year. Если в Query в свойство SQL ввести запрос, содержащий параметры, например: SELECT * FROM T1 WHERE (F6>:Year) AND (F2=:dep) а затем щелкнуть в Object Inspector на свойстве «Params», то откроется диалоговое окно со списком указываемых в запросе параметров: Year и Dep. Это окно содержит список объектов, в котором возможно выделять их по очереди и в Object Inspector устанавливать их свойства. Это свойства: DataType (тип данных параметра: Integer, String, …); Name (имя параметра); NumericScale (максимальное число цифр после запятой, применимо только к наборам данных dbExpress); 4 ParamType (тип параметра, используемый при обращении к процедурам, хранимым на сервере); Precision (максимальное число цифр – применимо только к наборам данных dbExpress); Size (максимальное число символов в строковых параметрах: применимо только к наборам данных dbExpress); Value (значение параметра по умолчанию); Type – подсвойство Value (тип значения по умолчанию). Программно доступ к параметрам во время выполнения приложения осуществляется аналогично доступу к полям набора данных. Параметры являются объектами типа TParam, образующими массив Params, к элементам которого можно обращаться по индексу. Значения параметров определяют Value, AsString, AsInteger и т.д. Например: Query1.Params[0].AsInteger:=1980; Задает первому параметру компонента Query1 значение 1980. Последовательность, в которой располагаются параметры в свойстве Params, определяются последовательностью их упоминания в запросе SQL. Другой способ обращения к параметрам, в котором не надо помнить их индексы – использование метода ParambyName. Например: Query1.ParamByName(`Year`).AsInteger:=1980; задает параметру с именем «Year» значение 1980. П.5. Основные свойства Query, связывание таблиц. Большинство свойств Query аналогичны свойствам Table. Из свойств, отличных от Table, остановимся на свойствах DataSource. Это свойство позволяет строить приложения, содержащие связанные друг с другом таблицы. Рассмотрим пример. Допустим, мы хотим построить приложение, включающее в себя таблицу T2, содержащую список отделов (в поле F2) и их характеристику, в качестве головной таблицы; и таблицу T1, содержащую в поле F2 имя отдела, в котором работает каждый сотрудник. Хотим, чтобы при выборе записи в таблице T2, в таблице T1 отбирались только записи, относящиеся к выбранному отделу. Открыть новое приложение. Перенести на форму компоненты Query1, DataSource1, DBGrid1 и соединить их обычной цепочкой: в DBGrid1 установить DataSourceDataSource1, а в DataSource1 задать свойство DataSet равным Query1. Компонент Query1 настроить на таблицу T2. Для этого установить свойство DataBase Name (например, dBP), а в свойстве SQL написать оператор SELECT * FROM T2; Установить ActiveTRUE и убедиться, что в DBGrid1 должны отображаться данные таблицы T2. Создать аналогичную цепочку: Query2, DataSource2, DBGrid2 и связать ее с таблицей T1 следующим запросом: 5 SELECT * FROM T1; в компоненте Query2. Установить свойство Active в Query2 в TRUE, и в DBGrid2 должны отобразиться данные таблицы T1. Если запустить приложение и убедиться, что оно работает, то можно увидеть, что таблицы независимы. Для связи таблиц: изменим текст запроса в свойстве SQL вспомогательного набора данных Query2 на: SELECT * FROM T1 WHERE (F2=:Dep); В этом условии указываем условие отбора: значение поля F2 должно быть равно параметру :Dep. В свойстве DataSource компонента Query2 надо сослаться на DataSource1 – источник данных, связанный с таблицей T2. Это скажет приложению, что оно должно взять значение параметра :Dep из текущей записи этого источника данных. А т.к. имя параметра совпадает с именем поля в источнике данных, то в качестве значения параметра будет взято текущее значение этого поля. Т.о. вспомогательная таблица, запрашиваемая в Query2, оказывается связанной с головной таблицей, запрашиваемой в Query1. Запустить приложение. При перемещении по первой таблице во второй отобразятся только те записи, которые относятся к отделу, указанному в текущей записи первой таблицы. Если попробовать редактировать данные, то не получится: компонент Query с запросом SELECT формирует таблицу только для чтения. Установим в компоненте Query свойству RequestLive значение TRUE. Это позволяет возвращать как результат запроса изменяемый, «живой» набор данных, вместо таблицы только для чтения. Успешной эта попытка будет только при соблюдении ряда условий, в частности: набор данных формируется обращением только к первой таблице; набор данных не упорядочен (в запросе не используется «ORDER BY»); в наборе данных не используются совокупные характеристики типа SUM, COUNT и др.; набор данных не кэшируется (CachedUpdates=FALSE). В нашем примере все эти условия соблюдаются. Так что можно установить RequestLiveTRUE, сделать видимыми все кнопки навигатора и выполнить приложение. При этом станет возможным редактировать данные, удалять и создавать записи. П.6. Основные методы компонента Query. К основным методам Query можно отнести методы открытия и соединения с БД. Метод Close закрывает соединение с БД, переводя свойство ActiveFALSE. Этот метод надо выполнить перед изменением некоторых свойств влияющих на выполнение запроса или на отображение данных. Метод Open открывает соединение с БД и выполняет запрос, содержащийся в свойстве SQL. Этот метод применим, только если запрос сводится к оператору SELECT. Если запрос содержит другой оператор, например, UPDATE, то при выполнении Open будет генерироваться исключение EDataBaseError. Для осуществления любого другого запроса, кроме SELECT, используется метод ExecSQL. Он подготавливает выполнение дополнительного запроса, если подготовка не осуществляется заранее, и затем выполняет 6 запрос. Подготовка выполнения требует определенных затрат времени. Поэтому для ускорения взаимодействия с БД полезно перед первым выполнением запроса выполнить метод Prepare. Как поступить, если запрос неизвестен? Какой метод применить? Неизвестный запрос, сформированный в некоторой переменной ssql типа строки, можно выполнить при помощи следующего кода: try Query1.Close; Query1.SQLClear; Query1.SQL.Add(ssql); Query1.Open; . . . except on EDataBaseError do Query1.ExecSQL; end; В некоторых случаях требуется получить список имен полей таблицы, связанной с Query. Это может быть сделано методом GetFieldNames, который загружает список имен полей в каждую переменную типа TStrings, передаваемую в нее в качестве аргумента. Например, оператор: Query1.GetFieldNames(ComboBox1.Items); загружает в выпадающий список ComboBox1 имена полей таблицы, связанной с Query1. П.7. Кэширование изменений, совместное применение Table и UpDateSQL. Метод ExecSQL осуществляет немедленную модификацию таблицы. Иногда удобнее кэшировать изменения, а после того, как проверки сделаны, переслать изменения в БД или отменить все исправления. Это делается также, как для компонента Table: в компоненте Query свойство CachedUpdatesTRUE и применяются методы ApplyUpdates для записи изменений в БД, метод CancelUpdates для отмены изменений и метод CommitUpdates для очистки буфера кэша. П.8. Пример приложения с использованием Query. Рассмотрим пример формирования произвольных запросов SQL. Существуют приложения, в которых пользователям разрешено формировать любые запросы к БД. В таких приложениях без языка SQL и, соответственно, без компонента Query, не обойтись. Рассмотрим на чисто демонстрационном примере, как строить подобные приложения. Общий вид приложения приведен на рис.1. Приложение позволяет сформировать различные запросы SELECT к таблице T1. Выпадающий список «Поля» (типа TComboBox, а программе назван CBFields) заполнен именами полей, которые пользователь может выбирать из него при формировании запроса. Выпадающий список «Операции» (типа TComboBox, в программе названCBOp) заполнен символами допустимых операций, функций и знаков, которые пользователь также может 7 выбирать при формировании запроса. Окно, расположенное ниже кнопок, является компонентом типа TMemo (в программе названо MSQL). Это окно служит для отображение формируемого запроса SQL. Ниже расположена таблица DBGrid1 типа TDBGrid, которая через компонент DataSource связана с компонентом Query. Этот компонент выполняет сформированные пользователем запросы. Рис.1. Общий вид приложения. Кнопка «Новый запрос» формирует запрос, посылая в MySQL текст «SELECT…». Затем пользователь может вводить имена полей или непосредственно в текст, или, чтобы не ошибиться, выбирая их имена из списка «Поле». При вводе совокупных характеристик или вычисляемых полей пользователь может брать необходимые символы и ключевые слова из списка «Операции». Если щелкнуть «Условие», то в запрос вводится элемент «WHERE». Если – на «Порядок», то в запрос вводится элемент «ORDER BY», на кнопке «Группировка» – «GROUP BY». После того, как запрос сформирован, пользователь выполняет его щелчком на кнопке «Выполнить» и в окне отображаются результаты запроса. Ниже приведен полный текст раздела «Implementation» данного приложения. {Перечисленный тип, определяющий режим работы в каждый момент времени} type TRegim = (RNone, RFields, RWhere, ROrder, REnd); var Regim: TRegim; procedure ADDS(S: String); begin with Form1.MSQL do Lines[Lines.Count-1]:=Lines[Lines.Count-1]+s; end; procedure TForm1.FormCreate(Sender: TObject); begin // загрузка в выпадающий список имен полей таблицы Query.GetFieldNames(CBFields.Items); CBFields.Items.Insert(0,`*`); CBFields.ItemIndex:=0; CBOp.ItemIndex:=0; 8 Regim:=None; end; procedure BbeginClick(Sender: TObject); begin MSQL.Clear; MSQL.Lines.Add(`Select`); Regim:=RFields; end; procedure TForm1.BexecClick(Sender: TObject); begin if Regim = RNone then begin ShowMessage(`Вы не ввели запрос.`); Exit; end; if Regim = RFields then ADDS(` FROM T1`); Regim:=REnd; Query.SQL.Assign(MSQL.Lines); Query.Open; end; procedure TForm1.CBFieldsChange(Sender: TObject); begin if (Regim = REnd) or (MSQL.Lines.Count<1) then ShowMessage(`Начните новый запрос или вводите оператор вручную.`); else ADDS(` `+CBFields.Text); end; end; procedure TForm.BWhereClick(Sender: TObject); begin if (Regim = RFields) then ADDS (` FROM T1`); ADDS(` WHERE`); Regim:=RWhere; end; procedure TForm1.CBOpChange(Sender: TObject); begin if (Regim = REnd) or (MSQL.Lines.Count<1) then ShowMessage(`Начните новый запрос или введите оператор вручную`) else ADDS(` `+CBOp.Text); end; procedure TForm1.BOrderClick(Sender: TObject); begin if (Regim = RFields) then ADDS(` FROM T1`); ADDS(` ORDER BY`); Regim:=ROrder; end; procedure TForm1.BGroupClick(Sender: TObject); begin if (Regim = RFields) then ADDS(` FROM T1`); ADDS(` GROUP BY`); Regim:=ROrder; end; end. Вначале вводится переменная Regim типа TRegim. Тип TRegim определен в приложении как перечисляемый тип: Type TRegim = (RNone, RFields, RWhere, ROrder, REnd); Обработчики щелчков каждой кнопки проверяют значение переменной Regim и, в зависимости от результата этой проверки, производят те или иные операции. Кроме того, они 9 изменяют значение этой переменной, сообщая приложению, какие операции выполняются пользователем. В приложение вводится процедура ADDS, которая добавляет заданную текстовую строку в конец текста, содержащегося в MSQL. 10