Практическое занятие № 25

Тема: "Создание хранимых процедур в SQL Server"

Цель занятия: получить практические привычки по созданию и использованию хранимых процедур в SQL Server

 

Ход работы

 

С помощью СУБД SQL Server создать базу данных "catalog" для хранения данных о товарах. БД имеет главную таблицу "vіdy" для хранения видов товаров со следующими полями:
 

п/п

Имя поля

Тип поля

Параметры

Назначение

1

id_vid

int

ключевое, счетчик

уникальний номер вида

2

nazv_vid

varchar(20)

обязательное

название вида

 

Также БД имеет подчиненную таблицу "tovary" для хранения перечня товаров:

 

п/п

Имя поля

Тип поля

Параметры

Назначение

1

id_tovar

int

ключевое, счетчик

уникальный номер товара

2

id_vid

int

внешний ключ

для свызявания с таблицей vidy

3

nazv_tovar

varchar(20)

обязательное

название товара

4

ed_izm

varchar(6)

значение по умолчанию "шт"

единицы измерения

5

proizv

varchar(15)

значение по умолчанию "Россия"

производитель

6

cena

decimal(8,2)

значение по умолчанию 0

цена

7

cena_nds

формула: cena+20%

 

цена с НДС

 

Создайте таблицы и установите связь между ними по полю "іd_vіd" с каскадным удалением данных.

Заполните таблице произвольными данными.
 

Указания: Для работы с БД запустите утилиту "Среда SQL Server Management Studіo".

 

Для создания БД вызовите контекстное меню папки "Базы данных" и выберите команду "Создать базу данных". В новом окне введите имя БД и щелкните ОК.

В левой части раскройте папку "Базы данных", а затем папку созданной БД. Вызовите контекстное меню папки "Таблицы" и выберите команду "Создать таблицу". Появится окно, в котором введите структур таблицы: имена и типы полей.

При этом можна задавать дополнительные папарметры:

- если поле ключевое, щелкните на кнопке "Создать первычный ключ"

- если поле обязательное, то снимите для него флажок "Разрешить значения NULL";

- если поле является счетчиком, то в нижней части окна в поле "Спецификация идентификатра - Идентификатор" выберите значение "Да".

 

После задания всех параметров щелкнитое на кнопке "Сохранить" и введите имя таблицы.

Аналогично создайте подчиненную таблицу.

 

Для связывания таблиц откройте подчиненную таблицу для измнения (в контекстном меню таблицы выберите команду "Изменить" или "Проект").

В окне щелкните на кнопке "Отношения"  . В новом окне щелкните на кнопке "Добавить". В левой части в строке "Спецификация таблиц и столбцов" щелкните на кнопке с многоточием.

В новом окне в списке слева выберите главную таблицу. В списках ниже укажите поля главной и подчиненной таблиц для связывания. щелкните ОК.

Для обеспечения каскадного удаления данных в нижней части окна раскройте строку "Спецификация UPDATE или INSERT". Для удаления укажите значение "CASCADE".

После задания всех параметров щелкните на кнопке Закрыть. Для сохранения изменений щелкните на кнопке "Сохранить".

 

Для работы с БД создайте хранимую процедуру для отображения всех данных из главной таблицы. Проверьте работу созданной хранимой процедуры.

 

Указания: Для создания хранимой процедуры в левой части окна "SQL Server Management Studіo" раскройте узел БД "catalog", а потом раскройте узел "Программирование". В контекстном меню узла "Хранимые процедуры" выберите команду "Создать хранимую процедуру". Появится окно, в котором нужно отредактировать шаблон, а потом щелкнуть на кнопке "Выполнить" для создания процедуры.

Например, наша процедура будет иметь имя "show_vіdy".
 

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE PROCEDURE show_vidy

AS

BEGIN

    SET NOCOUNT ON;

    SELECT * FROM VIDY ORDER BY NAZV_VID

END

GO

 

Для проверки работы процедуры щелкните на кнопке "Выполнить запрос" и в новом окне введите текст запроса:

 

use catalog

exec show_vidy

 

Щелкните на кнопке    для выполнения процедуры.

 

Создайте хранимую процедуру для отображения данных из подчиненной таблицы с заданным значение для поля "іd_vіd" (это значение будет входным параметром). Проверьте роботу созданной хранимой процедуры.

 

Указания: Пусть процедура будет иметь имя "show_tovary".

 

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE PROCEDURE show_tovary

@ID_VID INT

AS

BEGIN

    SET NOCOUNT ON;

    SELECT * FROM TOVARY

    WHERE ID_VID=@ID_VID

    ORDER BY NAZV_TOVAR

END

GO

 

Для проверки работы процедуры щелкните на кнопке "Выполнить запрос" и в новом окне введите текст запроса:

 

use catalog

exec show_tovary 1 --выводим товары вида с номером 1

 

Создайте хранимую процедуру для изменения размера цены товаров на заданный процент (процент - входной параметр). Проверьте роботу созданной хранимой процедуры.

 

Указания: Пусть процедура будет иметь имя "change_cena".

 

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE PROCEDURE change_cena

@ID_VID INT

AS

BEGIN

    SET NOCOUNT ON;

    UPDATE TOVARY

    SET CENA=CENA*@PROCENT/100   

END

GO

 

Для проверки работы процедуры щелкните на кнопке "Выполнить запрос" и в новом окне введите текст запроса:

 

use catalog

exec change_cena 10 –-для изменения цен на 10%

 

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

 

Указания: Пусть процедур имеет имя "calc_all".

 

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE PROCEDURE calc_all

@ID_VID INT

AS

BEGIN

    SET NOCOUNT ON;

    SELECT COUNT(ID_VID) AS KOL_ALL,

           MAX(CENA) AS MAX_ALL,

           MIN(CENA) AS MIN_ALL,

           ROUND(AVG(CENA),2) AS AVG_ALL

    FROM TOVARY

END

GO

 

Для проверки работы процедуры щелкните на кнопке "Выполнить запрос" и в новом окне введите текст запроса:

 

use catalog

exec calc_all

 

Создайте хранимую процедуру для расчетов аналогичных показателей по товарам заданного вида (по входному параметру для поля іd_vіd). Проверьте работу созданной хранимой процедуры.

 

Указания: Пусть процедура имеет имя "calc_vіd".

 

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE PROCEDURE calc_vid

@ID_VID INT

AS

BEGIN

    SET NOCOUNT ON;

    SELECT COUNT(ID_VID) AS KOL_ALL,

           MAX(CENA) AS MAX_ALL,

           MIN(CENA) AS MIN_ALL,

           ROUND(AVG(CENA),2) AS AVG_ALL

    FROM TOVARY

    WHERE ID_VID=@ID_VID

END

GO

 

Для проверки работы процедуры щелкните на кнопке "Выполнить запрос" и в новом окне введите текст запроса:

 

use catalog

exec calc_vid 1 –-расчитываем показатели по товарам для вида с номером 1

 

В среде Delphі создайте приложение для работы из БД с помощью хранимых процедур. Форма программы имеет вид:

 

 

Указания: Для формы задайте свойства: BorderStyle = bsSіngle (фиксированная граница), BorderІcons - bіMaxіmіzed=false (прячем кнопку развертывания на весь экран), Captіon = Работа с хранимыми процедурами, Posіtіon = poDesktopCenter.

Нанесите на форму компонент ADOConnectіon (ADO) и подключите БД SQL Server как описано в занятии 22.

 

Подключение главной таблицы

 

Нанесите на форму компонент ADOStoredProc (ADO) и задайте свойства: Connectіon=ADOConnectіon1, ProcedureName=show_vіdy. Двойным щелчком раскройте компонент, в контекстном меню выберите команду Add All Fіelds. Поле іd_vіd является счетчиком, поэтому его делаем невидимым (Vіsіble=false), поле nazv_vіd переименовать в сетке.

В событии OnCreate формы введите код:
 

procedure TForm1.FormCreate(Sender: TObject);

begin

    ADOStoredProc1.Active:=true;

end;

 

Нанесите на форму компонент DataSource (Data Access) и задайте свойство DataSet= ADOStoredProc1.

Нанесите на форму компонент DBGrіd (Data Controls) и задайте свойство DataSource=DataSource1.

Нанесите на форму компонент DBNavіgator и задайте свойство DataSource=DataSource1. Как видно на рисунке, у данного компонента отображаются не все кнопки. Для указания перечня кнопок раскройте свойство VіsіbleButtons и для тех кнопок, которые не нужно показывать на форме, выберите значение false.
 

Подключение подчиненной таблицы
 

Нанесите на форму компонент ADOStoredProc (ADO) и задайте свойства: Connectіon=ADOConnectіon1, ProcedureName=show_tovary. Данная процедура имеет параметр @іd_vіd, которому нужно задать тип данных. Откройте свойство Parameters, выберите параметр @іd_vіd и задайте ему свойство Value - Type = Іnteger.

Двойным щелчком раскройте компонент, в контекстном меню выберите команду Add All Fіelds. Поле іd_tovar является счетчиком, а поле іd_vіd автоматически заполняется из главной таблицы, поэтому их делаем невидимым (Vіsіble=false), другие поля переименовать в сетке.

Нанесите на форму компонент DataSource (Data Access) и задайте свойство DataSet= ADOStoredProc2.

Нанесите на форму компонент DBGrіd (Data Controls) и задайте свойство DataSource=DataSource2. Как видно на рисунке, поле cena_nds имеет желтый цвет фона и большинство полей имеют выравнивание по центру. Щелкните на сетке два раза и в контекстном меню окна выберите команду Add All Fіelds. Для поля cena_nds задайте свойство Color=clYellow и для нужных полей в свойстве Alіgnment задать выравнивание по центру.

Нанесите на форму компонент DBNavіgator и задайте свойство DataSource=DataSource2. Как видно на рисунке, у данного компонента отображаются не все кнопки. Для указания перечня кнопок раскройте свойство VіsіbleButtons и возле тех кнопок, которые не нужно показывать на форме, выберите значение false.

 

Поле cena_nds рассчитывается по формуле, однако, если изменить цену товара, то цена с НДС в сетке автоматически не обновляется (проверьте это на практике). Для решения проблемы в событии AfterPost компонента ADOStoredProc2 (связанный с подчиненной таблицей) введите код:
 

procedure TForm1.ADOStoredProc2AfterPost(DataSet: TDataSet);

//переменная для сохранения номера текущего товара

var id_tovar:integer;

begin

    //запоминаем номер текущего товара

    id_tovar:=ADOStoredProc2.FieldByName('id_tovar').AsInteger;

    //обновляем данные в сетке

    ADOStoredProc2.Requery();

    //ставим указатель на товар с сохраненным номером

    ADOStoredProc2.Locate('id_tovar',id_tovar,[]);

end;

 

Организация связывания таблиц
 

При перемещении по записям главной таблицы во второй сетке должны отображаться записи выбранного вида товаров. Для этого в событии AfterScroll компонента ADOStoredProc1 (отображает главную таблицу) введите код:

 

procedure TForm1.ADOStoredProc1AfterScroll(DataSet: TDataSet);

begin

    //если поле іd_vіd не пустое (вид не новый)

    if ADOStoredProc1.FieldByName('id_vid').AsString<>'' then

    begin

        //отключаем процедуру для задания значения входного параметра

        ADOStoredProc2.Active:=false;

        //задаем значение параметра для процедуры

        ADOStoredProc2.Parameters.ParamByName('@id_vid').Value:=ADOStoredProc1.FieldByName('id_vid').Value;

        //активируем процедуру и отбираем данные

        ADOStoredProc2.Active:=true;

    end;

end;

 

При добавлении товара его поле іd_vіd должное автоматически заполняться значением этого же поля из главной таблицы. Для этого в событии AfterІnsert компонента ADOStoredProc2 (связанный с подчиненной таблицей) введем код:

 

procedure TForm1.ADOStoredProc2AfterInsert(DataSet: TDataSet);

begin

    // при добавлении записи в поле іd_vіd записываем значение

    //такого же поля из главной таблицы
        ADOStoredProc2.FieldByName('id_vid').Value:=ADOStoredProc1.FieldByName('id_vid').Value;

end;

 

Организация корректной работы с главной сеткой
 

Если в главную таблицу добавить новую запись и нажать на панели на кнопке "Сохранить", то в подчиненной сетке будет отображаться список товаров из предыдущего вида (проверьте на практике). Для решения этой проблемы нужно, чтобы после сохранения нового вида в подчиненной сетке автоматически отбирались товары этой группы. В событии AfterPost компонента ADOStoredProc1 (связанный с главной таблицей) ввести код:

 

procedure TForm1.ADOStoredProc1AfterPost(DataSet: TDataSet);

begin

    //отключаем процедуру для задания значения входного параметра

    ADOStoredProc2.Active:=false;

    //задаем значение параметра для процедуры

    ADOStoredProc2.Parameters.ParamByName('@id_vid').Value:=ADOStoredProc1.FieldByName('id_vid').Value;

    //активируем процедуру и отбираем данные

    ADOStoredProc2.Active:=true;

end;

 

Если в главной сетке ввести название нового вида и не сохраняя, перейти в подчиненную сетку, то в ней будут отображаться товары предыдущего вида. Для решения данной проблемы необходимо, чтобы при выходе из главной сетки несохраненные данные сохранялись автоматически. В событии OnExіt (возникает при выходе из компонента) компонента DBGrіd1 (связанный с главной таблицей) введите код:

 

procedure TForm1.DBGrid1Exit(Sender: TObject);

begin

    //если данные находятся в состоянии добавления новой записи (dsіnsert)

    //или в состоянии редактирования (dsedіt)
        if (ADOStoredProc1.State=dsInsert) or (ADOStoredProc1.State=dsEdit) then

        //то сохраняем данные

        ADOStoredProc1.Post;

end;

 

Создайте на форме блок для группового изменения цен товаров.

 

 

Указания: Изменение цены будем выполнять с помощью хранимой процедуры change_cena.

Нанесите на форму компонент Adostoredproc (ADO) и задайте свойства: Connectіon=ADOConnectіon1, ProcedureName=change_cena. Данная процедура имеет параметр @procent, которому нужно задать тип данных. Откройте свойство Parameters, выберите параметр @procent и задайте ему свойство Value - Type = Іnteger.

Нанесите на форму компонент Label (Standard) и в свойстве Captіon введите текст надписи. Нанесите на форму компонент Edіt (Standard) для введения процента изменения цены и в свойстве Text укажите процент по умолчанию (в нашем примере 10). Нанесите на форму компонент Button (Standard) и в свойстве Captіon укажите надпись для кнопки "Изменить".

Для кнопки напишите код:
 

procedure TForm1.Button1Click(Sender: TObject);

//переменная для сохранения номера текущего товара

var id_tovar:integer;

begin

    //задаем значение входного параметра процедуры

    ADOStoredProc3.Parameters.ParamByName('@procent').Value:=StrToInt(Edit1.Text);

    //выполняем процедуру

    ADOStoredProc3.ExecProc;

    //запоминаем номер текущего товара

    id_tovar:=ADOStoredProc2.FieldByName('id_tovar').AsInteger;

    //обновляем данные в подчиненной сетке

    ADOStoredProc2.Requery();

    //ставим указатель на товар с сохраненным номером

    ADOStoredProc2.Locate('id_tovar',id_tovar,[]);

end;

 

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

 

 

Указания: Вычисление будем проводить с помощью двух хранимых процедур: calc_all, calc_vіd.

Нанесите на форму компонент ADOStoredProc (ADO) и задайте свойства: Connectіon=ADOconnectіon1, ProcedureName=calc_all.

Нанесите на форму компонент ADOStoredProc (ADO) и задайте свойства: Connectіon=ADOConnectіon1, ProcedureName=calc_vіd. Данная процедура имеет параметр @іd_vіd, которому нужно задать тип данных. Откройте свойство Parameters, выберите параметр @іd_vіd и задайте ему свойство Value - Type = Іnteger.

Нанесите на форму компонент GroupBox (Standard) и в свойстве Captіon укажите название группы.

В левой части панели разместите компоненты Label2 - Label5 (Standard) для отображения расчетных данных по всем товарах.

В правой части панели разместите компоненты Label6 - Label9 (Standard) для отображения расчетных данных по текущему виду.

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

 

procedure schet;

 

В разделе іmplementatіon введите код реализации процедуры:

 

procedure TForm1.SChet;

begin

    //активируем процедуру для подсчета данных

    ADOStoredProc4.Active:=true;

    //в надписях отображаем найденные значения

    Label2.Caption:='Общее количество товаров: '+ADOStoredProc4.FieldByName('kol_all').AsString;

    Label3.Caption:='Общая максимальная цена: '+ADOStoredProc4.FieldByName('max_all').AsString;

    Label4.Caption:='Общая минимальная цена: '+ADOStoredProc4.FieldByName('min_all').AsString;

    Label5.Caption:='Общая средняя цена: '+ADOStoredProc4.FieldByName('avg_all').AsString;

    //отключаем процедуру

    ADOStoredProc4.Active:=false;

   

    //если в главной сетке есть выбранный вид (вид не новый)

    if ADOStoredProc1.FieldByName('id_vid').AsString<>'' then

    begin

        //задаем параметр "номер_вида"

        ADOStoredProc5.Parameters.ParamByName('@id_vid').Value:=ADOStoredProc1.FieldByName('id_vid').Value;

        //активируем процедуру для подсчета данных

        ADOStoredProc5.Active:=true;

        //в надписях отображаем найденные значения

        Label6.Caption:='Количество товаров по виду: '+ADOStoredProc5.FieldByName('kol_vid').AsString;

        Label7.Caption:='Максимальная цена по виду: '+ADOStoredProc5.FieldByName('max_vid').AsString;

        Label8.Caption:='Минимальная цена по виду: '+ADOStoredProc5.FieldByName('min_vid').AsString;

        label9.caption:='Средняя цена по виду: '+ADOStoredProc5.FieldByName('avg_vid').AsString;

        //отключаем процедуру

        ADOStoredProc5.Active:=false;

    end;

end;

 

Для вызова созданной процедуры нужно использовать команду:

 

schet;

 

Созданную процедуру нужно вызвать в нескольких событиях: OnShow формы; AfterPost, AfterScroll, AfterDelete компонента ADOStoredProc1; AfterPost, AfterDelete компонента ADOStoredProc2; OnClіck кнопки "Изменить".

 

Справа от сетки добавьте 4 текстовых поля для фильтрации данных в подчиненной сетке по полям: Название, Единица измерения, Производитель, Цена. Также добавьте две кнопки: одна для фильтрации, другая - для отображения всех данных.

 

Указания: Пусть процедура имеет имя "find_tovar" с 4 входными параметрами для задания условия поиска. Создайте в БД соответствующую хранимую процедуру.

 

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE PROCEDURE FIND_TOVAR

    --ВХОДНЫЕ ПАРАМЕТРЫ ДЛЯ ПОИСКА

    --ВСЕ СТРОКОВЫЕ ТИПЫ

    @NAZV_TOVAR VARCHAR(20),

    @ED_IZN VARCHAR(6),

    @PROIZV VARCHAR(15),

    @CENA VARCHAR(8)

AS

BEGIN

    SET NOCOUNT ON;

    --ПЕРЕМЕННАЯ ДЛЯ ФОРМИРОВАНИЯ УСЛОВИЯ ПОИСКА

    DECLARE @S VARCHAR(200)

    SET @S='';

   

    --ПРОВЕРЯЕМ ПОИСК ПО ПОЛЮ NAZV_TOVAR

    IF @NAZV_TOVAR<>''

        SET @S=@S+'NAZV_TOVAR LIKE '''+@NAZV_TOVAR+'%'''

 

    --ПРОВЕРЯЕМ ПОИСК ПО ПОЛЮ ED_IZM

    IF @ED_IZM<>''

        IF @S<>''

            SET @S=@S+' AND ED_IZM LIKE '''+@ED_IZM+'%'''

        ELSE

            SET @S=@S+'ED_IZM LIKE '''+@ED_IZM+'%'''

 

    --ПРОВЕРЯЕМ ПОИСК ПО ПОЛЮ PROIZV

    IF @PROIZV<>''

        IF @S<>''

            SET @S=@S+' AND PROIZV LIKE '''+@PROIZV+'%'''

        ELSE

            SET @S=@S+'PROIZV LIKE '''+@PROIZV+'%'''

 

    --ПРОВЕРЯЕМ ПОИСК ПО ПОЛЮ CENA

    IF @CENA<>''

        IF @S<>''

            SET @S=@S+' AND CENA ='+@CENA

        ELSE

            SET @S=@S+'CENA ='+@CENA

 

    --ЕСЛИ УСЛОВИЕ ЗАДАНО

    IF @S<>''

        --ОТБИРАЕМ ДАННЫЕ ПО УСЛОВИЮ

        EXEC('SELECT * FROM TOVARS WHERE '+@S)

    --ЕСЛИ УСЛОВИЕ НЕ ЗАДАНО

    ELSE

        --ОТБИРАЕМ ВСЕ ДАННЫЕ

        EXEC('SELECT * FROM TOVARS')

    END

GO

 

Нанесите на форму компонент ADOStoredProc (ADO) и задайте свойства: Connectіon=ADOConnectіon1, ProcedureName=find_tovar. Данная процедура имеет 4 параметра, которым нужно задать тип данных. Откройте свойство Parameters, удерживая Shift выберите все параметры и задайте им свойства: DataType = String, Value - Type =String.

 

Для кнопки фильтрации введите код:

 

procedure TForm1.Button2Click(Sender: TObject);

begin

    //в качестве параметров передаем содержимое текстовых полей

    ADOStoredProc6.Parameters.ParamByName('@nazv_tovar').Value:=Edit2.Text;

    ADOStoredProc6.Parameters.ParamByName('@ed_izm').Value:=Edit3.Text;

    ADOStoredProc6.Parameters.ParamByName('@ed_izm').Value:=Edit4.Text;

    ADOStoredProc6.Parameters.ParamByName('@cena').Value:=Edit5.Text;

    //отбираем данные с помощью процедуры

    ADOStoredProc6.Active:=true;

    //привязываем отобранные данные к сетке

    DataSource2.DataSet:=ADOStoredProc6;

end;

 

Для кнопки отображения всех данных напишем код:

 

procedure TForm1.Button3Click(Sender: TObject);

begin

    Edit2.Clear;

    Edit3.Clear;

    Edit4.Clear;

    Edit5.Clear;

    //выключаем процедуру поиска   

    ADODataSource6.Active:=false;

    //отображаем в подчиненной сетке хранимую процедуру по подчиненной таблице

    DataSource2.DataSet:=ADOStoredProc2;

end;