Практическое занятие № 21
Тема: "Обработка таблиц с помощью запросов"
Цель работы: получить практические навыки по использованию запросов разных типов при работе с БД
Ход работы
Скачайте БД в формате Microsoft Access. , которая содержит таблицы grupy и students, связанные по полю id_grup.
Таблица grupy имеет структуру:
Имя поля | Тип | Размер |
id_grup | integer | ключевое, счетчик |
grup_sokr | varchar | 3 |
grup | varchar | 20 |
klruk | varchar | 50 |
kurs | integer |
|
Таблица students имеет структуру:
Имя поля | Тип | Размер |
|
id_stud |
integer |
ключевое, счетчик |
inn | integer | уникальное |
fam | varchar | 15 |
imya | varchar | 15 |
otch | varchar | 15 |
datar | date |
|
srbal | decimal | 2 знака после запятой |
id_grup | integer | внешний ключ для связывания |
В среде Delphi создайте программу для работы с данными таблиц. При этом форма должна иметь вид:

Указания: для формы задайте свойства: Caption=Работа с запросами, Position=DesktopCenter, Borderstyle=bsSingle, BorderIcons-biMaximized=false (скрывает кнопку разворачивания на весь экран).
Подключение главной таблицы
На форму нанесите компонент ADOConnection (ADO). В свойстве ConnectionString укажите параметры подключения к БД Access. В свойстве LoginPrompt укажите false, чтобы программа не запрашивала логин и пароль при соединении с БД.
Нанесить на форму компонент ADOTable (ADO). В свойстве Connection укажите соединение с БД ADOConnection1, в свойстве TableName укажите таблицу grupy.
Для того, чтобы программа корректно подключала таблицу в событии формы OnCreate введите код:
procedure TForm1.FormCreate(Sender: TObject);
begin
ADOTable1.Active:=true;
end;
Для того, чтобы программа корректно отключала таблицу в свойстве формы OnDestroy введите код:
procedure TForm1.FormDestroy(Sender: TObject);
begin
ADOConnection1.Connected:=false;
end;
Для отображения данных в событии формы OnCreate введите код:
procedure Tform1.Formcreate(Sender: TObject);
begin
ADOTable1.Active:=true;
end;
Для того, чтобы программа корректно отключалась от БД , в событии формы OnDestroy введите код:
procedure Tform1.FormDestroy(Sender: TObject);
begin
ADOConnection1.Connected:=false;
end;
Нанесите на форму компонент DataSource (DataAccess). Установите его свойство DataSet =ADOTable1.
Нанесите на форму компонент DBGrid (DataControls). В свойстве DataSource укажите имя компонента DataSource1.
Подключение подчиненной таблицы
По умолчанию подчиненная таблица отображает только те записи, которые соответствую группе, выбранной в главной таблице. Для реализации загрузки только нужного набора данных (что соответствует принципу работы по технологии клиент/сервер), будем отображать данные с помощью запроса.
На форму нанесите компонент ADOQuery (ADO). Для компонента задайте свойства: Connection=ADOConnection1, SQL = select * from students where id_grup=:id_grup.
Запрос имеет параметр :id_grup, для которого нужно задать правильный тип данных. Для этого откройте свойство Parameters компонента ADOQuery, выберите нужный параметр и укажите тип Integer в свойствах DataType и Value - Type.
Добавьте в запрос все поля, переименуйте и задайте нужную ширину. Поле id_grup связано с полем главной таблицы (заполняется автоматически), поэтому его можно не показывать (Visible=false). Аналогично скройте поле id_stud, которое как счетчик также заполняется автоматически.
Нанесите на форму компонент DataSource (DataAccess). Установите его свойство DataSet =ADOQuery1.
Нанесите на форму компонент DBGrid (DataControls). В свойстве DataSource укажите имя компонента DataSource2.
При переходе по группам в главной таблице в подчиненной нужно обновлять запрос так, чтобы из БД загружался тот список студентов, который соответствует текущей группе.
В событии AfterScroll для компонента ADOTable1 введите код:
procedure TForm1.ADOTable1AfterScroll(DataSet: TDataSet);
begin
//выключаем запрос
ADOQuery1.Active:=false;
//в текст запроса передаем новый номер группы из главной таблицы
ADOQuery1.Parameters.ParamByName('id_grup').Value:=AdoTable1.FieldByName('id_grup').Value;
//выключаем запрос
ADOQuery1.Active:=true;
end;
Каждая сетка DBGrid по умолчанию поддерживает специальные клавиши: Insert - додавить новую запись над текущей, Ctrl+Delete - удалить текущую запись (можете проверить эти возможности). Нужно заблокировать эти клавиши:
Указания: для того, чтобы форма могла реагировать и обрабатывать нажатия клавиш установите для формы свойство KeyPreview=true.
Для формы в событии OnKeyDown напишем код:
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
//если в момент нажатия на клавиши одна из сеток активна
if (DBGrid1.Focused) or (DBGrid2.Focused) then
//если нажата клавиша Insert
//или клавиши Ctrl+Insert
if (Key=VK_Insert) or (Key=VK_Delete) and (sSctrl in Shift) then
//то это нажатие
игнорируется
Abort;
end;
Под каждой из сеток добавим кнопки для добавления и удаления данных.
Указания: под главной сеткой разместите два компонента Button (Standard) и в свойстве Caption введите текст надписей на кнопках.
Для кнопки "Добавить" введите код:
procedure TForm1.Button1Click(Sender: TObject);
begin
//добавляем новую запись
ADOTable1.Append;
//ставим курсор в поле grup_sokr (первое поле в сетке)
ADOTable1.FieldByName('grup_sokr').FocusControl;
end;
Для кнопки "Удалить" напишем код:
procedure TForm1.Button2Click(Sender: TObject);
begin
//если данных для удаления нет, то выход
if ADOTable1.RecordCount=0 then Exit;
//показываем запрос на удаление
if Application.MessageBox(PChar('Удалить группу '''+
ADOTable1.FieldByName('grup').AsString+'''?'),
'Подтвердите',MB_YesNo+MB_IconQuestion)=IdYes then
//если ответ положительный, то удаляем запись
ADOTable1.Delete;
end;
Под подчиненной сеткой разместите два компонента Button (Standard) и в свойстве Caption введите текст надписей на кнопках.
Для кнопки "Добавить" введите код:
procedure TForm1.Button3Click(Sender: TObject);
begin
//добавляем новую запись
ADOQuery1.Append;
//ставим курсор в поле inn (первое поле в сетке)
ADOQuery1.FieldByName('inn').FocusControl;
end;
Так как подчиненная таблица строится на основании запроса и не связана с главной таблицей с помощью свойств Master Source, Master Fields, то перед сохранением записи в подчиненной таблице нужно программно заполнять поле id_grup номером группы из главной сетки.
Для компонента ADOQuery1 в с обытии BeforPost введите код:
procedure TForm1.ADOQuery1BeforePost(DataSet: TDataSet);
begin
ADOQuery1.FieldByName('id_grup').Value:=ADOTable1.FieldByName('id_grup').Value;
end;
Для кнопки "Удалить" напишем код:
procedure TForm1.Button4Click(Sender: TObject);
begin
//если данных для удаления нет, то выход
if ADOQuery1.RecordCount=0 then Exit;
//показываем запрос на удаление
if Application.MessageBox(PChar('Удалить студента '''+
ADOQuery1.FieldByName('fam').AsString+' '+
ADOQuery1.FieldByName('imya').AsString+' '+
ADOQuery1.FieldByName('otch').AsString+'''?'),
'Подтвердите',MB_YesNo+mb_IconQuestion)=IdYes then
//если ответ положительный, то удаляем запись
ADOQuery1.Delete;
end;
Реализуем просмотр данных по группам или целым списком. На форме создайте группу переключателей.
![]()
Если выбран первый переключатель (выбран по умолчанию), то в подчиненной сетке отображаются связанные данные. Если выбран второй переключатель, то в подчиненной сетке отображаются све записи одним списком без привязки к главной таблице.
Указания: для отображения всех данных из подчиненной таблицы нанесите на форму компонент ADOTable и задайте свойства: Connection = ADOConnection1, TableName = students.
добавьте на форму компонент RadioGroup (Standard) і задайте свойства Caption=Режим просмотра, Columns=2 (количество колонок), Items - названия переключателей, ItemIndex=0 (первый переключатель включен по умолчанию).В событии OnClick компонента напишем код:
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
case RadioGroup1.ItemIndex of
//если выбран первый переключатель (по группам)
//подключаем источник DataSource подчиненной сетки к запросу
0: begin
ADOTable2.Active:=false;
DataSource2.DataSet:=ADOQuery1;
end;
//если выбран второй переключатель (общим
списком)
//подключаем источник DataSource подчиненной сетки к запросу
1: begin
ADOTable2.Active:=true;
DataSource2.DataSet:=ADOTable2;
end;
end;
end;
Реализуем сортировку данных в подчиненной сетке при щелчке на заголовках сетки.
Указания: в практическом занятии № 11 был показан пример сортировки данных с помощью свойства ADOTable.IndexFieldNames. В нашем примере данные в подчиненной сетке отображаются с помощью компонента ADOQuery (при просмотре по группам) или с помощью компонента ADOTable (при просмотре общим списком). Компонент ADOQuery не имеет свойства IndexFieldNames, но имеет похожее свойство Sort.
В событии OnTitleClick подчиненной сетки введите код:
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
begin
//если источник данных для подчиненной сетки - ADOQuery1
if DataSource2.DataSet=ADOQuery1 then
//сортируем данные в ADOQuery по возрастанию или по убыванию
if ADOQuery1.Sort=Column.FieldName + ' DESC' then
ADOQuery1.Sort:=Column.FieldName
else
ADOQuery1.Sort:=Column.FieldName+' DESC'
//иначе, если источник данных для подчиненной сетки - ADOTable1
else
//сортируем данные в ADOTable по возрастанию или по убыванию
if ADOTable2.IndexFieldNames=Column.FieldName + ' DESC' then
ADOTable2.IndexFieldNames:=Column.FieldName
else
ADOTable2.IndexFieldNames:=Column.FieldName+' DESC';
end;
Реализуем функцию увеличения или уменьшения среднего балла студентов на заданный процент с помощью запроса на обновление данных. При этом на форме нужно создать конструкцию вида:

Пользователь в поле вводит нужный процент.
Указания: разместите на форме нужные компоненты.
Для обновления данных будем использовать запрос, задаваемый через запрос. нанесите на форму компонент ADOQuery (ADO) и задайте свойство Connection = ADOConnection1.
Для кнопки введите код:
procedure TForm1.Button5Click(Sender: TObject);
begin
//выдаем запрос на обновление
if Application.MessageBox('Ви подтверждаете обновление данных?',
'Подтвердите',MB_YesNo+MB_IconQuestion)=IdYes then
//если пользователь ответил утвердительно
begin
//формируем запрос на обновления поля srbal
ADOQuery2.Sql.Text:='update students set srbal = srbal + srbal * '+Edit2.Text+' / 100';
//выполняем запрос
ADOQuery2.ExecSql;
//обновляем данные в подчиненной сетке для просмотра результата
ADOQuery1.Requery();
end;
end;
Реализовать на форме информационную панель для отображения общих показателей как по всем студентам (синий цвет) так и по студентам в каждой группе (красный цвет).

Указания: нанесите на форму компонент Groupbox (Standard) и в свойстве Caption введите название панели.
На панель нанесите 4 компонента Label2 - Label5 (Standard), разместите ее левее на панели и в свойстве Font задайте синий цвет текста.
На панель нанесите 4 компонента Label6 - Label9 (Standard), разместите ее правее на панели и в свойстве Font задайте красный цвет текста.
Для выполнения расчетов показателей будем использовать запрос, который можно выполнить с помощью созданного ранее компонента ADOQuery2.
Расчет нужно будет обновлять в нескольких событиях, поэтому оформим его отдельной процедурой.
В верхней части кода формы найдите блок описания заголовков процедур и добавьте заголовок своей процедуры:
procedure Schet;
После ключевого слова Implementation введите код процедуры:
procedure TForm1.Schet;
begin
//выполняем расчет по всем записям таблицы
ADOQuery2.Active:=false;;
//формируем запрос на расчет показателей
//обратите внимание на округление каждой функции в запросе
//с помощью стандартной функции round
ADOQuery2.Sql.Text:='select count(inn) as kolvo, round(max(srbal),2) as maxbal, round(min(srbal),2) as minbal, round(avg(srbal),2) as sredbal from students';
//активируем запрос
ADOQuery2.Active:=true;
//в надписях на панели отображаем найденные значения
Label2.Caption:='Общее число студентов: '+
VarToStr(ADOQuery2.FieldByName('kolvo').Value);
Label3.Caption:='Общий максимальный балл: '+
VarToStr(ADOQuery2.FieldByName('maxbal').Value);
Label4.Caption:='Общий минимальный балл: '+
VarToStr(ADOQuery2.FieldByName('minbal').Value);
Label5.Caption:='Общий средний балл: '+
VarToStr(ADOQuery2.FieldByName('sredbal').Value);
//================================================
//выполняем расчет по студентам отдельной группы
ADOQuery2.Active:=false;
//если номер активной группы не пустой (группа не новая)
if ADOTable1.FieldByName('id_grup').AsString<>'' then
//формируем запрос на расчет показателей текущей группы
//обратите внимание на округление каждой функции в запросе
//с помощью стандартной функции round
ADOQuery2.Sql.Text:='select count(inn) as kolvo, round(max(srbal),2) as maxbal, round(min(srbal),2) as minbal, round(avg(srbal),2) as sredbal from students where id_grup='+ADOTable1.FieldByName('id_grup').AsString
//если номер группы пустой (группа нова)
else
//формируем запрос, который отберет пустые значения
//и на форме ничего не отобразиться
ADOQuery2.Sql.Text:='select null as kolvo, null as maxbal, null as minbal, null as sredbal from students';
//активируем запрос и отбираем данные
ADOQuery2.Active:=true;
//в надписях на панели отображаем найденные значения
Label6.Caption:='Общее число студентов в группе: '+
VarToStr(ADOQuery2.FieldByName('kolvo').Value);
Label7.Caption:='Общий максимальный балл в группе: '+
VarToStr(ADOQuery2.FieldByName('maxbal').Value);
Label8.Caption:='Общий минимальный балл в группе: '+
VarToStr(ADOQuery2.FieldByName('minbal').Value);
Label9.Caption:='Общий средний балл в группе: '+
VarToStr(adoquery2.FieldByName('sredbal').Value);
end;
Для вызова созданной процедуры используют команду:
Schet;
Созданную процедуру нужно вызывать в нескольких событиях:
- для формы: OnShow (при открытии формы);
- для компонента ADOTable1 - главная таблица: AfterDelete (после удаления группы удаляются и студенты, значит показатели нужно пересчитать показатели), AfterScroll (после перехода по группам в подчиненной сетке меняется список студентов, значит показатели нужно пересчитать показатели);
- для компонента ADOQuery1 - подчиненная таблица: AfterPost (после добавления или изменения данных о студенте), AfterDelete (после удаления данных о студенте);
- для кнопки "Изменить" после обновления поля srbal нужно пересчитать показатели.