
Лабораторная работа №8 Применение технологии доступа к данным Delphi – ibx при создании приложения для работы с бд. Команды языка sql для манипулирования данными
Цель работы: познакомиться с применением технологии доступа к данным Delphi – IBX при создании приложения для работы с БД, освоить основные команды языка SQL для манипулирования данными.
Основные теоретические сведения
На странице InterBase Палитры компонентов содержатся компоненты доступа к данным, адаптированные для работы с сервером InterBase и объединенные названием InterBase Express. Компоненты из набора InterBase Express предназначены для работы с сервером InterBase версии не ниже 5.5.
Преимущество данной технологии заключается в реализации всех функций за счет прямого обращения к API сервера InterBase. Благодаря этому существенно повышается скорость работы компонентов.
Технология IBX обеспечивается следующими компонентами:
IBDatabase – создает соединение с БД IB
IBTable – компонент, инкапсулирующий набор данных, обеспечивающий доступ к одной из таблиц
IBQuery - компонент, инкапсулирующий набор данных, позволяет на уровне приложения общаться с БД на языке SQL.
IBStoredProc – для обращения к процедурам действия (которые не возвращают результат), хранимых на сервере
IBTransaction - улучшенное управление транзакциями
IBUpdateSQL – используется для изменений в таблицах только для чтения и для кеширования изменений
IBSQL – выполняет запросы SQL, минимизируя затраты буферизации и обмена данными с компонентами Delphi
IBDataSet – Обеспечивает выполнение команд Select и выполняет команды SQL по вставке, удалению и изменению записей.Также как и IBSQL обеспечивает эффективный доступ к данным
IBDatabaseInfo - возможность получения сведений о состоянии базы данных без прямого обращения к ее системным таблицам
IBSQLMonitor - отслеживание состояния процессов выполнения запросов
IBEvents – обеспечивает асинхронную обработку событий сервера IB
IBExtract – извлекает метаданные с сервера
IBClientDataSet – клиентский набор данных
С точки зрения разработчика, за исключением нескольких новых свойств, методика использования этих компонентов в приложениях БД не отличается от стандартной методики. Компоненты, инкапсулирующие набор данных, совершенно обычным образом, через компонент TDataSource, подключается к любому из стандартных компонентов отображения данных.
Механизм доступа к данным InterBase Express
Все компоненты InterBase Express, инкапсулирующие набор данных, должны обращаться к базе данных только через компонент соединения TIBDatabase. Поэтому, для построения приложения на основе компонентов IBX на модуле данных должны быть, прежде всего, размещены компоненты IBDatabase и IBTransaction.
Компонент TIBDatabase осуществляет соединение с сервером БД, а IBTransaction управляет транзакциями. К этим компонентам подключаются компоненты наборов данных: IBTable, IBQuery, IBStoredProc, IBSQL, IBDataset, - заданием в них значений свойств Database (имя компонента IBDatabase) и Transaction (имя компонента IBTransaction). А далее к компонентам наборов данных могут подключаться обычные компоненты источников данных DataSource, к которым, в свою очередь подключаются компоненты отображения данных.
Установка соединения с БД
Для создания соединения с БД используется свойство DatabaseName, в котором необходимо указать полный путь (включая имя сервера) к выбранному файлу БД с расширением gdb. Компонент IBDatabase имеет собственный редактор, который также позволяет задать значения основных свойств, обеспечивающих соединение с базой данных.
На панели Connection выбирается требуемый сервер InterBase (локальный или доступный удаленно), затем в списке Protocol определяется используемый сетевой протокол и при помощи кнопки Browse выбирается файл базы данных.
На панели Database Parameters задаются имя пользователя (SYSDBA) и его пароль (masterkey). Также здесь можно выбрать и набор шрифтов для языковой адаптации приложения WIN1251 (Character Set). Свойство LoginPrompt определяет появление диалога, запрашивающего имя пользователя и пароль при соединении с базой данных.
Соединение с БД включается и отключается свойством Connected, которое связано со свойством Active компонентов наборов данных, подключенных к данному.
Свойство компонента IBDatabase - DefaultTransaction указывает на компонент IBTransaction, используемый по умолчанию при соединении с БД. Свойство компонента IBTransaction - DefaultDatabase задает компонент IBDatabase, используемый по умолчанию при выполнении транзакции.
При работе с серверными БД компонент набора данных TTable становится мало эффективным, так как он создает на компьютере пользователя временную копию серверной БД и работает с этой копией. Естественно, что такая процедура требует больших ресурсов и существенно загружает сеть. Преодолеть этот недостаток позволяет использование в приложении компонента IBQuery вместо IBTable, так как запрос SQL сводится к просмотру таблицы и результат этого запроса (а не сама исходная таблица) помещается во временном файле на компьютере пользователя. Ограничением работы с компонентом IBQuery является то, что набор данных, передаваемый в приложение, доступен только для чтения, однако существуют возможности преодолеть и этот недостаток.
В целом проектирование приложения соответствует вышеприведенному в работе 3, но вместо TTable используется компонент TQuery. Компонент TQuery не содержит свойства TableName, т.к. имя таблицы (таблиц) указывается текстом SQL-строки (задается в свойстве SQL). Выполнение этой SQL-строки инициируется в DesignTime установкой Active=True; при RunTime следует выполнять методом Open в том случае, когда SQL-оператор не возвращает набора данных (таких как INSERT, UPDATE, DELETE, CREATE TABLE) а также операторы Select, которые возвращают в качестве результата одну строку. или методом ExecSQL в противном случае (содержит SELECT предписание языка SQL).
Команды SQL для манипулирования данными
Одной из особенностей языка запросов к БД является его представление в виде текста; формирование текста SQL-запроса может быть произведено любыми средствами (важно лишь соблюдение правил синтаксиса запроса). Существуют три формы SQL:
• Интерактивный SQL применяется для непосредственной работы с БД - пользователь вводит SQL-оператор, он сразу же выполняется и пользователь видит результат выполнения (или код ошибки).
• Статический SQL содержит SQL-операторы, жестко закодированные в теле исполняемого приложения. Наиболее распространен встроенный SQL (Embedded SQL), где SQL-код включается в исходный текст (базовой) программы, написанной на другом языке (например, С или Pascal); при использовании встроенного SQL результаты выполнения операторов SQL перенаправляются в переменные, которыми оперирует базовая программа.
• Динамический SQL также является частью приложения, но конкретный SQL-код генерируется во время выполнения приложения (RunTime), а не вводится заранее. Наибольшей гибкостью обладает последний вариант формирования текста SQL-запроса.
С помощью динамического SQL программа-клиент выполняет программное формирование оператора SQL для его последующего исполнения, делая это в три этапа:
-
программа собирает текст оператора SQL в виде символьной строки, хранящейся в программной переменной; в общем случае это может быть не один, а несколько операторов SQL, разделенных точкой с запятой;
-
программа выполняет оператор Prepare, который обращается к серверу баз данных для анализа текста оператора и подготовки его к выполнению;
-
программа использует оператор Execute для выполнения подготовленного оператора.
Динамический оператор SQL по форме напоминает любой другой оператор SQL, записанный в программе, с тем ограничением, что он не может содержать имена главных переменных. Оператор Prepare создает структуру данных, имеющую имя и отображающую строку символов с текстом оператора SQL.Подготовленный по оператору Prepare динамический оператор (группу операторов) можно многократно выполнять.
Ручная настройка Query (статические запросы)
С помощью свойства DataBase указать имя компонента IBDatabase. Свойство SQL должно содержать запрос, содержащий имя таблицы, с которой вы хотите работать. Редактор для создания запроса может быть вызван с помощью свойства SQL Инспектора объектов или с помощью команды EditSQL контекстного меню компонента. После написания запроса необходимо установить свойство Active=True. Для управления отображением данных имеется Редактор полей. Вызов, назначение, функции и приемы работы с ним аналогичны Редактору полей компонента Table.
Свойства Query аналогичны свойствам Table. Программный доступ к полям осуществляется с помощью свойства Fields (Query1.Fields[1]), методом FieldByName (Query1.FieldByName(Name)) или по имени объекта (Query1Name). Также можно осуществлять доступ к значениям полей, используя свойства Value, AsString и др., осуществлять навигацию по набору данных, устанавливать фильтры, ограничивать вводимые значения и т.д.
Методы Query. Перед изменением каких-либо свойств, влияющих на выполнение запроса, необходимо закрыть соединение, связанное с прежним запросом методом Close. Для выполнения запроса используются методы Open или ExecSQL в зависимости от вида запроса (см. выше).
Получить список имен полей таблицы, связанной с Query позволяет метод GetFieldNames, например: Query1. GetFieldNames(ComboBox1.Items).
Работа с параметрами. Динамические запросы позволяют использовать параметры, которые можно применять вместо имен таблиц, имен полей и их значений. Значения этих параметров передаются извне и тем самым, не меняя текст самого запроса, позволяют изменять возвращаемый им результат. Параметры задаются в запросе с двоеточием, предшествующем имени параметра. После ввода запроса, содержащего параметры, свойство Инспектора объектов Params, позволит открыть окно со списком всех параметров, указанных в запросе. Каждый из параметров является объектом типа TParam, свойства которого могут быть изменены в Инспекторе объектов. Основные свойства параметров следующие:
DataType – тип данных параметра
ParamType – тип параметра (используется при обращении к процедурам, хранимым на сервере)
Value – значение по умолчанию
Type – тип значения по умолчанию.
Программный доступ к параметрам аналогичен доступу к полям набора данных Query1.ParamByName(‘xSt’).AsString:=Edit1.text;.
Отображение всех данных отдельной таблицы с помощью компонента IBQuery, используя динамический SQL:
IBQuery1.Close;
IBQuery1.SQL.Clear;
IBQuery1.SQL.Add('select * from <имя_табицы>');
IBQuery1.Open
Имя таблицы может быть выбрано из списка имен таблицы, связанных с IBDataBase, полученного с помощью метода GetTableNames, например: IBDatabase1.GetTableNames(form1.DBComboBox1.Items). Поскольку SQL-запрос представляет собой строку, то для формирования текста запроса может быть использована строковая переменная:
Str:=’ select * from’;
…
Str:=Str+’<имя_таблицы>’
…
IBQuery1.SQL.Add(str);
…
Отображение данных из нескольких связанных таблиц базы данных
Для демонстрации возможности отображения данных из нескольких связанных таблиц необходимо предварительно разместить на форме компоненты отображения данных (DBGrid) и навигации для этих таблиц. Для работы потребуется два компонента IBQuery, текст запроса компонента, соответствующего родительской таблице. В тексте запроса компонента IBQuery, соответствующего дочерней таблице, необходимо указать условие отбора: Select * from <имя_таблицы> Where (NM=:NM) (значение ключевого поля NM должно быть равно параметру :NM). В данном случае параметр не надо определять с помощью Редактора параметров, вызываемого из свойства Params компонента IBQuery. Вместо этого в свойстве DataSource компонента IBQuery дочерней таблицы необходимо сослаться на источник данных DataSource, связанный с родительской таблицей. Это сообщит приложению, что оно должно взять значения параметра из текущей записи этого источника данных (имя параметра должно совпадать с именем поля в источнике данных).
Модификация данных при работе с компонентом IBQuery.
-
Компонент Query с запросом Select формирует таблицу только для чтения. Установка значения True в свойстве компонента RequestLive позволяет возвращать изменяемый набор данных, вместо таблицы только для чтения при соблюдении следующих условий:
-
- набор данных формируется обращением только к одной таблице;
-
- набор данных не упорядочен использованием Order By;
-
- набор данных не использует агрегатных функций.
-
-
Использование запросов Insert, Delete UpDate. Как уже упоминалось выше, запросы, не возвращающие наборов данных, выполняются методом ExecSQL:
-
Использование компонента UpdateSQL. Компонент UpdateSQL позволяет модифицировать наборы данных, открытые в режиме только для чтения. В свойстве SQL компонента IBQuery написать текст запроса для отображения всех записей таблицы: Метод ExecSQL осуществляет немедленную модификацию данных таблицы: Select * from <имя_таблицы>. Установить свойство компонента CachedUpdates равным True. Разместить в приложении компонент IBUpdateSQL и связать его с приложением, установив в компоненте IBQuery свойство UpdateObject в имя этого компонента. В свойствах компонента UpdateSQL DeleteSQL, InsertSQL и ModifySQL записать тексты запросов, которые должны выполняться при удалении, вставке или модификации записи. Запросы могут быть созданы с помощью редактора компонентов, вызываемого с помощью контекстного меню. При работе в этом редакторе после указания всех необходимых сведений на вкладке Options, для просмотра сгенерированного редактором запроса необходимо нажать кнопку Generate SQL. Запросы автоматически переносятся в соответствующие свойства компонента после нажатия на ОК. Раздел Set сгенерированного запроса указывается установка всех полей в значения, задаваемые соответствующими параметрами с именами, тождественными именам полей. В разделе Where содержатся условия, по которым идентифицируется модифицируемая запись. Префикс OLD_ , используемый в именах полей, используется для указания значений, полученных компьютером до модификации записи. При создании запросов для модификации и добавления записи имена полей, значения в которые проставляется с помощью триггера, использующего генератор, включать не надо. После выполнения всех описанных действий редактирование, добавление и удаление записей таблицы может быть выполнено с помощью компонента DBNavigator.
-
Использование хранимых на сервере исполняемых процедур. Для работы с хранимыми процедурами используется компонент IBStoredProc, который позволяет передавать информацию в процедуры и воспринимать возвращаемую информацию с помощью параметров. Для работы компонента необходимо установить его свойства: DatabaseName и StoredProcName. Свойства Params и ParamType заносятся автоматически после связывания с конкретной процедурой во время проектирования в соответствии с описанием процедуры.
-
пример
-
Работа с просмотрами и хранимыми процедурами выбора не требует никаких специальных команд для своего вызова. Вызов осуществляется обычной командой Select, в которой их имена фигурируют как имена обычных таблиц. Текст запроса, содержащего такие имена, записывается в свойстве SQL компонента Query.
Оператор select
Позволяет производить выборки данных, преобразовывать полученные результаты, реализует сложные условия выбора. Формат оператора:
Select [ distinct | all {* | <значение 1> [, <значение 2>… ]}
from <таблица1 > [, <таблица2 >… ]
[ where <условие_поиска>]
[ group by столбец [collate collation]
[, столбец 1 [collate collation]…]
[ having <условия_поиска>]
[ union <оператор_select>]
[ plan <план_выполнения_запроса>]
[ order by <список_столбцов>];
В простейшем случае, когда требуется просмотреть все записи одной или нескольких таблиц, оператор имеет вид:
Select {* | <значение 1> [, <значение 2>…]}
from <таблица 1> [, <таблица 2>…];
<значение 1>, <значение 2>… – имя столбца возвращаемого оператором, * – все столбцы
<таблица 1>, <таблица 2>… – имя таблицы, из которой происходит выборка данных.
Например, создать набор данных, состоящий из всех столбцов:
Select * from prihod;
Такой же набор данных можно получить:
Select n_prihod, date_prihod, name_det, kolvo, from prihod;
Предложение where используется для включения в БД лишь нужных записей, удовлетворяющих условию:
Select { * | <значение 1> [, <значение 2>… ]}
from <таблица 1> [, <таблица 2>…]
where <условия поиска>;
Сравнение с константой
При сравнении значения столбца с константой условие поиска имеет вид:
<имя_столбца> < оператор> < константа>
<оператор> – =, <, >, <= (!>), >=(!<), <>(!=).
Например, выбрать из таблицы prihod все операции приема товара объемом 20 единиц:
Select * from prihod
where kolvo=20;
Внутреннее соединение таблиц
При сравнении значения столбца одной таблицы со значением столбца другой таблицы условие поиска имеет вид:
<имя_столбца_табл1> <оператор> <имя_столбца_табл2>.
Например, выбрать все записи о приходе деталей из таблицы prihod и для каждой детали указать его цену из таблицы detal:
Select prihod.*, detal.zena_ed
from prihod, detal
where prihod.name_det = detal.name_det;
Для каждой записи из таблицы prihod ищется запись в таблице detal, у которой значение в поле name_det совпадает со значением name_det текущей записи таблицы prihod. Порядок перечисления в условии поиска значения не имеет:
Prihod.name-det = detal.name_det
Использование псевдонимов таблиц
Идентификация столбцов через имя таблицы неудобно из-за громоздкости обозначений. Лучше присвоить каждой таблице краткое имя. Такие имена называются псевдонимами таблиц. Они отделяются пробелом от фактического имени таблицы в списке from:
Select … from <таблица_1 псевдоним_1> [, <таблица_2 псевдоним_2> …]
where … ;
Например:
Select prihod.*, detal.zena_ed
from prihod P, detal D
where P.name_det = D.name_det;
Определение сортировки order by
Результирующий HД можно упорядочить с помощью предложения:
Order by <список_столбцов>.
Если в списке столбцов указано больше одного столбца, то первый будет использоваться для глобальной сортировки, второй – для сортировки внутри группы, определенной единым значением первого столбца, и т.д. Например, показать все записи приема деталей, отранжировать по имени детали:
Select name_det, zena_ed
from detal
where zena-ed >= 35;
order by name_det;
Устранение повторяющихся записей
Ключевое слово Distinct. Повторяющимися считаются записи, содержащие идентичные значения во всех столбцах результирующего HД. Если в результирующем HД нужно вносить все записи, то указывают ключевое слово All (по умолчанию All). Например, получить наименование всех деталей, полученных на склад:
Select distinct name_det
from prihod;
Расчет вычисляемых столбцов
Для расчета вычисляемых столбцов результирующего HД используются арифметические выражения:
Select [distinct | аll ] { * | <столбец 1> [, <выражение 1>… ]}
from <таблица 1> [, <таблица 2>… ];
Если столбцу надо присвоить нестандартное имя, то оно может быть указано за выражением при помощи ключевого слова As.
Например: рассчитать общую стоимость полученных деталей для каждого факта получения:
Select p.*, d.zena_ed, p.kolvo * d.zena_ed
as stoim
from prihod p, detal d
where p.name_det = d.name_det;
Агрегатные функции
count (<выражение>) – подсчитывает число вхождений значения выражения во все записи результирующего HД;
sum (<выражение>) – суммирует значение выражения;
avg (<выражение>) – находит среднее значение;
max (<выражение>) – определяет максимальное значение;
min (<выражение>) – определяет минимальное значение.
а) количество наименований деталей, оприходованных на список:
select count (distinct name_det) as count_name
from prihod;
б) вычислить общую стоимость оприходованных деталей за 4.10.00:
select sum (p.kolvo * d.zena_ed) as itogo
from prihod p, detal d
where (p.name_det = d.name_det) and (p.date_prihod = ‘04.10.00’);
Группировка записей
Для группы записей столбца, характеризующие одинаковые значения можно получить агрегированные значения (min, max, avg). При этом один из столбцов представляется агрегирующей функцией, и предложение group by столбец [, столбец…] перед предложением where.
Например, получить общее количество прихода деталей по каждой из них:
Select p.name_det sum (p.kolvo) as priem
from prihod p
group by p.name_det;
Или, общая цена на каждую деталь на каждую дату:
Select p.name_det, p.date_prihod, sum (p.kolvo * d.zena_ed) as sum
from prihod p, detal d
where p.name_det = d.name_det
group by p.name_det, p.date_prihod;
Предложение having
Наложение ограничений на группировку записей. Агрегация выдается только по группам удовлетворяющим условию.
Формат: после group by столбец [, столбец…]
Having <агрег_функц> <отношение> <значение>, где
Агрег_функц – min, max, avg, sum;
Отношение – =, <>, <, <=, >=;
Значение – константа, результат вычисления или единичное значение, возвращенное select.
Найти минимальный приход товара (деталей) не меньше 100 единиц.
Select name_det, min (kolvo)
from prihod
group by name_det
having min (kolvo) >=100;
Можно задавать разные функции для столбца и условия having.
Отличие where и having
В условие where нельзя вносить агрегированную функцию.
Where – исключает значения, не удовлетворяющие условию.
Having – исключает группы с агрегированными значениями.
Использование подзапросов
Часто невозможно решить поставленную задачу путем использования единственного запроса. Например, в тех случаях, когда при использовании условия поиска в предложении where значение с которым надо сравнить, далее не определено, а вычисляется оператором select.
В таких случаях применяют вложенные запросы или подзапросы.
Оператор select имеет вид:
Select …
from …
where <сравниваемое значение> <оператор> (select);
Пусть надо найти дату, на которую приходится максимальный приход деталей. Тогда запрос может
быть записан так:
Select kolvo, date_prihod from prihod
where kolvo = ( select max
(kolvo) from prihod);
Оператор select возвращает не одно значение, а список. Поэтому может возникнуть ошибка. Чтобы ее избежать надо заменить оператор = на оператор выбора из нескольких возможных значений (in).
Синтаксис вложенного запроса ничем не отличается от синтаксиса основного запроса. Это значит, что в подзапрос может быть вложен другой подзапрос и т.д.
Например, составим список получения деталей от поставщика, который в свое время поставил максимальную партию любой детали:
Select p0.* from prihod p0
where p0.post in
(select p1.post
from prihod p1
where kolvo in
(select max (p2.kolvo)
from prihod p2));
Сначала определим max, далее имя поставщика, а затем все записи, связанные с данным поставщиком.
Внешние соединения
Определяются в предложении from согласно спецификации:
Select { * | <значение 1> [, <значение 2>… ]}
from <таблица 1> <вид соединения > join < таблица 2>
on <условие поиска>;
Внешнее соединение отличается от внутреннего тем, что в результирующий HД включаются записи ведущей таблицы соединения, которые объединяются с пустым множеством записей другой таблицы. Какая из таблиц будет ведущей, определяет вид соединения (left – левое внешнее соединение, ведущая таблица 1; right – правое внешнее соединение, ведущая таблица 2; full – полное внешнее соединение).
В случае полного внешнего соединения ведущими являются обе таблицы. В результирующий HД вкладываются все записи обеих таблиц согласно алгоритму:
1) если для записи таблицы 1 имеются записи таблицы 2, удовлетворяющие условию соединения, то в результирующий HД включаются все комбинации записей таблиц 1 и 2;
2) иначе, в HД включается запись из таблицы 1, соединенная с пустой записью таблицы 2;
3) пункты 1 и 2 повторяются для таблицы 2 и таблицы 1.
Пример. Пусть имеем таблицы A и B.
P1 |
P2 |
P3 |
|
P1 |
P2
|
a |
x |
400 |
|
x |
1 |
b |
x |
200 |
|
y |
2 |
c |
y |
500 |
|
z |
2 |
d |
|
|
|
|
|
Тогда выполнение оператора
Select A.P1, A.P2, B.P2
from A left join B
on A.P2 = B.P1;
A.P1 A.P2 A.P2
a x 1
b x 1
c y 2
d – –
A.P1 A.P2 A.P2
a x 1
b x 1
с y 2
– – 2
Запрос внешнего правого соединения:
Select A.P1, A.P2, B.P2
from A right join B
on A.P2 = B.P1;
дает другой результат:
Выполнение оператора полного внешнего соединения таблиц A и B:
Select A.P1, A.P2, B.P2
from A full join B
on A.P2 = B.P1;
приведет к результату:
В запросах к БД эта операция может быть использована, например, когда требуется найти поставщика, соответствующего каждой поставке детали или найти все поставки по каждому поставщику и т.п.
Select p.date_prihod, p.name_det, p.kolvo, p1.post, p1.gorod
from prihod p left join postаvshik p1
on p.name_det = p1.name_det;
или
Select p.date_prihod, p.name_det, p.kolvo, p1.post, p1.gorod
from prihod p right join postаvshik p1
on p.name_det = p1.name_det;
Объединение результатов нескольких операторов select.
Объединение производится оператором union. Результирующие НД должны иметь одинаковую структуру. Одинаковые записи не дублируются.
Произведем объединение трех результирующих наборов данных:
select p.*
from prihod p
where p.name_det containing ‘3’;
union
select p.*
from prihod p
where p.kolvo > 100;
union
select p.*
from prihod p
where p.post = ‘AMD’;
Операции реляционной алгебры