МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ ШАХТИНСКИЙ ИНСТИТУТ (ФИЛИАЛ) ФЕДЕРАЛЬНОГО ГОСУДАРСТВЕННОГО БЮДЖЕТНОГО ОБРАЗОВАТЕЛЬНОГО УЧРЕЖДЕНИЯ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ «ЮЖНО-РОССИЙСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ (НОВОЧЕРКАССКИЙ ПОЛИТЕХНИЧЕСКИЙ ИНСТИТУТ)» Лабораторная работа № 3 по дисциплине «Корпоративные информационные системы» на тему: «Основные управляющие конструкции языка Transact-SQL и объекты БД СУБД Microsoft SQL Server 2008» Составитель: канд. техн. наук Шахты, 2009 год Олейник П.П. Лабораторная работа № 3. Основные управляющие конструкции языка Transact-SQL и объекты БД СУБД Microsoft SQL Server 2008 Цель работы: Изучить синтаксические конструкции языка Transact-SQL, реализованные в СУБД Microsoft SQL Server 2008, применяемые для реализации алгоритмов обработки данных и конструкций, используемых для создания объектов баз данных, таких как представления, функции, хранимые процедуры и триггеры. Задание на лабораторную работу: Для каждого варианта индивидуального задания с помощью языка Transact-SQL необходимо создать объекты баз данных, описанные в приложении А, и написать запросы, позволяющие продемонстрировать разработанный функционал. Ход выполнения работы Использование основных синтаксических конструкций, применяемых для построения объектов будет продемонстрировано на тестовой базе данных, структура которой представлена на рис. 1. Рис. 1. – Физическая модель тестовой базы данных Для того, чтобы создать структуру БД и заполнить таблицы начальными данными, необходимо открыть оснастку SQL Server Management Studio и подключиться к серверу СУБД. Затем необходимо создать тестовую БД (назовём её ProductSales) и нажать кнопку Создать запрос. В результате появится окно, внешний вид которого представлен на рис 2. 2 Рис. 2. – Окно ввода нового запроса в оснастке SQL Server Management Studio В верхней части окна (вместо базы данных master) должна быть выбрана созданная БД, т.к. именно в ней будут определены требуемые таблицы. В среднюю часть рассматриваемого окна необходимо ввести программный код, представленный в листинге 1. Для запуска скрипта необходимо нажать кнопку Выполнить. --Создание таблиц в БД create table Product ( ProductNo bigint identity(1,1) not null primary key ,ProductName varchar(50) not null ) create table Client ( ClientNo bigint identity(1,1) not null primary key ,ClientName varchar(50) not null ) create table Sale ( SaleNo bigint identity(1,1) not null primary key ,ClientNo bigint not null ,ProductNo bigint not null ,Dates smalldatetime not null ,Counts int not null default 0 ,Price money not null default 0 ) --Создание внешних ключей для таблицы Sale alter table Sale add constraint FK_Sale_Client foreign key(ClientNo) references Client(ClientNo) on update cascade on delete cascade alter table Sale add constraint FK_Sale_Product foreign key(ProductNo) references Product(ProductNo) on update cascade on delete cascade --Вставка значений в таблицу Client insert Client --Без указания имени полей values('ИП Иванов И.И.') insert Client(ClientName)--С указанием имён полей values('ООО Эдельвейс') insert Client(ClientName) --Вставка нескольких значений select 'ИП Петров П.П.' union all --Допускается дублирование select 'ОАО Юг-Креатив' --Вставка значений в таблицу Product insert into Product --Несколько значений зез указания полей select 'Авторучка' union select 'Карандаш' union select 'Линейка' union select 'Ластик' union select 'Треугольник' union select 'Маркер' --Вставка значений в таблицу Sale insert Sale --Без указания полей values(1,1,'2010-02-01T08:30:00',100,5) --C указанием полей (в порядке объявления) insert Sale(ClientNo,ProductNo,Dates,Counts,Price) values(2,1,'2010-02-07T09:15:00',30,4.5) --C указанием полей (в произвольном порядке) insert Sale(Dates,Counts,Price,ClientNo,ProductNo) values('2010-02-02T17:13:00',7,2,1,2) 3 select 3,4,'2010-02-15T18:09:00',1,7 union all --Использование выражение в качестве значения select 3,4,'2010-02-16T12:28:00',2*5,7 union all select 2,5,'2010-04-07T10:00:00',5,17 union all select 1,5,'2010-04-08T09:56:00',2,17 union all select 2,5,'2010-04-09T13:15:00',10,17 --Несколько строк с указанием полей (в порядке объявления) insert into Sale(ClientNo,ProductNo,Dates,Counts,Price) select 3,2,'2010-03-03T15:10:00',10,2.1 union all select 2,3,'2010-02-05T10:53:00',5,15 union all select 1,3,'2010-03-07T14:07:00',3,14.9 union all Листинг 1. – Программный код создания таблиц в БД и занесения в них данных Ключевой возможностью любого языка программирования является возможность объявления переменных и присвоения им значений. В языке Transact-SQL для описания переменных применяется директива declare (рис. 3). Данный язык является нечувствительным к регистру написания имён переменных, объектов и т.п. Т.е. записи @a и @A являются обращением к одной и той же переменной. Сразу после объявления переменным автоматически присваивается null-значение. Переменные определяются с помощью ключевого слова declare, вслед за которым следует имя переменной, начинающиеся с символа @. После каждой переменной указывается тип данных, значения которого может принимать переменная. Для присвоения значения одной переменной используется команда set, а для присвоения нескольким – select. В случае использования команды select нельзя в одном операторе присваивать значения нескольким переменным, если значение одной зависит от значения другой, рассчитываемой в этой же команде. Причина в том, что SQL Server может рассчитывать значения в любой последовательности. Представленный пример демонстрирует использование скалярных переменных, т.е. переменных, принимающих одно единственное значение. В СУБД Microsoft SQL Server имеется возможность объявлять и использовать табличные переменные, т.е. переменные, значениями которых выступают целые таблицы. --Объявление переменных declare @a bigint, @b bigint, @c varchar(20) --Присвоение значения одной переменной set @a=123 --Присвоение значений нескольким переменным select @B=777, @C='Тестовая строка' --Вывод значений select @A as a, @B as b, @C as c Рис. 3. – Объявление и использование скалярных переменных Рассмотрим пример использования табличных переменных (рис. 4). Отметим, что табличная переменная сохраняет значения в памяти, т.е. не создаёт таблицы в БД и поэтому её не следует удалять с помощью директивы drop table и она доступна только из того соединения, в котором создана. В примере из рис. 4 выполняется объявление табличной переменной @Tab, представляющую собой таблицу с тремя полями (col1, col2, col3). Первоначально таблица пуста, поэтому с помощью оператора insert в неё вставляются две строки, которые затем возвращаются с помощью команды select. 4 --Объявление табличной переменной declare @Tab table ( col1 int primary key ,col2 varchar(100) not null default '<>' ,col3 money null default 123 ) --Вывод содержимого таблицы (Таблица пуста) select * from @Tab --Вставка значений insert @Tab(col1,col2) select 1, 'Тест 1' insert @Tab(col1,col2,col3) select 2, 'Тест 2', 750 --Вывод содержимого таблицы select * from @Tab Рис. 4. – Объявление и использование табличной переменной Декларативный язык SQL подразумевает манипулирование множествами строк, поэтому в базовой реализации отсутствуют синтаксические конструкции, позволяющие перемещаться между строками в наборе данных. В том случае, когда необходим навигационный (построчный) подход к обработке строк, используется курсор. Пример применения курсора представлен на рис. 5. В последнем примере используется курсор для получения всех клиентов и вывода названия организации через запятую. Объявленный курсор (с помощью директивы declare) перед использованием необходимо открыть с помощью команды open. Отметим, что имя курсора не начинается с символа @, в отличие от имени переменной. Затем происходит выборка первой строки (первого клиента) в переменную @ClName. С помощью оператора while организуется цикл до тех пора, пока выполняется условие @@fetch_status=0, т.е. до тех пор, пока извлечение следующего клиента выполняется успешно. Внутри цикла, ограниченного блоком begin..end, выполняется обновление переменной @ClAllName, содержащей названия всех организаций. А именно происходит добавление текущего названия к добавленным ранее. В конце итерации цикла выполняется выборка следующей строки. После того, как курсор обработан, его необходимо закрыть (с помощью команды close) и уничтожить (команда deallocate). --Объявление курсора declare ClientCursor cursor for select ClientName from Client --Открытие курсора (выборка данных) open ClientCursor --Переменная для значения текущей строки declare @ClName varchar(100) ,@ClAllName varchar(200) select @ClName='', @ClAllName='' --Получение первой строки fetch ClientCursor into @ClName --Цикл по всем клиентам while @@fetch_status=0 begin set @ClAllName=@ClAllName+@ClName+', ' --Следующая строка fetch ClientCursor into @ClName end --Закрытие и уничтожение курсора close ClientCursor deallocate ClientCursor --Вывод рассчитанного значения select @ClAllName 5 Рис. 5. – Пример использования курсора После того, как были представлены основные способы применения инструкции declare, рассмотрим её синтаксис [1]: DECLARE { {{ @local_variable [AS] data_type } | [ = value ] } | { @cursor_variable_name CURSOR } } [,...n] | { @table_variable_name [AS] <table_type_definition> | <user-defined table type> } <table_type_definition> ::= TABLE ( { <column_definition> | <table_constraint> } [ ,... ] ) <column_definition> ::= column_name { scalar_data_type | AS computed_column_expression } [ COLLATE collation_name ] [ [ DEFAULT constant_expression ] | IDENTITY [ ( seed ,increment ) ] ] [ ROWGUIDCOL ] [ <column_constraint> ] <column_constraint> ::= { [ NULL | NOT NULL ] | [ PRIMARY KEY | UNIQUE ] | CHECK ( logical_expression ) } <table_constraint> ::= { { PRIMARY KEY | UNIQUE } ( column_name [ ,... ] ) | CHECK ( search_condition ) } Рассмотрим основные синтаксические элементы более подробно: @local_variable Имя переменной. Имена переменных должны начинаться с символа @. Имена локальных переменных должны соответствовать правилам для идентификаторов. data_type Любой системный тип данных, определяемый пользователем табличный тип CLR или псевдоним типа данных. Переменная не может принадлежать к типу данных text, ntext или image. = value Подставляет значение переменной. Значение может быть константой или выражением, но должно совпадать с объявленным типом переменной или явно преобразовываться в этот тип. @cursor_variable_name Имя переменной курсора. Имена переменных курсора должны начинаться с символа @ и должны соответствовать правилам именования идентификаторов. CURSOR Указывает, что переменная является локальной переменной курсора. @table_variable_name Имя переменной типа table. Имена переменных должны начинаться с символа @ и соответствовать правилам именования идентификаторов. <table_type_definition> Определяет тип данных table. Объявление таблицы включает в себя определения столбцов, имен, типов данных и ограничений. Допустимы только ограничения PRIMARY KEY, UNIQUE, NULL и CHECK. Псевдоним типа данных не может использоваться как скалярный тип данных столбца, если с этим столбцом связано правило или значение по умолчанию. Аргумент <table_type_definition> представляет собой подмножество данных, используемых для определения таблицы в инструкции CREATE TABLE. Сюда включены элементы и наиболее существенные определения. n 6 Заполнитель, указывающий на то, что могут быть заданы несколько переменных и им могут быть присвоены значения. В инструкции DECLARE может быть описано только одно объявление переменной типа table. Часто при написании запросов возникает ситуация, когда необходимо выполнить несколько команд виде единого целого (см. цикл while на рис. 5). В таких случаях используется блок begin..end, синтаксис которого имеет вид [1]: BEGIN { sql_statement | statement_block } END где { sql_statement | statement_block } Любая допустимая инструкция или группа инструкций языка Transact-SQL, определенных блоком инструкций. Для определения блока инструкций (пакета) следует воспользоваться ключевыми словами BEGIN и END языка потока управления. Хотя все инструкции языка Transact-SQL допустимы в пределах блока begin...end, некоторые инструкции языка Transact-SQL не следует группировать вместе в пределах одного пакета (блока инструкций). Для организации ветвления хода выполнения программы на два потока используется оператор if..else. C помощью инструкции if проверяется выполнение условия. Результирующий поток управления зависит от того, указана ли необязательная инструкция else: Инструкция if задана без else. Если результатом выполнения инструкции является значение true, то выполняется инструкция или блок инструкций, следующие за IF. Если результатом выполнения инструкции является значение false, то инструкция или блок инструкций, следующие за if, пропускаются. Если результатом выполнения инструкции является значение true, то выполняется инструкция или блок инструкций, следующие за if. После этого управление переходит к точке после инструкции или блока инструкций, следующих после else. Если результатом выполнения инструкции является значение false, то инструкция или блок инструкций, следующие за инструкцией if, пропускаются и выполняется инструкция или блок инструкций, следующие после необязательной инструкции else. На рис. 6 представлен пример использования инструкции if..else. declare @a int, @b int select @a=5, @b=10 if @a>3 select 'Переменная @a больше 3' if @b>20 select 'Переменная @b больше 20' else select 'Переменная @b меньше 20' if @a>10 and @b<10 select 'Строка никогда не выведется' Рис. 6. – Использование инструкции if..else Синтаксис имеет вид [1]: IF Boolean_expression { sql_statement | statement_block } [ ELSE { sql_statement | statement_block } ] Рассмотрим основные синтаксические элементы более подробно: Boolean_expression 7 Выражение, возвращающее значение true или false. Если логическое выражение содержит инструкцию select, то она должна быть заключена в скобки. { sql_statement | statement_block } Любая инструкция или группа инструкций языка Transact-SQL, указанная с помощью блока инструкций. Без использования блока инструкций условия if и else могут повлиять на выполнение только одной инструкции языка Transact-SQL. Для организации циклов в Transact-SQL используется инструкция while. На следующем рисунке представлен пример цикла, суммирующего все числа от 1 до 10. declare @i tinyint, @sum tinyint select @i=1, @sum=0 while @i<=10 begin --Требуется две отдельных команды set @sum=@sum+@i set @i=@i+1 end select @sum Рис. 7. – Цикл суммирования чисел от 1 до 10 В цикле использовано две команды set: одна для подсчёта суммы, а вторая для увеличения счётчика цикла. В данном случае нельзя использовать одну команды (например, так select @sum=@sum+@i, @i=@i+1), т.к. при суммировании используется переменная @i которая рассчитывалась бы в том же операторе. Расчет в одном операторе значений @i и @sum мог бы привести к тому, что переменная @i рассчитывалась бы раньше @sum и результат был бы неверным. В цикле while допускается использование ключевых слов break и continue, смысл которых понятен из рис. 8. В данном случае организуется цикл по значению переменной @i в диапазоне от 1 до 1000. При этом пропускается значение @i=5 (оно отсутствует на рис. 8). В при достижении переменной @i значения 9 цикл прекращается не напечатав значение. declare @i tinyint set @i=1 --Цикл до @i=10 while @i<=10 begin if @i=5 --Пропустить элемент begin --Защита от зацикливания set @i=@i+1 continue end if @i=9 --Окончить цикл break select @i --Увеличение счётчика set @i=@i+1 end Рис. 8. – Использование ключевых слов break и continue В заключение обсуждения инструкции while рассмотрим её синтаксис [1]: WHILE Boolean_expression { sql_statement | statement_block } [ BREAK ] { sql_statement | statement_block } [ CONTINUE ] 8 { sql_statement | statement_block } Рассмотрим основные синтаксические элементы более подробно: Boolean_expression Выражение, возвращающее значение true или false. Если логическое выражение содержит инструкцию select, то она должна быть заключена в скобки. { sql_statement | statement_block } Любая инструкция или группа инструкций Transact-SQL, определенная в виде блока инструкций. Для определения блока инструкций необходимо использовать ключевые слова потока управления begin и end. BREAK Приводит к выходу из ближайшего цикла while. Вызываются инструкции, следующие за ключевым словом end, обозначающим конец цикла. CONTINUE Выполняет переход на начало цикла, игнорируя все инструкции, следующие после ключевого слова continue. Рассмотрим объекты базы данных, которые чаще все используются в приложениях, а именно – представления. Представление является именованным запросом, сохранённым в БД, предназначенным для извлечения данных (команда select). Из рис. 1 видно, что в таблице продаж (Sale) отсутствуют названия продуктов и названия организаций, выполнивших покупку, а вместо них присутствуют неудобочитаемые идентификаторы. На рис. 9 представлен Transact-SQL скрипт, создающий представление SaleFullInfo, которое выполняет внутреннее естественное соединение трёх таблиц. Представление создаётся с помощью фразы create view, вслед за которым следует имя, а после слова as запрос, извлекающий данные. Общий формат запроса практически полностью соответствует запросу select за тем исключением, что в представлении нельзя использовать фразу order by, используемую для сортировки данных по значениям полей. Создание представления предполагает определение нового определение нового объекта (см. рис. 9 справа) и последующее использование его для выборки данных так же, как для этого применяются таблицы. Для извлечения всех строк и столбцов из созданного представления, необходимо написать запрос, представленный на рис. 10. Возвращаемый набор данных сортируется по названию организации и по купленному продукту (т.к. в представлениях нельзя использовать директиву order by). create view SaleFullInfo as select s.SaleNo, c.ClientNo, c.ClientName ,p.ProductNo, p.ProductName ,s.Dates, s.Counts, s.Price ,s.Counts*s.Price as Summa from Sale s join Client c on c.ClientNo=s.ClientNo join Product p on p.ProductNo=s.ProductNo Рис. 9. – Создание представления SaleFullInfo select * from SaleFullInfo order by ClientName, ProductName Рис. 10. – Выборка данных из представления SaleFullInfo В примере на рис. 9 представление построено на основе таблиц БД. Однако существует возможность создания представления на основе данных, полученных из другого представления. На рис. 9 11 создаётся представление, группирующие информацию о продажах по клиентам и продуктам, полученных из ранее созданного представления SaleFullInfo. Для каждой группы рассчитывается общее количество проданного товара и валовой доход от продаж. create view SaleGroupInfo as select sfi.ClientName, sfi.ProductName ,sum(sfi.Counts) as Counts ,sum(sfi.Summa) as Summa from SaleFullInfo sfi group by sfi.ClientName, sfi.ProductName Рис. 11. – Создание представления SaleGroupInfo Для получения сгруппированной информации и сортировки её по названию организации и продукта, используется запрос, представленный на рис. 12. select * from SaleGroupInfo order by ClientName, ProductName Рис. 12. – Выборка сгруппированной информации Изменить представление (заменить текст сохранённого запроса) можно с помощью команды alter view, а удалить - с помощью оператора drop view с указанием имени представления. Любое представление может быть создано с помощью следующего синтаксиса [1]: CREATE VIEW [ schema_name . ] view_name [ (column [ ,...n ] ) ] [ WITH <view_attribute> [ ,...n ] ] AS select_statement [ WITH CHECK OPTION ] [ ; ] <view_attribute> ::= { [ ENCRYPTION ] [ SCHEMABINDING ] [ VIEW_METADATA ]} WHILE Boolean_expression { sql_statement | statement_block } [ BREAK ] { sql_statement | statement_block } [ CONTINUE ] { sql_statement | statement_block } Рассмотрим основные синтаксические элементы более подробно: schema_name Имя схемы, которой принадлежит представление. view_name Имя представления. Имена представлений должны соответствовать требованиям, предъявляемым к идентификаторам. Указывать имя владельца представления не обязательно. column Имя, которое будет иметь столбец в представлении. Имя столбца требуется только в тех случаях, когда столбец формируется на основе арифметического выражения, функции или константы, если два или 10 более столбцов могут по иной причине получить одинаковые имена (как правило, в результате соединения) или если столбцу представления назначается имя, отличное от имени столбца, от которого он произведен. Назначать столбцам имена можно также в инструкции select. Если аргумент column не указан, столбцам представления назначаются такие же имена, которые имеют столбцы в инструкции select. select_statement Инструкция select, которая определяет представление. В этой инструкции можно указывать более одной таблицы и другие представления. Для выбора объектов, указанных в предложении select создаваемого представления, необходимы соответствующие разрешения.Представление не обязательно является простым подмножеством строк и столбцов одной конкретной таблицы. С помощью предложения select можно создавать представление, использующее более одной таблицы, или другие представления любой степени сложности. При использовании в определении индексированного представления инструкция select должна содержать указание одной таблицы или соединять инструкцией join несколько таблиц с необязательной статистической обработкой. Предложения select, используемые в определении представления, не могут включать следующие элементы: предложения compute и compute by; предложение order by, если только в списке выбора инструкции select нет также предложения top; ключевое слово into; предложение option; ссылку на временную таблицу или табличную переменную. Так как аргумент select_statement использует инструкцию select, допустимо включать в состав предложения from подсказки <join_hint> и <table_hint>. В аргументе select_statement можно использовать функции и множественные инструкции select, разделенные оператором union или union all. CHECK OPTION Обеспечивает соответствие всех выполняемых для представления инструкций модификации данных критериям, заданным при помощи аргумента select_statement. Если строка изменяется посредством представления, предложение with check option гарантирует, что после фиксации изменений доступ к данным из представления сохранится. ENCRYPTION Выполняет шифрование элементов представления sys.syscomments, содержащего текст инструкции create view. Использование предложения with encryption предотвращает публикацию представления в рамках репликации SQL Server. SCHEMABINDING Привязывает представление к схеме базовой таблицы или таблиц. Если аргумент schemabinding указан, нельзя изменить базовую таблицу или таблицы таким способом, который может повлиять на определение представления. Сначала нужно изменить или удалить само представление для сброса зависимостей от таблицы, которую требуется изменить. Аргумент SCHEMABINDING нельзя указывать, если представление содержит столбцы, имеющие псевдоним. VIEW_METADATA Указывает, что экземпляр SQL Server возвратит в API-интерфейсы DB-Library, ODBC и OLE DB сведения метаданных представления. В представлении, созданном с предложением with view_metadata, все столбцы, за исключением столбца timestamp, поддерживают обновление, если представление включает триггеры instead of insert или instead of update. Таким образом, объявление представления является простым и элегантным способом сократить форму записи наиболее часто используемых запросов. Кроме того, все представления хранятся на СУБД в откомпилированном оптимизированном виде, что позволяет увеличить скорость выполнения запроса по сравнению с формированием динамических запросов из клиентского приложения. Несмотря на существенные достоинства, у представления есть ключевой недостаток. Представление не может принимать параметры и строить на основе них результирующий набор. Для этих целей используют пользовательские функции, создаваемые с помощью инструкции create function. Функция представляет собой подпрограмму Transact-SQL или среды CLR (платформы .Net), которая возвращает значение. Пользовательская функция не может выполнять действия, изменяющие 11 состояние базы данных (создавать, изменять или удалять объекты БД). Она, как и системная функция, может быть вызвана из запроса. Скалярные функции, как и хранимые процедуры, могут быть выполнены инструкцией execute. В СУБД Microsoft SQL Server 2008 можно создать следующие типы пользовательских функций: Скалярная функция возвращает одно единственное значение. Встроенная (inline) табличная функция представляет собой единственную строку запроса на выборку данных. Табличная функция, реализованная в виде набора различных операторов, вставляющих строки в результирующую объявленную табличную переменную. CLR-функция, написанная на любом объектно-ориентированном языке программирования, поддерживающем платформу .Net (например, на C#). Рассмотрим первые три типа функций, т.к. они чаще всего используются, потому что для их написания применяется язык Transact-SQL. Пример скалярной функции представлен на рис. 13. create function CalcValue(@In int) returns int as begin declare @RetValue int if @In<10 set @RetValue=@In*@In else set @RetValue=@In*@In*@In --Возврат итогового значения return @RetValue end Рис. 13. – Пример скалярной функции После фразы create function указывается имя функции (CalcValue) и в круглых скобках входные параметры с указанием типов данных. При объявлении параметров (в отличие от объявления локальных переменных) не требуется писать ключевое слово declare. В нашем случае имя функции указано без описания схемы в которой она будет создана, поэтому она создаётся в схеме dbo. После слова returns указывается тип данных возвращаемого значения. Значение из скалярной функции возвращается с помощью оператора return в след за которым указывается выражение, соответствующего типа данных. В рассматриваемом примере в функции объявлена переменная @RetValue, которая будет использоваться в расчетах и возвращается в качестве значения функции. Если в функцию передано значение меньше чем 10, то функция вёрнёт квадрат переданного значение, если больше или равно десяти - то куб. Для тестирования работоспособности функции сделаем два вызова (рис. 14). --Вернёт квадрат значения select dbo.CalcValue(5) --Вернёт куб значения select dbo.CalcValue(20) Рис. 14. – Вызов скалярной функции При вызове скалярной функции необходимо указывать схему, в которой она объявлена, в нашем случае dbo. Скалярная функция может быть использована везде, где подразумевается использование выражения. На рис. 15 продемонстрировано использование скалярной функции при построении логического выражения. select *, dbo.CalcValue(9) as CalcValue from SaleGroupInfo sgi where sgi.Summa>dbo.CalcValue(9) 12 Рис. 15. – Использование скалярной функции в логическом выражении В примере выполняется выборка сгруппированных данных из представления SaleGroupInfo. Извлекаются только те строки, в которых сумма дохода больше 81 рубля (это значение, которое возвращено из функции dbo.CalcValue(9)). Скалярная функция позволяет вернуть только одно значение, но бывает ситуации, когда необходимо вернуть набор данных, содержимое которого зависит от переданных параметров. На рис. 16 представлен пример объявления встроенной табличной функции, которая извлекает данные о продажах на сумму, меньшую чем переданное в качестве параметра значение. create function SaleDatailInfo(@MaxSumma numeric(18,3)) returns table as return select * from SaleFullInfo sfi where sfi.Summa<@MaxSumma Рис. 16. – Пример встроенной табличной функции Так как результатом выполнения функции является таблица, то после слова returns указан тип данных table. Встроенная табличная функция позволяет записать только одну команду языка SQL в качестве реализации, поэтому не следует писать слова begin и end, а сам запрос записывается непосредственно после слова return. На рис. 17 представлен пример вызова функции SaleDatailInfo (имя схемы dbo указывать не обязательно). Рассматриваемый тип функций может быть использован везде где предполагается применение таблиц и представлений, поэтому СУБД может оптимизировать план выполнения запроса с целью сокращения затрачиваемого времени. select ClientName, ProductName, Summa from dbo.SaleDatailInfo(50) Рис. 17. – Вызов встроенной табличной функции Если необходимо реализовать сложный алгоритм обработки, то используются табличные функции, содержащие несколько операторов. На рис. 18 представлен пример такой функции. Функция возвращает название организации, название продукта и суммы продаж определённому клиенту конкретного продукта. Кроме того формируется итоговая строка, рассчитывающая сумму продаж по всем строкам. Если вместо идентификаторов клиента и/или продукта передать пустые (null) значения, то будет выполнена группировка по всем клиентам и/или продуктам. 13 create function SaleGroup (@ClientNo bigint, @ProductNo bigint) returns @RetTable table ( ClientName varchar(50) not null ,ProductName varchar(50) not null default '' ,Summa numeric(18,2) ) as begin insert @RetTable(ClientName, ProductName, Summa) select sfi.ClientName, sfi.ProductName, sum(sfi.Summa) from SaleFullInfo sfi where (sfi.ClientNo=@ClientNo or @ClientNo is null) and (sfi.ProductNo=@ProductNo or @ProductNo is null) group by sfi.ClientName, sfi.ProductName order by sfi.ClientName, sfi.ProductName --Итоговая строка по всем группам insert @RetTable(ClientName, Summa) select 'ИТОГО', sum(Summa) from @RetTable return --Обязательно написать последним end Рис. 18. – Пример табличной функции, содержащей несколько операторов Структура возвращаемой таблицы данных описывается после слова returns и представляет собой объявление табличной переменной (см. рис. 4). Операторы, реализующие тело табличной функции выполняют вставку значений (обновление, удаление) в возвращаемую переменную. Последним оператором табличной функции должна быть команда return. Примеры вызова функции представлены на рис. 19. Первый запрос возвращает сгруппированные данные по всем клиентам и всем купленным или продуктов. Это достигается передачей пустых (null-значений) и соответствующей проверкой их в функции (is null). Во втором запросе запрашивается информация по продажам клиенту ИП Иванов И.И (его идентификатор передан в качестве первого параметра). Из рис. 18 видно, что данные возвращены в отсортированном виде, что достигается использованием ключевого слова order by непосредственно в функции (см. рис. 18). Самой последней выводится строка с итоговой суммой. Таким образом, при вызове табличной функции все строки возвращаются в том порядке, в котором они были вставлены в табличную переменную. --Группировка всех клиентов и всех продуктов select * from SaleGroup(null, null) --Все продукты, купленные ИП Иванов И.И. select * from SaleGroup(1, null) 14 Рис. 19. – Примеры вызова табличной функции, содержащей несколько операторов Для изменения функции используется оператор alter function. При этом новая версия функции должна быть того же типа, что и старая версия. Т.е. если функция была скалярной, её нельзя изменить и сделать табличной. Для этого потребуется удалить старую версию и создать новую. Для удаления функции применяется команда drop function. Любая функция может быть создана с помощью следующего синтаксиса, конечный формат которого зависит от типа функции [1]: --Скалярная функция CREATE FUNCTION [ schema_name. ] function_name ( [ { @parameter_name [ AS ][ type_schema_name. ] parameter_data_type [ = default ] [ READONLY ] } [ ,...n ] ] ) RETURNS return_data_type [ WITH <function_option> [ ,...n ] ] [ AS ] BEGIN function_body RETURN scalar_expression END [;] --Встроенная табличная функция CREATE FUNCTION [ schema_name. ] function_name ( [ { @parameter_name [ AS ] [ type_schema_name. ] parameter_data_type [ = default ] [ READONLY ] } [ ,...n ] ] ) RETURNS TABLE [ WITH <function_option> [ ,...n ] ] [ AS ] RETURN [ ( ] select_stmt [ ) ] [;] --Табличная функция с множеством операторов CREATE FUNCTION [ schema_name. ] function_name ( [ { @parameter_name [ AS ] [ type_schema_name. ] parameter_data_type [ = default ] [READONLY] } [ ,...n ] ] ) RETURNS @return_variable TABLE <table_type_definition> [ WITH <function_option> [ ,...n ] ] [ AS ] 15 BEGIN function_body RETURN END [;] <order_clause> ::= { <column_name_in_clr_table_type_definition> [ ASC | DESC ] } [ ,...n] Method Specifier <method_specifier>::= assembly_name.class_name.method_name Function Options <function_option>::= { [ ENCRYPTION ] | [ SCHEMABINDING ] | [ RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT ] | [ EXECUTE_AS_Clause ] } <clr_function_option>::= } [ RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT ] | [ EXECUTE_AS_Clause ] } Table Type Definitions <table_type_definition>:: = ( { <column_definition> <column_constraint> | <computed_column_definition> } [ <table_constraint> ] [ ,...n ] ) <column_definition>::= { { column_name data_type } [ [ DEFAULT constant_expression ] [ COLLATE collation_name ] | [ ROWGUIDCOL ] ] | [ IDENTITY [ (seed , increment ) ] ] [ <column_constraint> [ ...n ] ] } <column_constraint>::= { [ NULL | NOT NULL ] { PRIMARY KEY | UNIQUE } [ CLUSTERED | NONCLUSTERED ] [ WITH FILLFACTOR = fillfactor | WITH ( < index_option > [ , ...n ] ) [ ON { filegroup | "default" } ] | [ CHECK ( logical_expression ) ] [ ,...n ] } <computed_column_definition>::= column_name AS computed_column_expression <table_constraint>::= { { PRIMARY KEY | UNIQUE } [ CLUSTERED | NONCLUSTERED ] ( column_name [ ASC | DESC ] [ ,...n ] ) 16 [ WITH FILLFACTOR = fillfactor | WITH ( <index_option> [ , ...n ] ) | [ CHECK ( logical_expression ) ] [ ,...n ] } <index_option>::= { PAD_INDEX = { ON | OFF } | FILLFACTOR = fillfactor | IGNORE_DUP_KEY = { ON | OFF } | STATISTICS_NORECOMPUTE = { ON | OFF } | ALLOW_ROW_LOCKS = { ON | OFF } | ALLOW_PAGE_LOCKS ={ ON | OFF } } CREATE VIEW [ schema_name . ] view_name [ (column [ ,...n ] ) ] [ WITH <view_attribute> [ ,...n ] ] AS select_statement [ WITH CHECK OPTION ] [ ; ] <view_attribute> ::= { [ ENCRYPTION ] [ SCHEMABINDING ] [ VIEW_METADATA ]} WHILE Boolean_expression { sql_statement | statement_block } [ BREAK ] { sql_statement | statement_block } [ CONTINUE ] { sql_statement | statement_block } Рассмотрим основные синтаксические конструкции более подробно: schema_name Имя схемы, к которой принадлежит пользовательская функция. function_name Имя пользовательской функции. Имена функций должны удовлетворять правилам построения идентификаторов и должны быть уникальными в пределах базы данных и схемы. @parameter_name Аргумент пользовательской функции. Один или несколько аргументов могут быть объявлены. Для функций допускается не более 2 100 параметров. При выполнении функции значение каждого из объявленных параметров должно быть указано пользователем, если для данного параметра не определено значение по умолчанию. Определяет имя параметра, используя знак @ как первый символ. Имя параметра должно соответствовать правилам построения идентификаторов. Параметры являются локальными в пределах функции; в разных функциях могут быть использованы одинаковые имена параметров. Параметры можно использовать только вместо констант. Они не могут использоваться вместо имен таблиц, имен столбцов или имен других объектов базы данных. [type_schema_name.] parameter_data_type Тип данных параметра (возможно, с указанием схемы, которой он принадлежит). Для функций TransactSQL допустимы любые типы данных, включая определяемые пользователем типы данных CLR и определяемые пользователем табличные типы, за исключением типа данных timestamp. Для функций CLR допустимы все типы данных, включая пользовательские типы данных CLR, за исключением типов данных text, ntext, image, определяемых пользователем табличных типов и типов данных timestamp. Нескалярные типы cursor и table не могут быть указаны в качестве типов данных параметров ни для функций Transact-SQL, ни для функций CLR. [= default] Значение параметра по умолчанию. Если определено значение default, функция выполняется даже в том случае, если для данного параметра значение не указано. READONLY Указывает, что параметр не может быть обновлен или изменен при определении функции. Если тип параметра является определяемым пользователем табличным типом, то должно быть указано ключевое слово readonly. return_data_type 17 Возвращаемое значение скалярной пользовательской функции. Для функций Transact-SQL допустимы любые типы данных, включая определяемые пользователем типы данных CLR, за исключением типа данных timestamp. Для функций CLR допустимы все типы данных, включая определяемые пользователем типы данных CLR, за исключением типов данных text, ntext, image и timestamp. Нескалярные типы данных cursor и table не могут быть указаны в качестве возвращаемых типов данных ни для функций Transact-SQL, ни для функций CLR. function_body Указывает серию инструкций Transact-SQL, которая в совокупности не вызывает побочных эффектов (например, изменение содержимого таблиц) и формирует возвращаемое значение функции. function_body используется только в скалярных функциях и функциях, возвращающих табличное значение, из нескольких инструкций. Для скалярных функций аргумент function_body представляет собой серию инструкций Transact-SQL, которые в совокупности вычисляют скалярное значение. Для функций, возвращающих табличное значение из нескольких инструкций, аргумент function_body представляет собой серию инструкций Transact-SQL, заполняющих возвращаемую переменную table. scalar_expression Указывает скалярное значение, возвращаемое скалярной функцией. TABLE Указывает, что возвращаемым значением функции, является таблица. Функциям, возвращающим табличное значение, могут передаваться только константы и @local_variables. Во встроенных функциях возвращаемое значение table определяется при использовании единственной инструкции select. Встроенные функции не имеют соответствующих возвращаемых переменных. В функциях, возвращающих табличное значение из нескольких инструкций, переменной @return_variable является переменная TABLE, используемая для сохранения данных и накопления строк, которые будут возвращены в качестве значения функции. Аргумент @return_variable может быть указан только для функций Transact-SQL, но не для функций CLR. select_stmt Одиночная инструкция SELECT, определяющая возвращаемое табличное значение встроенной функции. ORDER (<order_clause>) Указывает порядок, в котором возвращаются результаты из возвращающей табличное значение функции. EXTERNAL NAME <method_specifier> assembly_name.class_name.method_name Указывает метод сборки, привязываемый к функции. Значение assembly_name должно соответствовать имени существующей сборки в SQL Server в текущей базе данных, для которой включена видимость. Значение class_name должно быть допустимым идентификатором SQL Server, указывающим имя класса в сборке. Если имя класса через точку (.) предваряется квалификатором пространства имен, то оно должно быть заключено в квадратные скобки ([ ]) или двойные кавычки (" "). Значение method_name должно быть допустимым идентификатором SQL Server, указывающим имя статического метода в указанном классе. <table_type_definition>({<column_definition> <column_constraint>| <computed_column_definition> } [ <table_constraint> ] [ ,...n ] ) Определяет тип данных таблицы для функции Transact-SQL. Объявление таблицы включает определения столбцов, а также ограничений для столбцов и таблиц. Таблица всегда помещается в первичную файловую группу. < clr_table_type_definition > ({ column_name data_type } [ ,...n ]) Определяет табличные типы данных для функции CLR. <function_option>::= и <clr_function_option>::= Определяет один или несколько из следующих параметров. ENCRYPTION Указывает, чтобы SQL Server выполнил шифрование исходного кода SCHEMABINDING Указывает, что функция привязана к объектам базы данных, которые содержат ссылки на нее. Это предотвращает изменение функции, если на нее имеются ссылки из других 18 объектов, привязанных к схеме. RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT Указывает атрибут OnNullCall скалярной функции. Если данный аргумент не указан, по умолчанию предполагается called on null input. Это означает, что тело функции выполняется даже в том случае, если в качестве аргумента передано значение null. Предложение EXECUTE AS Указывает контекст безопасности, в котором выполняется пользовательская функция. Иными словами, есть возможность управлять тем, какую учетную запись пользователя SQL Server использует при определении разрешений на объекты базы данных, на которые ссылается функция. < column_definition >::= Определяет тип данных таблицы. Объявление таблицы включает определения столбцов и ограничений. Для функций CLR могут быть указаны только column_name и data_type. column_name Имя столбца в таблице. Имена столбцов должны соответствовать правилам построения идентификаторов и быть уникальными в пределах таблицы. Значение column_name может содержать от 1 до 128 символов. data_type Указывает тип данных столбца. Для функций Transact-SQL допустимы любые типы данных, включая определяемые пользователем типы данных CLR, кроме типа данных timestamp. Для функций CLR допустимы любые типы данных, включая определяемые пользователем типы данных CLR, кроме типов text, ntext, image, char, varchar, varchar(max) и timestamp. Нескалярный тип cursor не может указываться в качестве типа данных столбца ни для функций Transact-SQL, ни для функций CLR. DEFAULT constant_expression Указывает значение, присваиваемое столбцу, в том числе, когда при вставке его значение явно не указано. В качестве значения constant_expression может быть указана константа, null или значение системной функции. Определения default могут применяться к любым столбцам, кроме тех, которые имеют свойство identity. Предложение default не может быть указано для функций CLR, возвращающих табличное значение. COLLATE collation_name Задает параметры сортировки для столбца. Если не указано, столбцу назначаются параметры сортировки, принятые в базе данных по умолчанию. ROWGUIDCOL Показывает, что новый столбец является столбцом глобального уникального идентификатора строки. В качестве столбца rowguidcol можно назначить только один столбец uniqueidentifier в таблице. Свойство rowguidcol можно присвоить только столбцу, имеющему тип uniqueidentifier. Свойство rowguidcol не обеспечивает уникальности значений, хранимых в столбце. Кроме того, данное свойство не производит автоматическое формирование значений для новых строк, вставляемых в таблицу. Для формирования уникальных значений произвольного столбца используйте функцию newid в инструкции insert. Может быть указано значение по умолчанию, однако функция newid не может быть указана в качестве значения по умолчанию. IDENTITY Указывает, что новый столбец является столбцом идентификаторов. При добавлении в таблицу новой строки SQL Server формирует для столбца уникальное, последовательное значение. < column_constraint >::= и < table_constraint>::= Определяет ограничение для указанного столбца или таблицы. Для функций CLR единственное допустимое ограничение — null. Именованные ограничения недопустимы. Могут применяться следующие ограничения. NULL | NOT NULL Определяет, допустимы ли для столбца значения null. параметр null не является ограничением в строгом смысле слова, но может быть указан так же, как и not null. Ограничение not null не может быть указано для функций CLR, возвращающих табличное значение. PRIMARY KEY 19 Ограничение, обеспечивающее целостность сущностей для указанного столбца через уникальный индекс. В возвращающих табличное значение пользовательских функциях ограничение primary key может быть создано только для одного столбца таблицы. Ограничение primary key не может быть указано для функций CLR, возвращающих табличное значение. UNIQUE Ограничение, которое обеспечивает сущностную целостность для указанного столбца или столбцов с помощью уникального индекса. Таблица может содержать несколько ограничений unique. Ограничение unique не может быть указано для функций CLR, возвращающих табличное значение. CLUSTERED | NONCLUSTERED Указывает, что для ограничения primary key или unique создается кластеризованный или некластеризованный индекс. Ограничения primary key используют параметр clustered, а ограничения unique используют параметр nonclustered. CHECK Ограничение, обеспечивающее доменную целостность путем ограничения возможных значений, которые могут быть введены в столбец или столбцы. logical_expression Логическое выражение, возвращающее значения true или false. <computed_column_definition>::= Указывает вычисляемый столбец. column_name Имя вычисляемого столбца. computed_column_expression Выражение, определяющее значение вычисляемого столбца. <index_option> ::= Указывает параметры индекса для primary key или unique. Возможными значениями являются. PAD_INDEX = { ON | OFF } Определяет заполнение индекса. Значение по умолчанию — OFF. FILLFACTOR = fillfactor Указывает значение в процентах, показывающее, насколько полным компонент Database Engine должен сделать конечный уровень каждой индексной страницы во время создания или замены индекса. Аргумент fillfactor должен быть целым числом от 1 до 100. Значение по умолчанию — 0. IGNORE_DUP_KEY = { ON | OFF } Определяет ответ на ошибку, случающуюся, когда операция вставки пытается вставить в уникальный индекс повторяющиеся значения ключа. Параметр IGNORE_DUP_KEY применяется только к операциям вставки, производимым после создания или перестроения индекса. Значение по умолчанию — OFF. STATISTICS_NORECOMPUTE = { ON | OFF } Указывает, выполнялся ли перерасчет статистики распределения. Значение по умолчанию — off. ALLOW_ROW_LOCKS = { ON | OFF } Указывает, разрешена ли блокировка строк. Параметр по умолчанию — on. ALLOW_PAGE_LOCKS = { ON | OFF } Указывает, разрешена ли блокировка страниц. Параметр по умолчанию — on. Несмотря на наличия ряда ключевых достоинств, функциям присущи следующие недостатки: Отсутствие возможности использовать операторы изменения данных в основных таблицах. Функции предназначены только для выборки данных с помощью оператора select из таблиц БД. Невозможно в функции выполнить операторы insert, update, delete для данных, хранящихся в таблицах БД. Имеется лишь возможность манипулировать данными, сохранёнными в табличной переменной, но она недоступна за пределами функции в которой она объявлена. 20 Не реализована возможность вывода нескольких наборов данных. Табличная функция позволяет вернуть лишь один набор данных, который может состоять из множества строк. Т.е. все возвращённые строки содержат один и тот же набор столбцов. Иногда возникает ситуация, когда требуется вернуть несколько совершенно разных наборов данных. Также отсутствует возможность вернуть результаты расчёта через переданные (выходные) параметры. Отсутствует возможность выполнения динамических запросов. В некоторых ситуациях необходимо сформировать строку SQL-кода на основе значений переданных параметров, а затем выполнить запрос. Внутри функции этого выполнить невозможно. Не поддерживается возможность создания объектов баз данных внутри функций. С целью автоматизации типовых задач создания объектов в БД, таких как таблицы, представления и т.п. возникает необходимость написать SQL-скрипт, который будет в последствии многократно использован. С помощью функций такие задачи решить нельзя. Для решения обозначенных задач используются хранимые процедуры, рассмотрим основные принципы создания и использования их. На рис. 20 представлен текст хранимой процедуры, выполняющей вставку информации о новом клиенте. create procedure InsertClient @ClientName varchar(100) as if isnull(@ClientName,'')='' begin select 'Не переданно название организации' end else insert Client(ClientName) values(@ClientName) Рис. 20. – Хранимая процедура вставки нового клиента в БД Название хранимой процедуры указывается после слов create procedure, после которых описаны параметры, которые можно передать. Текст процедуры начинается после слова as. В данном случае проверятся передал ли пользователь название организации: если нет, то выводится соответствующее сообщение, если ввёл – то вставляется новая организация. Пример вызова процедуры и результат работы показан на рис. 21. --Вызов без указания названия execute InsertClient '' --С передачей названия организации exec InsertClient 'ИП Сидоров С.С.' --Выборка всех клиентов select * from Client Рис. 21. – Пример вызова хранимой процедуры InsertClient Для вызова хранимой процедуры используется ключевое слово exec (полная форма execute) после которого указывается название процедуры и передаваемые параметры. В первом вызове передана пустая строка в виде названия организации, поэтому получено соответствующее сообщение. Во втором вызове передана строка «ИП Сидоров С.С.» и добавлен соответствующий клиент. Если вызов хранимой процедуры является единственной командой, то слово exec можно не писать. Следующая хранимая процедура (рис. 22) получает в качестве параметра идентификатор клиента и возвращает два набора данных. В первом содерщится информация о клиенте, а во втором – подробная информация о каждом заказе сделанном данным клиентом. 21 create procedure ShowClientDetailInfo @ClientNo bigint = 1 as select * from Client where ClientNo=@ClientNo select sfi.ProductName, sfi.Dates, sfi.Counts, sfi.Price ,sfi.Summa from SaleFullInfo sfi where sfi.ClientNo=@ClientNo order by sfi.ProductName, sfi.Dates Рис. 22. – Пример процедуры, возвращающей несколько наборов данных На рис. 23 представлены примеры двух вызовов процедуры. Т.к. при первом вызове в хранимую процедуру не передаётся идентификатор клиента, то используется значение 1, которое указано в качестве значения по умолчанию для переменной @ClientNo (см. рис. 22). Второй вызов приводит в выводу информации о клиенте идентификатором 2 (ООО Эдельвейс). Для передачи значения используется запись @ClientNo=2, т.е. указывается имя параметра, которому присвоено значение. Такой подход позволяет передавать параметры в любом порядке, а не только в порядке объявления (как было в рассмотренных ранее вызовах, в которых отсутствовало имя параметра). --Не указан идентификатор клиента exec ShowClientDetailInfo --Клиент ООО Эдельвейс exec ShowClientDetailInfo @ClientNo=2 Рис. 23. – Два вызова процедуры ShowClientDetailInfo Невозможно заранее реализовать все запросы, которые могут понадобиться пользователям, поэтому имеет смысл разработать ряд процедур, позволяющих выполнят динамические запросы, сформированные на основании переданных пользователем параметров. На рис. 24 представлен текст хранимой процедуры, которая формирует динамический запрос на выборку подробной информации о продажах и добавляет условия фильтрации данных, переданные пользователем. Основным достоинством динамического запроса является возможность формирования в виде строки текста практически любых запросов. Однако подобная гибкость достигается за счёт снижения производительности запроса, т.к. его текст становится доступным оптимизатору только в момент выполнения, в отличие от объектов БД, которые компилируются в оптимизированный вид в момент создании. Кроме того, проверка синтаксиса динамического запроса также выполняется лишь в момент выполнения, что может привести к появлению непредвиденных ошибок. 22 create procedure DynamicSaleInfo @Filter varchar(200) = '' as declare @Sql varchar(300) --Формирование строки запроса set @Sql='select * from SaleFullInfo ' +case isnull(@Filter,'') when '' then '' else 'where '+@Filter end +' order by ClientName, ProductName' --Выполнение динамического запроса exec(@Sql) Рис. 24. – Процедура, формирующая динамический запрос к БД Рассмотрим примеры вызова процедуры DynamicSaleInfo (рис. 25). При первом вызове в процедуру не передаётся параметр, поэтому фильтрации не происходит и возвращаются все строки из представления SaleFullInfo. --Вывод всей имеющейся информации exec DynamicSaleInfo --Вывод только ООО Эдельвейс exec DynamicSaleInfo 'ClientNo=2' --Запрос с ошибкой exec DynamicSaleInfo 'ClientN=3' Рис. 25. – Вызовы процедуры DynamicSaleInfo Второй запрос выводит информацию только о клиенте с идентификатором равным 2. В третьем запросе преднамеренно допущена ошибка и вместо поля ClientNo указано название несуществующего поля ClientN. Выполнение третьего вызова приводит к ошибке, в которой SQL Server сообщает об отсутствие поля с таким названием. На рис. 26 представлен листинг процедуры, которая создаёт таблицу в БД и заполняет тестовыми данными. Для вызова процедуры использован запрос, изображённый на рис. 27. Т.к. в пакете команд присутствует лишь одна команда и этой командой является вызов процедуры, то слово exec вводить не обязательно. Из результата выполнения видно, что в БД создана новая таблица TestTable в которой присутствует три строки. Кроме создания постоянных таблиц, в СУБД Microsoft SQL Server имеется возможность определения временных таблиц, которые существуют до тех пор, пока выполняется процедура, в которой они созданы. Отличие временных таблиц от табличных переменных в том, что временные таблицы создаются не в памяти, а во временной системной базе данных tempdb и их можно использовать во всех процедурах, которые были вызваны из процедуры, создавшей временную таблицу. Рассмотрим пример из рис. 28. 23 create procedure CreateTable as --Создание таблицы create table TestTable ( A int primary key ,B varchar(100) ) --Заполнение тестовыми значениями insert TestTable values(1,'Строка №1') insert TestTable values(2,'Строка №2') insert TestTable values(3,'Строка №3') --Вывод всех значений из таблицы select * from TestTable Рис. 26. – Создание таблицы в БД CreateTable Рис. 27. – Вызов процедуры CreateTable --Процедура, заполняющая таблицу -- тестовыми данными create procedure Child as insert #TempTable select 1, 'Строка 1' union all select 2, 'Строка 2' union all select 3, 'Строка 3' go --Процедура, создающая -- временную таблицу create procedure Master as create table #TempTable ( A int primary key ,B varchar(20) ) --Заполнение значениями exec Child --Вывод значений select * from #TempTable Рис. 28. – Пример использования временных таблиц В нём создаётся две хранимых процедуры. Процедура Child заполняет временную таблицу с именем #TempTable тестовыми значениями. Имена всех временных таблиц должны начинаться с символа #. Отметим, что таблица используется в Child, хотя в ней она не создавалась. Создание временной таблицы выполняется в процедура Master. После этого происходит вызов процедуры Child, которая заносит временные данные и выполняется вывод всех введённых значений. Т.к. #TempTable создана в процедуре Master, то её можно использовать как самой процедуре, так и внутри всех процедур, которые вызваны из Master (в нашем случае в процедуре Child). После завершения процедуры Master, СУБД автоматически удалить эту временную таблицу. При этом, если одновременно вызвать несколько процедур Master, то в каждой из них будет своя временная #TempTable недоступная из других процедур Master. На рис. 29 представлен результат выполнения 24 процедуры Master. exec Master Рис. 29. – Вызов процедуры Master Результат соответствует ожиданиям, при этом временная таблица не отображается в списке таблиц, присутствующих в БД. Хранимые процедуры позволяют вернуть результат своей работы не только в виде наборов данных, но и через параметры. Кроме того, через оператор return можно вернуть результат выполнения процедуры в виде значения типа int. На рис. 30 продемонстрирована описанная возможность. create procedure OutputParams @i bigint = 123 ,@j bigint = 777, @Sum int =0 output as set @Sum=@i+@j if @i=123 and @j = 777 return 0 else return 1 Рис.30. – Использование выходных параметров Переменная @Sum используется в качестве возвращаемого параметра в которой сохраняется сумма значений двух переданных значений. С помощью оператора return возвращается результат выполнения процедуры. На рис. 31 представлено два примера вызова процедуры. declare @RetSum int, @RetValue int --Вызов со значениями "по умолчанию" exec @RetValue=OutputParams 123, 777, @RetSum output select @RetValue, @RetSum --Вызов с произвольными значениями exec @RetValue=OutputParams 10, 30, @RetSum output select @RetValue, @RetSum Рис.31. – Вызовы процедуры, имеющей выходные параметры Для того, чтобы присвоить значение выходному параметру, при вызове процедуры, для него необходимо указать ключевое слово output (или out). После того, как мы рассмотрели хранимые процедуры, определяющие функционал, который не может быть реализован в виде функций, опишем синтаксис команды create procedure [1]: CREATE { PROC | PROCEDURE } [schema_name.] procedure_name [ ; number ] [ { @parameter [ type_schema_name. ] data_type } [ VARYING ] [ = default ] [ OUT | OUTPUT ] [READONLY] ] [ ,...n ] [ WITH <procedure_option> [ ,...n ] ] [ FOR REPLICATION ] 25 AS { <sql_statement> [;][ ...n ] | <method_specifier> } [;] <procedure_option> ::= [ ENCRYPTION ] [ RECOMPILE ] [ EXECUTE AS Clause ] <sql_statement> ::= { [ BEGIN ] statements [ END ] } <method_specifier> ::= EXTERNAL NAME assembly_name.class_name.method_name Рассмотрим основные синтаксические конструкции более подробно: schema_name Имя схемы, к которой принадлежит хранимая процедура. procedure_name Имя новой хранимой процедуры. Имена процедур должны соответствовать требованиям, предъявляемым к идентификаторам, и должны быть уникальными в схеме. В имена процедур настоятельно не рекомендуется включать префикс sp_. Этим префиксом в SQL Server обозначаются системные хранимые процедуры. Локальную или глобальную процедуру можно создать, указав один символ номера (#) перед procedure_name (#procedure_name) в случае локальных временных процедур и два символа номера в случае глобальных временных процедур (##procedure_name).Полное имя хранимой процедуры или глобальной временной хранимой процедуры не может включать более 128 символов (с учетом символов ##). Полное имя локальной временной хранимой процедуры с учетом символа # не может включать более 116 символов. ; number Необязательное целое число, используемое для группировки процедур с одним и тем же именем. Все сгруппированные процедуры можно удалить, выполнив одну инструкцию drop procedure. Если имя содержит идентификаторы с разделителями, номер не должен быть частью идентификатора; следует выделять при помощи подходящего разделителя только procedure_name. @parameter Параметр процедуры. При выполнении процедуры значение каждого из объявленных параметров должно быть указано пользователем, если для параметра не определено значение по умолчанию или значение не задано равным другому параметру. Хранимая процедура может иметь не более 2 100 параметров. Если процедура содержит возвращающие табличное значение параметры, а в вызове отсутствует параметр, передается пустая таблица по умолчанию. Следует указывать имя параметра, используя в качестве первого символа знак @. Имя параметра должно соответствовать правилам для идентификаторов. Параметры являются локальными в пределах процедуры; в разных процедурах могут быть использованы одинаковые имена параметров. По умолчанию параметры могут использоваться только в качестве константных выражений; они не могут быть использованы вместо имен таблиц, столбцов или других объектов базы данных. [ type_schema_name. ] data_type Тип данных параметра и схема, к которой он относится. Все типы данных, которые могут использоваться в качестве параметра хранимой процедуры Transact-SQL. Можно использовать определяемый пользователем табличный тип, чтобы объявить возвращающий табличное значение параметр в качестве параметра хранимой процедуры Transact-SQL. Возвращающие табличное значение параметры можно указать только в качестве входных параметров, и они должны сопровождаться ключевым словом readonly. Тип данных cursor можно использовать только в качестве выходного параметра. При указании типа данных cursor нужно также указать ключевые слова varying и output. Выходных параметров типа cursor может быть несколько. Параметры хранимых процедур CLR не могут иметь тип char, varchar, text, ntext, image, cursor, table и определяемый пользователем тип таблицы. VARYING Указывает результирующий набор, поддерживаемый в качестве выходного параметра. Этот аргумент динамически формируется хранимой процедурой и его содержимое может различаться. Применяется только к аргументам типа cursor. 26 default Значение по умолчанию для аргумента. Если значение default определено, процедуру можно выполнить без указания значения соответствующего аргумента. Значение по умолчанию должно быть константой или может равняться null. Если в процедуре используется аргумент с ключевым словом like, он может включать символы-шаблоны %, _, [] и [^]. OUTPUT Показывает, что аргумент процедуры является выходным. Значение этого аргумента можно получить при помощи инструкции execute. Используйте выходные аргументы для возврата значений коду, вызвавшему процедуру. Аргументы типов text, ntext и image не могут быть выходными, если процедура не является процедурой CLR. Выходным аргументом с ключевым словом output может быть заполнитель курсора, если процедура не является процедурой CLR. Определяемый пользователем табличный тип не может быть указан в качестве выходного параметра хранимой процедуры. READONLY Указывает, что параметр не может быть обновлен или изменен в теле процедуры. Если тип параметра является определяемым пользователем табличным типом, должно быть указано ключевое слово readonly. RECOMPILE Показывает, что компонент Database Engine не кэширует план выполнения процедуры и что процедура компилируется во время выполнения. Этот аргумент нельзя использовать, если указан аргумент for replication. Задать аргумент recompile для хранимой процедуры CLR нельзя. ENCRYPTION Показывает, что SQL Server выполнит шифрование исходного текста инструкции create procedure. EXECUTE AS Определяет контекст безопасности, в котором должна быть выполнена хранимая процедура. FOR REPLICATION Указывает, что хранимые процедуры, созданные для репликации, не могут выполняться на подписчике. Хранимая процедура, созданная с аргументом for replication, используется как процедура-фильтр и выполняется только во время репликации. Если указан аргумент for replication, параметры не могут быть объявлены. Указать аргумент for replication для хранимой процедуры CLR нельзя. Аргумент recompile не учитывается для процедур, созданных с аргументом for replication. <sql_statement> Одна или несколько инструкций языка Transact-SQL, которые будут включены в состав процедуры. EXTERNAL NAME assembly_name.class_name.method_name Метод сборки .NET Framework, на который должна ссылаться хранимая процедура CLR. Аргумент class_name должен быть допустимым идентификатором SQL Server и соответствовать существующему в сборке классу. Если имя класса включает названия пространств имен, отделенные точками (.), оно должно быть ограничено при помощи квадратных скобок ([ ]) или двойных кавычек (" "). Указанный метод класса должен быть статическим. Для выполнения действий с помощью хранимых процедур и функций пользователю необходимо их вызвать и передать требуемые параметры. При этом в СУБД SQL Server имеется особый тип хранимых процедур, которые называются триггеры DML (языка манипулирования данными, data manipulation language), которые могут выполняться автоматически при вставке/обновление/удалении данных из таблиц (или представлений). SQL Server поддерживает два различных типа триггеров: триггеры after и триггеры instead of. Триггеры after вызываются после выполнения команды, которой они назначены, а триггеры instead of вызываются вместо команды. After-триггеры можно использовать для команд insert, update и delete и только для таблиц, но не для представлений. Для каждой из этих трех команд могут быть установлены несколько триггеров. При этом один триггер может быть применен для любой комбинации этих трех команд. Триггер after вызывается после того, как выполнены все операции по обработке ограничений низкого уровня, и не будут вызваны в случае нарушения ограничения. Например, если осуществляется попытка вставить строку, которая нарушает ограничение primary key для таблицы, оператор insert не будет выполнен до того, как произойдет вызов триггера. Триггеры instead of заменяют команду, для которой они объявлены. Подобно триггерам after, 27 триггеры instead of для команд insert, update или delete. Один триггер может быть применен к нескольким командам. Однако, в отличие от триггеров after, вы можете создавать триггеры instead of как для таблиц, так и для представлений, но для каждого действия над этой таблицей или представлением может быть создан только один триггер instead of. Поскольку триггеры instead of могут быть объявлены для представлений, они чрезвычайно полезны для получения функциональных возможностей представления, которые не могут быть доступны иным способом. Например, SQL Server не дает возможности применить для представления оператор insert, содержащий фразу group by, но позволяет определить триггер instead of insert для представления. Вы можете воспользоваться триггером для вставки записей в таблицы, лежащие в основе представления, тем самым давая знать пользователю, что новая строка была вставлена в представление. Инструкции триггеров DML используют две специальные таблицы: deleted (удаленные значения) и inserted (вставленные значения). SQL Server автоматически создает эти таблицы и управляет ими. Эти временные таблицы, находящиеся в оперативной памяти, используются для проверки результатов изменений данных и для установки условий срабатывания триггеров DML. Нельзя в этих таблицах изменять данные напрямую или выполнять над ними операции языка DDL, например инструкцию create index. В DML-триггерах таблицы inserted и deleted используются для выполнения следующих операций: Расширение ссылочной целостности между таблицами. Вставка или обновление данных в базовых таблицах соответствующего представления. Проверка на ошибки и принятие соответствующих мер в связи с появлением ошибок. Поиск различий между состояниями таблицы до и после изменения данных и принятие соответствующих мер в зависимости от наличия или отсутствия различий. Содержимое таблиц inserted и deleted зависит от выполняемой операции и является следующим: В таблице deleted находятся копии строк, с которыми работали инструкции delete или update. При выполнении инструкции delete или update происходит удаление строк из таблицы для которой определён триггер и их перенос в таблицу deleted. У таблицы deleted обычно нет общих строк с таблицей триггера (для которой определён триггер). В таблице inserted находятся копии строк, с которыми работали инструкции insert или update. При выполнении транзакции вставки или обновления происходит одновременное добавление строк в таблицу триггера и в таблицу inserted. Строки таблицы inserted являются копиями новых строк таблицы триггера. Транзакция обновления аналогична выполнению операции удаления с последующим выполнением операции вставки; сначала старые строки копируются в таблицу deleted, а затем новые строки копируются в таблицу триггера и в таблицу inserted. При задании условий триггера используйте таблицы inserted и deleted соответственно действию, заставившему триггер сработать. Хотя ссылка на таблицу deleted при проверке инструкции insert или ссылка на таблицу inserted при проверке инструкции delete не приводит к появлению ошибок, но данные тестовые таблицы триггера все равно не содержат в таких случаях никаких строк. Для демонстрирования функциональных возможностей триггеров создадим простую систему логирования, в которой будут указаны все операции, выполненные для таблицы Client. Для этого создадим вспомогательную таблицу (LogTable) и триггер trg_Client для таблицы Client, который логирует информацию о вставке, удалении и обновлении информации о клиентах (см. рис. 32). 28 --Таблица для логирования create table LogTable ( OrderNo bigint identity(1,1) ,DateAction smalldatetime not null ,Operation varchar(10) not null ,Description varchar(500) not null ) go --Триггер для таблицы Client create trigger trg_Client on Client for insert, delete, update as declare @RowCnt int, @Desc varchar(500) ,@NL varchar(2) --Ко-во обработанных строк set @RowCnt=@@rowcount set @NL=char(13)+char(10) --Новая строка --Нельзя удалять несколько строк --за одну операцию if @RowCnt>1 and not exists (select * from inserted) begin rollback --Отмена операции insert LogTable select getdate(),'Удаление' ,'Нельзя удалять более одного клиента' end --if @@rowcount>1 and else begin --Вставка строк if exists(select * from inserted) and not exists(select * from deleted) begin --Формирование результирующей строки set @Desc='Вставлены следующие строки:'+@NL select @Desc=@Desc +'ClientNo='+cast(i.ClientNo as varchar) +', ClientName='+i.ClientName+@NL from inserted i --Вставка insert LogTable select getdate(),'Вставка',@Desc end -- if exists(select * from inserted) else --Удаление (удаление только по одной строке) if exists(select * from deleted) and not exists(select * from inserted) insert LogTable select getdate(),'Удаление' ,'ClientNo='+cast(d.ClientNo as varchar) +', ClientName='+d.ClientName from deleted d else --Обновление данных begin --Формирование результирующей строки set @Desc='Обновлены следующие строки:'+@NL select @Desc=@Desc +'ClientNo='+cast(i.ClientNo as varchar) +case when update(ClientName) then ', ClientName={Старое - '+d.ClientName +'; Новое - '+i.ClientName+'}'+@NL else '' --Проверка других полей end from inserted i join deleted d on d.ClientNo=i.ClientNo --Логирование insert LogTable select getdate(),'Обновление',@Desc end --Обновление данных end --else Рис.32. – Создание триггера для таблицы Client В начале триггера сохраняется значение переменной @@rowcount, в которой хранится 29 количество строк, обработанных последней командой insert/update/delete, которая привела к вызову триггера. Значение этой переменной в общем случае содержит количество строк, обработанных последней командой, поэтому её необходимо сразу же сохранить, т.к. она изменится при первом же использовании команды set. В том случае, если обновлено более чем одна запись и при этом нет строк в таблице inserted, то это указывает на то, что пользователь удаляет несколько клиентов одновременно. В нашем случае это недопустимо, поэтому происходит вызов rollback, выполняющей откат (отмену) транзакции. Т.к. вызов триггера выполняется в той же транзакции, что и оператора модификации данных, то откат транзакции внутри триггера приведёт к отмене всех сделанных изменений. Именно поэтому вставка значений в таблицу логирования выполняется после отката транзакции. При вставке значений (в таблице inserted имеются строки, а в deleted - отсутствуют), всё содержимое копируется в строку таблицы LogTable. При удалении единственной строки также копируется эта строка из таблицы deleted. При изменении строк, выполняется связь таблиц inserted и deleted с помощью естественного внутреннего соединения по значениям первичного ключа (поле ClientNo). Это необходимо для получения исходного значения поля (до изменения) и нового (после изменения). Рассмотрим запросы, изменяющие данные в таблице Client (рис. 33) и результат работы триггера (рис. 34). select 'До изменения данных' select * from Client select * from LogTable --Удаление всех записей (будет прервано) delete Client go --Оператор необходим, т.к. откат транзакции select 'После попытки удалить все строки' select * from LogTable --Удаление строки с ClientNo=1 delete Client where ClientNo=1 select 'После удаления строки с ClientNo=1' select * from Client select * from LogTable --Обновление строки с ClientNo=2 update Client set ClientName='Эдельвейс +' where ClientNo=2 select 'После обновления строки с ClientNo=2' select * from Client select * from LogTable --Вставка новой строки insert Client values('Тестовый клиент') select 'После вставки новой строки' select * from Client select * from LogTable Рис. 33. – Изменение данных в таблице Client 30 Рис. 34. – Результат работы триггера Первоначально (см. рис. 33) выводится содержимое таблицы Client и LogTable. Из рис. 34 видно, что LogTable пуста. Затем предпринимается попытка удалить всех клиентов. В триггере выполняется откат данной операции, о чём появляется сообщение: Сообщение 3609, уровень 16, состояние 1, строка 7 Транзакция завершилась в триггере. Выполнение пакета прервано. Затем выполняется удаление клиента с идентификатором 1. Операция удаления выполняется успешно, т.к. удалена только одна строка. Затем выполняется обновление названия организации и вставка новой. О каждой выполненной операции записывается соответствующая информация в таблицу логирования. В завершении лабораторной работы рассмотрим синтаксис команды create trigger (для DMLтриггера) [1]: CREATE TRIGGER [ schema_name . ] trigger_name 31 ON { table | view } [ WITH <dml_trigger_option> [ ,...n ] ] { FOR | AFTER | INSTEAD OF } { [ INSERT ] [ , ] [ UPDATE ] [ , ] [ DELETE ] } [ WITH APPEND ] [ NOT FOR REPLICATION ] AS { sql_statement [ ; ] [ ,...n ] | EXTERNAL NAME <method specifier [ ; ] > } <dml_trigger_option> ::= [ ENCRYPTION ] [ EXECUTE AS Clause ] <method_specifier> ::= assembly_name.class_name.method_name Рассмотрим основные синтаксические конструкции более подробно: schema_name Имя схемы, к которой принадлежит триггер. trigger_name Имя триггера. Аргумент trigger_name должен соответствовать правилам для идентификаторов — за исключением того, что trigger_name не может начинаться с символов # или ##. table | view Таблица или представление, в которых выполняется триггер DML, иногда указывается как таблица триггера или представление триггера. Указание уточненного имени таблицы или представления не является обязательным. На представление может ссылаться только триггер instead of. Триггеры DML не могут быть описаны в локальной или глобальной временных таблицах. FOR | AFTER Тип after указывает, что триггер DML срабатывает только после успешного выполнения всех операций в инструкции sql, запускаемой триггером. Все каскадные действия и проверки ограничений, на которые имеется ссылка, должны быть успешно завершены, прежде чем триггер сработает. Если единственным заданным ключевым словом является for, аргумент after используется по умолчанию. Триггеры after не могут быть определены на представлениях. INSTEAD OF Указывает, что триггер DML срабатывает вместо инструкции SQL, используемой триггером, переопределяя таким образом действия выполняемой инструкции триггера. Аргумент instead of не может быть указан для триггеров DDL или триггеров входа.На каждую инструкцию insert, update или delete в таблице или представлении может быть определено не более одного триггера instead of. Однако можно определить представления на представлениях, где у каждого представления есть собственный триггер instead of.. { [ DELETE ] [ , ] [ INSERT ] [ , ] [ UPDATE ] } Определяет инструкции изменения данных, по которым срабатывает триггер DML, если он применяется к таблице или представлению. Необходимо указать как минимум одну инструкцию. В определении триггера разрешены любые их сочетания в любом порядке. Для триггеров instead of параметр delete не разрешен в таблицах, имеющих ссылочную связь с указанием каскадного действия on delete. Точно так же параметр update не разрешен в таблицах, имеющих ссылочную связь с указанием каскадного действия on update. WITH APPEND Указывает, что требуется добавить триггер существующего типа. Использование этого необязательного предложения необходимо только при уровне совместимости 65 и ниже. NOT FOR REPLICATION Указывает, что триггер не может быть выполнен, если агент репликации изменяет таблицу, используемую триггером. sql_statement Условия и действия триггера. Условия триггера указывают дополнительные критерии, определяющие, какие события — DML, DDL или событие входа — вызывают срабатывание триггера. Действия триггера, указанные в инструкциях языка Transact-SQL, вступают в силу после попытки 32 использования операции. Триггеры могут содержать любое количество инструкций языка Transact-SQL любого типа, за некоторыми исключениями. Триггеры разработаны для контроля или изменения данных на основании инструкций модификации или определения данных; они не возвращают пользователю никаких данных. Инструкции языка Transact-SQL в составе триггера часто содержат выражения языка управления потоком. Из описаний синтаксиса объявления объектов видно, что любой объект базы данных создаётся с помощью команды create вслед за которой следует название типа объекта (view, function, procedure, trigger). Изменение объекта выполняется с помощью оператора alter (с указанием типа объекта) а удаление с помощью команды drop. Выводы В ходе выполнения лабораторной работы были изучены основные управляющие синтаксические конструкции, применяемые для манипулирования данными тестовой БД, реализованные в языке Transact-SQL. Были рассмотрены некоторые типы объектов баз данных (представления, функции, хранимые процедуры, триггеры), которые могут быть созданы в СУБД Microsoft SQL Server 2008. Литература 1. Электронная документация по Microsoft SQL Server 2008 (Июль http://download.microsoft.com/download/A/D/3/AD3A804B-94A4-4F4E-A5B58E44E5937679/SQLServer2008_BOL_Jul2009_RUS.msi 33 2009 г.), Приложение А. – Индивидуальные задания на лабораторную работу С помощью языка Transact-SQL, реализованного в СУБД Microsoft SQL Server 2008, необходимо разработать следующие объекты БД и представить примеры их вызова и результаты работы: 1. а) Написать хранимую процедуру, которая позволяет добавлять в БД нового клиента, имя которого передаётся в качестве параметра. В конце процедуры необходимо вывести идентификатор нового клиента. б) Разработать процедуру, которая для переданного идентификатора клиента, двух идентификаторов продукта создавала бы строки с информацией о продажах. в) Сделать табличную функцию, выводящую название организации, переданной в виде идентификатора, название купленного продукта и сумму, на которую был приобретён продукт. При этом вывести всю информацию необходимо только по заказам, сделанным после даты, переданной в качестве параметра. 2. а) Написать процедуру принимающую идентификатор организации, которая для всех организаций, идентификатор которых больше переданного, добавит значение поля ClientNo в название организации. б) Разработать табличную функцию, которая посчитает количество проданных товаров и общий доход от продаж, сделанных после даты, переданной в качестве параметра. в) Создать представление, группирующее все имеющиеся продажи по продуктам и вывести проданное количество и название. 3. а) Написать процедуру, удаляющую организацию (и все её заказы) из БД. Идентификатор организации передаётся в виде параметра. б) С помощью табличной функции вывести подробную информацию о каждом заказе (с указанием продукта и организации), сделанном организацией с идентификатором большем, чем значение первой переменной и меньшем, чем значение второй переменной после переданной в качестве параметра даты. в) Написать функцию, группирующую данные о продажах по покупателям и выводящую для каждого из них количество проданного продукта, в названии которого присутствует сочетание букв, переданного с помощью переменной. Анализировать только продажи, выполненные до даты, переданной в качестве второго параметра. 4. а) Написать хранимую процедуру, увеличивающую цену в N раз (указывается в виде параметра) всех заказов, сделанных клиентом, идентификатор которого передан в качестве параметра. б) Разработать табличную функцию, которая выводит все заказы карандашей и ластиков, сделанные покупателем, идентификатор которого передан в качестве параметра. в) Создать представление в БД, которое группирует все продажи по покупателям и продуктам и вывести общее количество проданной продукции и среднюю цену. 5. а) Написать процедуру, которая вставит новый продукт, название которого передано в качестве параметра и сделает несколько продаж данного продукта. б) С помощью табличной функции вывести информацию обо всех заказах, сделанных фирмой, название которой передано в качестве параметра в период с даты, переданной в виде переменной. в) Разработать представление, группирующее все продажи по продуктам и покупателям и определяющее суммарное количество проданной продукции. Результат отсортировать в порядке возрастания названий продуктов и в порядке убывания названий фирм-покупателей. 6. а) Написать процедуру, выполняющую переименование организации. Идентификатор организации и новое название передаются в виде параметров. б) Разработать табличную функцию, выводящую подробную информацию обо всех продажах карандашей за период, границы которого переданы в виде параметров. в) Создать представление, группирующее те продукты, которые были проданы в количестве более 8 штук по покупателю и вывести суммарное количество проданной продукции. 7. а) Разработать процедуру, в которую с помощью трёх переменных передаются названия новых организаций, вставляемых с помощью одного оператора insert. б) Разработать табличную функцию, выводящую подробную информацию о всех продуктах, купленных до даты, переданной в качестве параметра. Результат отсортировать в порядке возрастания названий продуктов. 34 в) Создать представление, группирующее все продажи по году, месяцу и наименованию продукта и вывести среднее количество проданного продукта в порядке возрастания. 8. а) Написать хранимую процедуру, добавляющую нового покупателя и продукт, а также создающую 2 продажи этого продукта покупателю. б) Создать функцию, извлекающую подробную информацию о продажах продуктов, название которых начинается на переданную в качестве параметра букву начиная с переданной даты. в) Разработать функцию, которая все продажи на сумму, меньшую чем значение переданного параметра сгруппирует по названию продукта и выведет общее количество проданных товаров. 9. а) Создать процедуру, выполняющую переименование продукта. Старое и новое названия передаются в виде параметров. б) Создать представление, которое выведет подробную информацию о продажах треугольников индивидуальным предпринимателям. в) Написать функцию, группирующую все продажи двум клиентам, идентификаторы которых переданы в виде параметров по организациям и вывести общую сумму, на которую были проданы все товары. 10. а) Создать процедуру, принимающую четыре строковых переменных и вставляющую переданные названия организации с помощью одного запроса. б) Написать процедуру, придающую в качестве входного строкового параметра идентификаторы клиентов (через запятую) и формирующую динамический запрос выборки информации о всех продажах товаров переданным клиентам. в) Написать функцию, которая все продажи, выполненные с переданной даты, и в которых продано более 5 штук товаров, сгруппирует по покупателям и для каждого определить общую уплаченную сумму за все товары. 11. а) Разработать процедуру, которая удалит все заказы клиента, идентификатор которого передан в качестве параметра и в которых было куплено менее N единиц каждого товара. Значение N также передаётся параметром. б) Разработать табличную функцию, которая выберет все заказы для переданного в виде названия организации клиента, сделанные на сумму, более чем значение переданной переменной. в) Создать представление, которое все продажи карандашей и треугольников группирует по клиентам и товарам и рассчитывает среднее количество проданного товара. 12. а) Написать процедуру, которая для переданного названия клиента, сделает три продажи различных продуктов с помощью трёх операторов insert. б) Написать табличную функцию, которая выводит подробную информацию о продажах ластика более N штук в период с DtStart по DtFinish. Значения N, DtStart и DtFinish необходимо передать в виде параметра. в) Разработать представление, которое для клиентов с идентификаторами 1 и 4 выведет информацию, сгруппированную по клиентам и продуктам с указанием среднего количества и общей суммы продаж. 13. а) Написать хранимую процедуру, которая заносит информацию о трёх продажах маркеров различным клиентам, идентификаторы которых переданы с помощью трёх переменных, с помощью одного оператора insert. б) Разработать функцию, которая выведет список всех товаров дороже значения переданной переменной с указанием названия и клиента, купившего его. в) Создать представление, которое группирует по месяцам и продуктам все продажи и выводит среднее, максимальное и минимальное количество проданного товара. 14. а) Разработать процедуру, которая создаст три продажи маркеров клиенту, идентификатор которого передан в качестве параметра в разные периоды времени. б) С помощью функции посчитать количество продаж товаров и общий доход от продаж, сделанных после даты, переданной в качестве параметра. в) Создать представление, которое группирует все продажи клиентам ОАО Юг-Креатив, ИП Петров П.П. и ИП Иванов И.И. по организациям и выводит общую сумму, на которую были проданы все товары и общее количество проданных товаров. 15. а) С помощью процедуры добавить три продажи маркера клиенту, название которого передано в 35 качестве параметра с помощью одного выражения insert с указанием названий полей. б) Написать табличную функцию, которая выведет информацию обо всех заказах (с указанием названия продукта) сделанных фирмой ООО Эдельвейс в период, границы которого переданы в виде двух переменных. в) Разработать представление, которое все продажи карандашей и маркеров сгруппирует по клиентам и товаром и рассчитает общее количество проданного товара и сумму, на которую они были проданы. 16. а) С помощью хранимой процедуры удалить все заказы клиента, идентификатор которого передан в качестве параметра на сумму меньшую, чем 100 рублей. б) Разработать табличную функцию, которая выводит подробную информацию о каждом заказе, сделанным организациями с идентификаторами больше чем значение переданной переменной после даты, переданной в качестве параметра. в) С помощью представления сгруппировать те продукты, которые были проданы в количестве менее 10 штук по покупателю и продукту, а затем и вывести полученную сумму денег. 17. а) С помощью хранимой процедуры, для клиента, идентификатор которого передан в качестве параметра, сделать три продажи различных продуктов с помощью одного оператора insert. б) Написать табличную функцию, выводящую подробную информацию о всех продуктах, купленных некоторой организацией (идентификатор передаётся в качестве параметра) после переданной даты. Результат отсортировать в порядке возрастания названий продуктов и дат продажи. в) С помощью представления сгруппировать все продажи по покупателям и продуктам и вывести общее количество проданной продукции и среднюю, максимальную и минимальную цену. 18. а) Написать процедуру, выполняющую увеличение цены в 3 раза всех заказов, сделанных клиентом, название которого передано с помощью параметра, в которых куплено более 5 единиц продукции. б) С помощью табличной функции выбрать подробную информацию о всех продажах товаров клиенту, идентификатор клиента которого передан с помощью переменной. Результат отсортировать по возрастанию даты и убыванию названия продуктов. в) Разработать представление, которое все продажи, выполненные до 01.04.2010, и в которых продано менее 10 штук товаров сгруппирует по покупателям, и для каждого определить общую уплаченную сумму за все товары. 19. а) С помощью хранимой процедуры добавить несколько продаж авторучек нескольким организациям в одном операторе insert. б) Написать функцию, которая выведет информацию обо всех заказах, сделанных фирмой, идентификатор которой передан в качестве параметра в указанный период (задан с помощью 2 параметров) на сумму более 60 рублей. в) С помощью табличной функции, для двух переданных идентификаторов клиента вывести информацию, сгруппированную по названию продукта с указанием максимального и минимального количества и общей суммы продаж. 20. а) Написать процедуру, которая добавит новую организацию, название которой передано в виде параметра и создаст несколько продаж различных товаров. б) С помощью представления сгруппировать все продажи по месяцам и организациям и вывести общее количество проданного продукта и сумму продаж за месяц. в) С помощью функции посчитать количество продаж товаров и общий доход от продаж, сделанных после даты, переданной в качестве параметра. 36