Лабораторна робота № 5.
SQL/CLR, Using .NET Procedural Code, UDT, TRIGGERS
1. SQL/CLR
Мета: Ознайомитись із використанням CLR у SQL Server 2008
1. Завантаження і каталогізації збірки
1.1. Відкрийте проект source/sqlclr/MetricConverter.sln (VB або C#) у Visual Studio.
Примітка.
В папці Before – проекти із скриптами, які потрібно дописати та доопрацювати.
В папці After – результуючі проекти та скрипти для самоперевірки.
Прогляньте код проекту. Зауважимо, що це звичайна .NET збірка (assembly). Скомпілюйте збірку і скопіюйте її DLL в папку, з якої буде зручно завантажити у SQL Server.
1.2. Відкрийте у SQL Server Management Studio проект SQLCLR.ssmssln. Зверніть увагу, що в ньому містяться деякі SQL Query файли з неповним кодом. Вам потрібно почати роботу із запиту catalog.sql, зробивши поточною ВАШУ базу даних. Використайте команду CREATE ASSEMBLY для каталогізації збірки в SQL Server. Перевірте коректність роботи CREATE ASSEMBLY. Якщо є помилки, змініть налаштування, щоб завантажити CLR в SQL Server.
CREATE ASSEMBLY math FROM 'c:\types\math.dll'
псевдо ^ ім’я збірки ^
1.3. Переконайтеся, що ім’я збірки має збігається із назвою-псевдо при каталогізації в SQL Server, намагаючись каталогізувати збірку під іншим іменем об’єкта бази даних. Враховуючи регістр літер перевірте чутливість імен збірки, об’єкта бази даних.
1.4. Каталогізуйте одну із статичних функцій перетворення як користувацьку SQL Server функцію за допомогою CREATE FUNCTION. Потрібно вказати ім'я функції, використовуючи наступний синтаксис:
CREATE FUNCTION FunctionName (@parm1 type1...)
RETURNS sometype
EXTERNAL NAME AssemblyName.ClassName.MethodName

Кількість і типи параметрів і значення, що повертається повинні збігатися у T-SQL і .NET (підказка: в T-SQL немає типу даних DOUBLE, який присутній у .NET). З урахуванням регістра літер перевірте чутливість ім'я класу, ім'я методу, та обох цих імен. Зауважте, що ви повинні використовувати повне ім'я .NET-класів (в тому числі простір імен - namespaces) і ClassName як це використовує .NET - імена повинні бути екрановані (у дужки) наступним чином: [MyNamespace.MyClass] або [MyNamespace].[MyClass]. Напишіть Transact-SQL код для виконання цієї функції.
1.5. Спробуйте видалити оригінальний файл збірки (MetricConverter.dll), який використовувався при запуску CREATE ASSEMBLY. Чи отримали ви успіх? Чи виконувалася користувацька функція після видалення файлу збірки? Де SQL Server зберігає образ збірки? Підказка: подивіться на вміст таблиці sys.assembly_files.
1.6. Спробуйте видалити збірку MetricConverter.
DROP ASSEMBLY math
Чи отримали ви успіх командою DROP? Спочатку ви повинні видалити всі залежності для користувацької функції (UDF), перед тим як зможете видалити збірку MetricConverter. Ви можете перевірити залежності у каталозі sys.assembly_modules, або клацнувши правою кнопкою мишки на збірці в SQL Server Management Studio, і вибравши "Показати залежності / show dependencies". Після видалення залежностей, видаліть збірку. SQL Server може підтримувати опцію WITH CASCADE автоматичного каскадного видалення залежностей і іншого коду.
2. Залежності збірки
2.1. Відкрийте проект source/sqlclr/TempFunctions.sln (VB.NET або C #) і зверніть увагу, що код для функції addtemp і subtracttemp використовує метричні функції перетворення. Скомпілюйте ці проекти і скопіюйте DLL у папку для завантаження. Каталогізуйте лише TempFunctions збірку до бази даних.
2.2. Виконайте наступний Transact-SQL код для вивчення SQL Server представлень метаданих.
SELECT * FROM sys.assemblies
GO
SELECT * FROM sys.assembly_files
GO
SELECT *
Зверніть увагу, що збірка MetricConverter була каталогізована автоматично до бази даних. Спробуйте виконати аналогічний запит CREATE FUNCTION з частини 1.4. Чи виконується він успішно? Спробуйте видалити збірку MetricConverter.
2.3. Видаліть збірку TempFunctions. Чи видалилася автоматично збірка MetricConverter?
2.4. Складіть Transact-SQL, щоб зробити збірку MetricConverter "видимою", для того щоб можна було каталогізувати одну з її функцій.
CREATE ASSEMBLY asm from 'c:\mydir\asm.dll'
GO
-- додання інформації для відлагодження
ALTER ASSEMBLY asm
ADD FILE FROM 'c:\mydir\asm.pdb'
-- зараз робимо збірку видимою
ALTER ASSEMBLY dep1
WITH VISIBILITY = ON
GO
3*. Завантаження збірки з байтів
3.1. Використовуючи системне подання, з яким ви щойно ознайомились, напишіть код SQL для отримання коду збірки MetricConverter у Transact-SQL змінну. Тепер використовуючи цю змінну каталогізуйте код в різні бази даних. Підказка: запишіть код в змінну типу VARBINARY (Max), а потім використовуйте альтернативний синтаксис CREATE ASSEMBLY для перекаталогізації її в нову базу даних.
2. CLRProcs (Using .NET Procedural Code)
Мета: Ознайомитись із використанням збережених процедур у SQL Server 2008
1. Каталогізація коду процедури
1.1. Відкрийте проект source/clrprocs/CLRProcs.ssmssln у SQL Server Management Studio, і файл запитів cataloging.sql. Створіть нову базу даних із назвою "Demo1" і каталогізуйте метричну збірку до бази даних Demo1 використовуючи CREATE ASSEMBLY. Проекти для збірок є в source/clrprocs/MetricConverter.sln (VB або C#).
1.2. Каталогізуйте користувацьку функцію FahrenheitToCentigrade і KilogramsToPounds за допомогою CREATE FUNCTION. Після того як ви їх каталогізували, напишіть Transact-SQL код для виклику функції. Тепер спробуйте передати значення NULL у функцію KilogramsToPounds допомогою Transact-SQL коду. Що відбувається?
1.3. Створіть таблицю, що містить чотири колонки: ReadingDate (datetime), Місто (varchar), hitemp_far і hitemp_cen. Hitemp_cen повинна бути обчислювальною колонкою з використанням функції FahrenheitToCentigrade. Тепер спробуйте додати індекс на основі колонки hitemp_cen. Що відбувається?
Перекомпілюйте збірку MetricConversion додавши SqlFunction атрибут до функції FahrenheitToCentigrade щоб вказати, що операції з плавуючою точкою є неточними.
[SqlFunction(DataAccess=DataAccessKind.None, IsDeterministic=true, IsPrecise=true)]
Видаліть таблицю, створіть її та індекс заново. Змініть значення атрибутів SqlFunction так, щоб ви могли створити індекс (Підказка: Ви повинні використовувати атрибут PERSISTED для обчислювального стовпця під час визначення таблиці).
1.4. Додайте деякі температурні показники в таблицю. Наприклад:
INSERT readings VALUES('7/4/03', 'Boston', 75)
і
INSERT readings VALUES('7/5/03', 'Boston', 90)
Тепер подивіться на вміст таблиці. Подивіться на значення в стовпці Hitemp_Cen. Чи вони правильні?
2. Використання SqlTypes
2.1.  SQL-99 специфікація каже, що не-SQL користувацькі функції можуть вказати (за допомогою функції SQL CREATE FUNCTION) чи можуть вони бути викликані з параметром NULL. Змініть код збірки та перекаталогізуйте функцію так, щоб виклик функції для вхідного параметру NULL повертав значення NULL.
2.2. Змініть функцію FahrenheitToCentigrade для одного з типів у System.Data.SqlTypes для всіх параметрів і відповідний код повернення. Вставте програмний код для перевірки передачі NULL значення. Перекаталогізуйте і перевірте ще раз код із використанням NULL значення для одного чи обох параметрів.
2.3. Перепишіть одну з функцій метричного конвертування із використанням .NET System.Decimal або System.Data.SqlTypes.SqlDecimal в якості параметрів. Поекспериментуйте з різними дробовими числами на вході та виході.
3. Безпосереднє вказання параметрів

3.1. Додайте новий клас в проект. Створіть дві збережені процедури AddToTemp і AddAndReturnBoth в .NET мовою за вашим вибором. Збережена процедура AddToTemp має додавати значення декількох градусів температури і повертати нове значення. Збережена процедура AddAndReturnBoth має приймати значення температури за Фаренгейтом і кількість градусів і повертати два вихідних параметри: значення в кожній температурній шкалі. Прототипи методів виглядають наступним чином:
static void AddToTemp(double thetemp, double theadder);
static void AddAndReturnBoth(double thetemp, double theadder,
double result_f, double result_c);
3.2. Створіть ці функції, каталогізуйте їх за допомогою CREATE PROCEDURE і протестуйте їх використовуючи Transact-SQL код. Який тип параметрів потрібно вказати у CREATE PROCEDURE?
3.3. Змініть функції так, щоб в AddToTemp температура передавалася за посиланням. У AddAndReturnBoth обидва результуючі параметри мають повертатися за посиланням (в C# ви можете використовувати ключове слово "out"). Каталогізуйте ці функції використовуючи CREATE PROCEDURE DDL і протестуйте за допомогою Transact-SQL коду. Який тип параметрів потрібно вказати у CREATE PROCEDURE? Порівняйте це із попереднім варіантом.
3. UDT
Мета: Ознайомитись із використанням користувацьких типів даних (UDT).
1. Створення користувацького типу даних
1.1.      Відкрите проект source/udts/ComplexNumber (C # або VB.NET). Зауважимо, що цей клас містить:           1. Дві приватні змінні SqlDouble. Вони відомі як атрибути в специфікації SQL.
      2. Відкриті властивості для роботи з приватними змінними.
1.2. По-перше, розкоментуйте відкриті властивості. Далі, нам необхідно реалізувати методи Parse і ToString для підтримки конвертування в UDT від та до типу даних VARCHAR. Вам потрібно зробити просту реалізацію розбору ComplexNumber (комплексного числа) із стрічки у форматі "R:li" та друк ComplexNumber як VARCHAR у форматі R:li, де R та I – відповідно дійсна та уявна частини комплексного числа. Наприклад, комплексне число з дійсною частиною 3.4 та уявною частиною 7.3, буде представлено як "3.4:7.3і".
1.3. Далі, вам потрібно реалізувати INullable інтерфейс. У прикладі, це зроблено за вас. Ми визначили підтримку NULL лише для дійсної частини комплексного числа.. Зверніть увагу, що значення SQL NULL не аналогічне посилання NULL у .NET. Вам потрібно перевизначити реалізації за замовчуванням GetHashCode для повернення HashCode за значенням екземпляра.
1.3.1. Вкажіть, що ваш ComplexNumber підтримує INullable інтерфейс. Ваша реалізація може повертати значення NULL у випадку, якщо дійсна і умовна частина комплексного числа є NULL. Як це вже реалізовано в коді?
1.3.2. Реалізуйте метод інтерфейса INullable.IsNull (це вже також реалізовано).
1.3.3. Реалізуйте статичну властивість з іменем Null, що повертає екземпляр NULL вашого UDT.
1.3.4. Внесіть зміни у свої властивості, і метод Parse для перевірки формату стрічки і поверненню помилки якщо він не коректний.
1.4. Тепер потрібно додати атрибут SqlUserDefinedTypeAttribute. Цей атрибут вказує, які методи серіалізації ви будете використовувати, чи ваш тип є фіксованої довжини та/або байт впорядкованим, а також максимальну довжина. Виберіть Native метод серіалізації, які зроблять ваш UDT довжиною 8 байт. Вкажіть IsByteOrdered = true, що дозволить вашому ComplexNumbers взяти участь у порівнянні величин. Подумайте про можливості використання цього параметра.
1.5. Скомпілюйте ваш тип. Скопіюйте DLL до папки для завантаження.
2. Завантаження та використання UDT
2.1. Відкрийте проект source/udts/UDTS.ssmssln та запит SQL ComplexNumber.sql.
2.2. Каталогізуйте збірку до сервера використовуючи CREATE ASSEMBLY. Зауваження, область видимості типу даних і збірки є одна база даних.
CREATE ASSEMBLY Point
FROM 'c:\types\Point.dll'
GO
2.3. Каталогізуйте тип даних використовуючи інструкцію CREATE TYPE.
CREATE TYPE PointCls
EXTERNAL NAME Point.PointCls
GO
2.4. Створіть таблицю з двома колонками. Одна колона є первинним ключем а інша це стовпець, який має тип вашого UDT (тобто ComplexNumber). Використовуйте символічне ім'я для класу ComplexNumber, яке ви використовували при створенні типу.
CREATE TABLE point_tab(
id int primary key,
thepoint PointCls)
INSERT INTO point_tab
values(1, '100:200')
DECLARE @p PointCls
SET @p = convert(PointCls, '300:400')
2.5. Заповніть таблицю рядками. Для того, щоб використовувати UDT колонку в якості значення в операторі INSERT, ви повинні використовувати його стрічкове представлення, або використовувати функції CAST або CONVERT для конвертування із стрічки. Напишіть T-SQL код, що вибирає первинний ключ, дійсну та уявну частини комплексного числа.
2.6. Спробуйте вставити значення в не коректному форматі, наприклад, "А:5i". Що відбувається?
2.7. Відкриті методи у вашому UDT доступні у запиті. Перевірити це. Якщо імена вашої дійсної і уявної частин "realpart" і "imagpart" і назва стовпця із комплексним числом "cnumber", то спробуйте запит типу "Select cnumber.realpart, cnumber.imagpart від complex_tab".
2.8. Додайте T-SQL код, який буде здійснювати порівняння комплексних чисел. Наприклад, якщо одне з чисел в таблиці "5:3і", спробуйте запит:
select cnumber.realpart, cnumber.imagpart from complex_tab
where cnumber = Convert(ComplexNumber, '5:3i')
2.9. Реалізуйте та каталогізуйте користувацьку функцію в T-SQL, яка буде додавати два комплексні числа, додаючи відповідно дійсну та уявні частини. Функція має приймати два параметра типу ComplexNumber і повертати ComplexNumber. Напишіть T-SQL код для тестування цієї функціональності. Перевірте функціональнітсь при умові коли вхідні параметри є NULL.
4. TRIGGER
CREATE TRIGGER
Создает триггер языка обработки данных, DDL или входа. Триггер — это особая разновидность хранимой процедуры, выполняемая автоматически при возникновении события на сервере базы данных. Триггеры языка обработки данных выполняются по событиям, вызванным попыткой пользователя изменить данные с помощью языка обработки данных. Событиями DML являются процедуры INSERT, UPDATE или DELETE, применяемые к таблице или представлению.
Триггеры DDL срабатывают в ответ на ряд событий языка определения данных (DDL). Эти события прежде всего соответствуют инструкциям Transact-SQL CREATE, ALTER, DROP и некоторым системным хранимым процедурам, которые выполняют схожие с DDL операции. Триггеры входа могут срабатывать в ответ на событие LOGON, возникающее при установке пользовательских сеансов. В компоненте SQL Server 2005 Database Engine триггеры могут быть созданы непосредственно из инструкций Transact-SQL или из методов сборок, созданных в среде CLR платформы Microsoft.NET Framework, и переданы на экземпляр SQL Server. SQL Server допускает создание нескольких триггеров для любой указанной инструкции.
INCLUDEPICTURE "http://i.msdn.microsoft.com/ms189799.05b1d166-d807-482c-891f-30b3b6b58046%28ru-ru,SQL.90%29.gif" \* MERGEFORMATINET HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms177563%28SQL.90%29.aspx" Соглашения о синтаксическом обозначении в Transact-SQL
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Синтаксис
Trigger on an INSERT, UPDATE, or DELETE statement to a table or view (DML Trigger)
CREATE TRIGGER [ schema_name . ]trigger_name
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
Trigger on a CREATE, ALTER, DROP, GRANT, DENY, REVOKE, or UPDATE STATISTICS statement (DDL Trigger)
CREATE TRIGGER trigger_name
ON { ALL SERVER | DATABASE }
[ WITH <ddl_trigger_option> [ ,...n ] ]
{ FOR | AFTER } { event_type | event_group } [ ,...n ]
AS { sql_statement [ ; ] [ ,...n ] | EXTERNAL NAME < method specifier > [ ; ] }
<ddl_trigger_option> ::=
[ ENCRYPTION ]
[ EXECUTE AS Clause ]
<method_specifier> ::=
assembly_name.class_name.method_name
Trigger on a LOGON event (Logon Trigger)
CREATE TRIGGER trigger_name
ON ALL SERVER
[ WITH <logon_trigger_option> [ ,...n ] ]
{ FOR | AFTER } LOGON
AS { sql_statement [ ; ] [ ,...n ] | EXTERNAL NAME <method specifier> [ ; ] }
<logon_trigger_option> ::=
[ ENCRYPTION ]
[ EXECUTE AS Clause ]
<method_specifier> ::=
assembly_name.class_name.method_name
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Аргументы
schema_name
Имя схемы, которой принадлежит триггер DML. Триггеры DML ограничены областью схемы таблицы или представления, для которых они созданы. Аргумент schema_name не может быть указан для триггеров DDL или входа.
trigger_name
Имя триггера. Аргумент trigger_name должен соответствовать правилам для HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms175874%28SQL.90%29.aspx" идентификаторов — за исключением того, что он не может начинаться с символов # или ##.
table | view
Таблица или представление, в которых выполняется триггер DML, иногда указывается как таблица триггера или представление триггера. Указание уточненного имени таблицы или представления не является обязательным. На представление может ссылаться только триггер INSTEAD OF. Триггеры DML не могут быть описаны в локальной или глобальной временных таблицах.
DATABASE
Применяет область действия триггера DDL к текущей базе данных. Если этот аргумент определен, триггер срабатывает всякий раз при возникновении в базе данных события типа event_type или event_group.
ALL SERVER
Применяет область действия триггера DDL или триггера входа к текущему серверу. Если этот аргумент определен, триггер срабатывает всякий раз при возникновении в любом месте на текущем сервере события типа event_type или event_group.
WITH ENCRYPTION
Затемняет текст инструкции CREATE TRIGGER. Использование аргумента WITH ENCRYPTION не позволяет публиковать триггер как часть репликации SQL Server. Параметр WITH ENCRYPTION не может быть указан для триггеров CLR.
EXECUTE AS
Указывает контекст безопасности, в котором выполняется триггер. Позволяет управлять учетной записью пользователя, используемой экземпляром SQL Server для проверки разрешений на любые объекты базы данных, ссылаемые триггером.
Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms188354%28SQL.90%29.aspx" EXECUTE AS, предложение (Transact-SQL).
FOR | AFTER
Тип AFTER указывает, что триггер срабатывает только после успешного выполнения всех операций в инструкции SQL, запускаемой триггером. Все каскадные действия и проверки ограничений, на которые имеется ссылка, должны быть успешно завершены, прежде чем триггер сработает.
Если единственным заданным ключевым словом является FOR, аргумент AFTER используется по умолчанию.
Триггеры AFTER не могут быть определены на представлениях.
INSTEAD OF
Указывает, что триггер DML срабатывает вместо инструкции SQL, используемой триггером, переопределяя таким образом действия выполняемой инструкции триггера. Аргумент INSTEAD OF не может быть указан для триггеров DDL или триггеров входа.
На каждую инструкцию INSERT, UPDATE или DELETE в таблице или представлении может быть определено не более одного триггера INSTEAD OF. Однако можно определить представления на представлениях, где у каждого представления есть собственный триггер INSTEAD OF.
Триггеры INSTEAD OF не разрешены для обновляемых представлений, использующих параметр WITH CHECK OPTION. SQL Server вызывает ошибку, если триггер INSTEAD OF добавляется к обновляемому представлению с параметром WITH CHECK OPTION. Пользователь должен удалить этот параметр при помощи инструкции ALTER VIEW перед определением триггера INSTEAD OF.
{ [ DELETE ] [ , ] [ INSERT ] [ , ] [ UPDATE ] }
Определяет инструкции изменения данных, по которым срабатывает триггер DML, если он применяется к таблице или представлению. Необходимо указать как минимум одну инструкцию. В определении триггера разрешены любые их сочетания в любом порядке.
Для триггеров INSTEAD OF параметр DELETE не разрешен в таблицах, имеющих ссылочную связь с указанием каскадного действия ON DELETE. Точно так же параметр UPDATE не разрешен в таблицах, имеющих ссылочную связь с указанием каскадного действия ON UPDATE.
event_type
Имя события языка Transact-SQL, которое после выполнения вызывает срабатывание триггера DDL. Список событий, которые могут быть использованы в триггерах DDL, приведен в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms189871%28SQL.90%29.aspx" DDL-события, вызывающие срабатывание триггеров DDL.
event_group
Имя стандартной группы событий языка Transact-SQL. Триггер DDL срабатывает после возникновения любого события языка Transact-SQL, принадлежащего к группе event_group. Список групп событий, которые могут быть использованы в триггерах DDL, приведен в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms191441%28SQL.90%29.aspx" Группы событий для использования с триггерами DDL.
После завершения инструкции CREATE TRIGGER параметр event_group работает в режиме макроса, добавляя охватываемые им типы события в представление каталога sys.trigger_events.
WITH APPEND
Указывает, что требуется добавить триггер существующего типа. Использование этого необязательного предложения необходимо только при уровне совместимости 65 и ниже. Если уровень совместимости 70 и выше, для добавления дополнительного триггера существующего типа предложение WITH APPEND не требуется. Данное поведение используется по умолчанию для инструкции CREATE TRIGGER с настройкой уровня совместимости 70 и выше. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms178653%28SQL.90%29.aspx" sp_dbcmptlevel (Transact-SQL).
Предложение WITH APPEND не может быть использовано для триггеров INSTEAD OF или при явном указании триггера AFTER. Предложение WITH APPEND может использоваться только при указании параметра FOR без INSTEAD OF или AFTER из соображений поддержки обратной совместимости. Предложение WITH APPEND не может быть указано, если указан параметр EXTERNAL NAME (в случае триггера CLR).
NOT FOR REPLICATION
Указывает, что триггер не может быть выполнен, если агент репликации изменяет таблицу, используемую триггером. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms152529%28SQL.90%29.aspx" Управление ограничениями, идентификаторами и триггерами с помощью параметра «NOT FOR REPLICATION».
sql_statement
Условия и действия триггера. Условия триггера указывают дополнительные критерии, определяющие, какие события — DML, DDL или событие входа — вызывают срабатывание триггера.
Действия триггера, указанные в инструкциях языка Transact-SQL, вступают в силу после попытки использования операции.
Триггеры могут содержать любое количество инструкций языка Transact-SQL любого типа, за некоторыми исключениями. Дополнительные сведения см. в разделе «Примечания». Триггеры разработаны для контроля или изменения данных на основании инструкций модификации или определения данных; они не возвращают пользователю никаких данных. Инструкции языка Transact-SQL в составе триггера часто содержат выражения HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms174290%28SQL.90%29.aspx" языка управления потоком.
Триггеры DML используют логические (концептуальные) таблицы deleted и inserted. По своей структуре они подобны таблице, на которой определен триггер, то есть таблице, к которой применяется действие пользователя. В таблицах deleted и inserted содержатся старые или новые значения строк, которые могут быть изменены действиями пользователя. Например, для запроса всех значений таблицы deleted можно использовать инструкцию:
SELECT *
FROM deleted
Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms191300%28SQL.90%29.aspx" Использование таблиц inserted и deleted.
Триггеры DDL и триггеры входа собирают сведения о событиях, запускаемых с помощью функции HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms173781%28SQL.90%29.aspx" EVENTDATA (Transact-SQL). Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms187909%28SQL.90%29.aspx" Использование функции EVENTDATA.
SQL Server не допускает использование ссылок на столбцы типа text, ntext или image таблиц inserted и deleted в триггерах DELETE, INSERT или UPDATE, если установлен уровень совместимости 70. Доступ к значениям столбцов типа text, ntext и image в таблицах inserted и deleted невозможен. Для получения новых значений для триггера INSERT или UPDATE соедините таблицу inserted с исходной обновляемой таблицей. При уровне совместимости 65 и меньше для столбцов типа text, ntext или image, допускающих значения NULL, из таблиц inserted или deleted возвращаются значения NULL; если в этих столбцах значения NULL недопустимы, возвращаются пустые строки.
При уровне совместимости 80 и выше SQL Server разрешает обновление столбцов типа text, ntext или image с помощью триггера INSTEAD OF, применяемого к таблицам или представлениям.
< method_specifier >
Указывает метод сборки для связывания с CLR-триггером. Метод не должен иметь аргументов и возвращать значение типа void. Аргумент class_name должен быть допустимым идентификатором SQL Server, а в сборке должен существовать класс с таким именем, видимый во всей сборке. Если класс имеет имя, содержащее точки («.») для разделения частей пространства имен, имя класса должно быть заключено в квадратные скобки ([ ]) или двойные кавычки (" "). Класс не может быть вложенным.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Замечания
Триггеры DML
Триггеры DML часто используются для соблюдения бизнес-правил и целостности данных. В SQL Server декларативное ограничение ссылочной целостности обеспечивается инструкциями ALTER TABLE и CREATE TABLE. Однако декларативное ограничение ссылочной целостности не обеспечивает ссылочную целостность между базами данных. Ограничение ссылочной целостности подразумевает выполнение правил связи между первичными и внешними ключами таблиц. Для обеспечения ограничений ссылочной целостности используйте в инструкциях ALTER TABLE и CREATE TABLE ограничения PRIMARY KEY и FOREIGN KEY. Если ограничения распространяются на таблицу триггера, они проверяются после срабатывания триггера INSTEAD OF и до выполнения триггера AFTER. В случае нарушения ограничения выполняется откат действий триггера INSTEAD OF, и триггер AFTER не срабатывает.
Первые и последние триггеры AFTER, которые будут выполнены в таблице, могут быть определены с использованием процедуры sp_settriggerorder. Для таблицы можно определить только один первый и один последний триггер для каждой из операций INSERT, UPDATE и DELETE. Если в таблице есть другие триггеры AFTER, они будут выполняться случайным образом.
Если инструкция ALTER TRIGGER меняет первый или последний триггер, первый или последний набор характеристик измененного триггера удаляется, а порядок сортировки должен быть установлен заново с помощью процедуры sp_settriggerorder.
Триггер AFTER выполняется только после того, как вызывающая срабатывание триггера инструкция SQL была успешно выполнена. Успешное выполнение также подразумевает завершение всех ссылочных каскадных действий и проверки ограничений, связанных с измененными или удаленными объектами.
Если триггер INSTEAD OF, определенный для таблицы, выполняет по отношению к таблице какую-либо инструкцию, которая бы снова вызвала срабатывание триггера INSTEAD OF, триггер рекурсивно не вызывается. Вместо этого инструкция обрабатывается так, как если бы у таблицы отсутствовал триггер INSTEAD OF, и начинается применение последовательности ограничений и выполнение триггера AFTER. Например, если триггер определен в виде триггера INSTEAD OF INSERT для таблицы и выполняет инструкцию INSERT для этой же таблицы, инструкция INSERT не вызывает нового срабатывания триггера. Команда INSERT, выполняемая триггером, начинает процесс действий применения ограничений и обработки всех триггеров AFTER INSERT, определенных для данной таблицы.
Если триггер INSTEAD OF, определенный для представления, выполняет по отношению к представлению какую-либо инструкцию, которая бы снова вызвала срабатывание триггера INSTEAD OF, триггер рекурсивно не вызывается. Вместо этого инструкция выполняет изменение базовых таблиц, на которых основано представление. В данном случае определение представления должно удовлетворять всем ограничениям, установленным для обновляемых представлений. Сведения об определении обновляемых представлений см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms180800%28SQL.90%29.aspx" Изменение данных через представление.
Например, если триггер определен как INSTEAD OF UPDATE для представления и выполняет инструкцию UPDATE для этого же представления, инструкция UPDATE, выполняемая триггером, не вызывает нового срабатывания триггера. Инструкция UPDATE, выполняемая в триггере, обрабатывает представление так, как если бы в представлении не имелось триггера INSTEAD OF. Столбцы, измененные с помощью инструкции UPDATE, должны разрешаться в одну базовую таблицу. Каждая модификация базовой таблицы вызывает применение последовательности ограничений и взвод триггеров AFTER, определенных для данной таблицы.
Проверка действий инструкций UPDATE или INSERT на указанные столбцы
Триггер языка Transact-SQL можно сконструировать для выполнения конкретных действий, основанных на изменении определенных столбцов с помощью инструкций UPDATE или INSERT. Используйте для этих целей в теле триггера конструкции HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms187326%28SQL.90%29.aspx" UPDATE() или HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms186329%28SQL.90%29.aspx" COLUMNS_UPDATED. Конструкция UPDATE() проверяет действие инструкций UPDATE или INSERT на одном столбце. С помощью конструкции COLUMNS_UPDATED проверяются действия инструкций UPDATE или INSERT, проводимых на нескольких столбцах, и возвращается битовый шаблон, показывающий, какие столбцы были вставлены или обновлены.
Ограничения триггеров
Инструкция CREATE TRIGGER должна быть первой инструкцией в пакете и может применяться только к одной таблице.
Триггер создается только в текущей базе данных, но может, тем не менее, содержать ссылки на объекты за пределами текущей базы данных.
Если для уточнения триггера указано имя схемы, имя таблицы необходимо уточнить таким же образом.
Одно и то же действие триггера может быть определено более чем для одного действия пользователя (например, INSERT и UPDATE) в одной и той же инструкции CREATE TRIGGER.
Триггеры INSTEAD OF DELETE/UPDATE нельзя определить для таблицы, у которой есть внешний ключ, определенный для каскадного выполнения операции DELETE/UPDATE.
Внутри триггера может быть использована любая инструкция SET. Выбранный параметр SET остается в силе во время выполнения триггера, после чего настройки возвращаются в предыдущее состояние.
Во время срабатывания триггера результаты возвращаются вызывающему приложению так же, как и в случае с хранимыми процедурами. Чтобы предотвратить вызванное срабатыванием триггера возвращение результатов приложению, не следует включать инструкции SELECT, возвращающие результат, или инструкции, которые выполняют в триггере присвоение переменных. Триггер, содержащий либо инструкции SELECT, которые возвращают результаты пользователю, либо инструкции, выполняющие присвоение переменных, требует особого обращения; эти возвращаемые результаты должны быть перезаписаны во все приложения, в которых разрешены изменения таблицы триггера. Если в триггере происходит присвоение переменной, следует использовать инструкцию SET NOCOUNT в начале триггера, чтобы предотвратить возвращение каких-либо результирующих наборов.
Хотя инструкция TRUNCATE TABLE фактически является инструкцией DELETE, она не может запустить триггер, потому что операция не регистрирует в журнале удаления отдельных строк. Однако только пользователям с разрешением на выполнение в таблице инструкции TRUNCATE TABLE следует обращать внимание на непреднамеренный обход триггера DELETE с помощью инструкции TRUNCATE TABLE.
Инструкция WRITETEXT (с ведением журнала и без него) не запускает триггеры.
Следующие инструкции языка Transact-SQL не разрешены в триггерах DML:
Кроме того, следующие инструкции языка Transact-SQL не разрешены в теле триггера DML, если он используется по отношению к таблице или представлению, которые являются целью действий триггера.
Триггеры DDL
Триггеры DDL, как и стандартные триггеры, выполняют хранимые процедуры в ответ на какое-либо событие. В отличие от стандартных триггеров, они не срабатывают в ответ на выполнение инструкций UPDATE, INSERT или DELETE по отношению к таблице или представлению. Вместо этого триггеры срабатывают в первую очередь в ответ на инструкции языка определения данных (DDL). Это инструкции CREATE, ALTER, DROP, GRANT, DENY, REVOKE и UPDATE STATISTICS. Системные хранимые процедуры, выполняющие операции, подобные операциям DDL, также могут запускать триггеры DDL.
Дополнительные сведения о триггерах DDL см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms190989%28SQL.90%29.aspx" Триггеры DDL.
Триггеры DDL не срабатывают в ответ на события, влияющие на локальные или глобальные временные таблицы и хранимые процедуры.
В отличие от триггеров DML, триггеры DDL не ограничены областью схемы. Поэтому для запроса метаданных о триггерах DDL нельзя воспользоваться функциями OBJECT_ID, OBJECT_NAME, OBJECTPROPERTY и OBJECTPROPERTYEX. Используйте вместо них представления каталога. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms184304%28SQL.90%29.aspx" Получение сведений о триггерах DDL.
Триггеры входа
Триггеры входа срабатывают в ответ на событие LOGON. Событие вызывается при установке пользовательских сеансов. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/bb326598%28SQL.90%29.aspx" Триггеры входа.
Общие соглашения о триггерах
Возвращаемые результаты
Возможность возвращать результаты из триггеров будет исключена из следующей версии SQL Server. Триггеры, возвращающие результирующие наборы, могут вызвать непредвиденное поведение тех приложений, которые не предназначены для работы с ними. Не используйте в разрабатываемых приложениях триггеры, возвращающие результирующие наборы, и запланируйте изменение приложений, которые используют их в настоящее время. Чтобы триггеры не возвращали результирующие наборы в SQL Server 2005, в HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms186337%28SQL.90%29.aspx" параметре disallow results from triggers установите значение 1.
Триггеры LOGON всегда запрещают возврат результирующих наборов, и данное поведение нельзя настроить. Если триггер LOGON создает результирующий набор, то триггер не выполняется и попытка входа, вызванная триггером, запрещается.
Несколько триггеров
SQL Server позволяет создавать несколько триггеров для каждого события DML, DDL и LOGON. Например, если инструкция CREATE TRIGGER FOR UPDATE выполняется в таблице, уже имеющей триггер UPDATE, дополнительно создается триггер обновления. В более ранних версиях SQL Server был разрешен только один триггер в каждой таблице для каждого события изменения данных INSERT, UPDATE или DELETE.
Рекурсивные триггеры
SQL Server разрешает рекурсивный вызов триггеров, если с помощью инструкции ALTER DATABASE включена настройка RECURSIVE_TRIGGERS.
В рекурсивных триггерах могут возникать следующие типы рекурсии:
Косвенная рекурсияПри косвенной рекурсии приложение обновляет таблицу T1. Это событие вызывает срабатывание триггера TR1, обновляющего таблицу T2. Это вызывает срабатывание триггера T2 и обновление таблицы T1.
Прямая рекурсияПри прямой рекурсии приложение обновляет таблицу T1. Это событие вызывает срабатывание триггера TR1, обновляющего таблицу T1. Поскольку таблица T1 уже была обновлена, триггер TR1 срабатывает снова и т.д.
В следующем примере используются оба типа рекурсий: прямая и косвенная. Допустим, для таблицы T1 определены два триггера: TR1 и TR2. Триггер TR1 рекурсивно обновляет таблицу T1. Инструкция UPDATE выполняет каждый из триггеров TR1 и TR2 один раз. В дополнение к этому срабатывание триггера TR1 вызывает выполнение триггеров TR1 (рекурсивно) и TR2. В таблицах inserted и deleted триггера содержатся строки, которые относятся только к инструкции UPDATE, вызвавшей срабатывание триггера.
Отключение настройки RECURSIVE_TRIGGERS предотвращает выполнение только прямых рекурсий. Чтобы отключить косвенную рекурсию, с помощью хранимой процедуры sp_configure присвойте параметру сервера nested triggers значение 0.
Если один из триггеров выполняет инструкцию ROLLBACK TRANSACTION, никакие другие триггеры, вне зависимости от уровня вложенности, не срабатывают.
Вложенные триггеры
Вложенность триггеров может достигать максимум 32 уровня. Если триггер изменяет таблицу, для которой определен другой триггер, то запускается второй триггер, вызывающий срабатывание третьего и т.д. Если любой из триггеров в цепочке отключает бесконечный цикл, то уровень вложенности превышает допустимый предел, и срабатывание триггера отменяется. Чтобы отменить вложенные триггеры, присвойте значение 0 параметру nested triggers хранимой процедуры sp_configure. В конфигурации по умолчанию вложенные триггеры разрешены. Если вложенные триггеры отключены, рекурсивные триггеры тоже будут отключены, вне зависимости от настройки RECURSIVE_TRIGGERS, установленной с помощью инструкции ALTER DATABASE.
Отложенная интерпретация имен
В SQL Server разрешены хранимые процедуры, триггеры и пакеты на языке Transact-SQL, которые содержат ссылки на таблицы, не существующие в момент компиляции. Такая возможность называется отложенной интерпретацией имен. Однако если хранимая процедура, триггер или пакет на языке Transact-SQL содержат ссылку на таблицу, определенную в хранимой процедуре или триггере, то во время создания выдается предупреждение, только если установлен уровень совместимости 65. Предупреждение во время компиляции возникает, если используется пакет. Если таблица, на которую имеется ссылка, не существует во время выполнения, возникает сообщение об ошибке. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/ms190686%28SQL.90%29.aspx" Отсроченное разрешение и компиляция имен.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Разрешения
Для создания триггера DML требуется разрешение ALTER на таблицу или представление, в которых создается триггер.
Для создания триггера DDL с областью действия в пределах сервера (ON ALL SERVER) или триггера входа требуется разрешение CONTROL SERVER на сервер. Для создания триггера DDL с областью видимости в пределах базы данных (ON DATABASE) требуется разрешение ALTER ANY DATABASE DDL TRIGGER на текущую базу данных.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Примеры
А. Использование триггера DML с предупреждающим сообщением
Следующий триггер DML отправляет клиенту сообщение, когда кто-то пытается добавить или изменить данные в таблице Customer.
USE AdventureWorks;
GO
IF OBJECT_ID ('Sales.reminder1', 'TR') IS NOT NULL
DROP TRIGGER Sales.reminder1;
GO
CREATE TRIGGER reminder1
ON Sales.Customer
AFTER INSERT, UPDATE
AS RAISERROR ('Notify Customer Relations', 16, 10);
GO
Б. Использование триггера DML с предупреждающим сообщением, отправляемым по электронной почте
В следующем примере указанному пользователю (MaryM) по электронной почте отправляется сообщение при изменении таблицы Customer.
USE AdventureWorks;
GO
IF OBJECT_ID ('Sales.reminder2','TR') IS NOT NULL
DROP TRIGGER Sales.reminder2;
GO
CREATE TRIGGER reminder2
ON Sales.Customer
AFTER INSERT, UPDATE, DELETE
AS
EXEC msdb.dbo.sp_send_dbmail
@profile_name = 'AdventureWorks Administrator',
@recipients = 'danw@Adventure-Works.com',
@body = 'Don''t forget to print a report for the sales force.',
@subject = 'Reminder';
GO
В. Использование триггера DML AFTER для применения бизнес-правил между таблицами PurchaseOrderHeader и Vendor
Поскольку ограничение CHECK может содержать ссылки только на столбцы, для которых определены ограничения на уровне столбцов или таблицы, любые межтабличные ограничения (в данном случае бизнес-правила) должны быть заданы в виде триггеров.
В следующем примере создается триггер DML. Этот триггер проверяет уровень кредитоспособности поставщика при попытке добавить новый заказ на покупку в таблицу PurchaseOrderHeader. Для получения сведений о кредитоспособности поставщика требуется ссылка на таблицу Vendor. В случае слишком низкой кредитоспособности выводится соответствующее сообщение, и вставка не производится.
IF OBJECT_ID ('Purchasing.LowCredit','TR') IS NOT NULL
DROP TRIGGER Purchasing.LowCredit;
GO
CREATE TRIGGER LowCredit ON Purchasing.PurchaseOrderHeader
AFTER INSERT
AS
DECLARE @creditrating tinyint,
@vendorid int
SELECT @creditrating = v.CreditRating, @vendorid = p.VendorID
FROM Purchasing.PurchaseOrderHeader AS p
INNER JOIN inserted AS i ON p.PurchaseOrderID =
i.PurchaseOrderID
JOIN Purchasing.Vendor AS v on v.VendorID = i.VendorID
IF @creditrating = 5
BEGIN
RAISERROR ('This vendor''s credit rating is too low to accept new
purchase orders.', 16, 1)
ROLLBACK TRANSACTION
END
GO
Г. Использование отложенной интерпретации имен
В следующем примере для демонстрации отложенной интерпретации имен создаются два триггера DML.
USE AdventureWorks;
GO
IF OBJECT_ID ('HumanResources.trig1','TR') IS NOT NULL
DROP TRIGGER HumanResources.trig1;
GO
-- Creating a trigger on a nonexistent table.
CREATE TRIGGER HumanResources.trig1
on HumanResources.Employee
AFTER INSERT, UPDATE, DELETE
AS
SELECT e.EmployeeID, e.BirthDate, x.info
FROM HumanResources.Employee AS e INNER JOIN does_not_exist AS x
ON e.EmployeeID = x.xID
GO
-- Here is the statement to actually see the text of the trigger.
SELECT t.object_id, m.definition
FROM sys.triggers AS t INNER JOIN sys.sql_modules AS m
ON t.object_id = m.object_id
WHERE t.type = 'TR' and t.name = 'trig1'
AND t.parent_class = 1
GO
-- Creating a trigger on an existing table, but with a nonexistent
-- column.
USE AdventureWorks;
GO
IF OBJECT_ID ('HumanResources.trig2','TR') IS NOT NULL
DROP TRIGGER HumanResources.trig2
GO
CREATE TRIGGER HumanResources.trig2
ON HumanResources.Employee
AFTER INSERT, UPDATE
AS
DECLARE @fax varchar(12)
SELECT @fax = 'AltPhone'
FROM HumanResources.Employee
GO
-- Here is the statement to actually see the text of the trigger.
SELECT t.object_id, m.definition
FROM sys.triggers AS t INNER JOIN sys.sql_modules AS m
ON t.object_id = m.object_id
WHERE t.type = 'TR' and t.name = 'trig2'
AND t.parent_class = 1
GO
Д. Использование триггера DDL с областью видимости в пределах базы данных
В следующем примере триггер DDL используется для предотвращения удаления синонимов в базе данных.
USE AdventureWorks;
GO
IF EXISTS (SELECT * FROM sys.triggers
WHERE parent_class = 0 AND name = 'safety')
DROP TRIGGER safety
ON DATABASE;
GO
CREATE TRIGGER safety
ON DATABASE
FOR DROP_SYNONYM
AS
RAISERROR ('You must disable Trigger "safety" to drop synonyms!',10, 1)
ROLLBACK
GO
DROP TRIGGER safety
ON DATABASE;
GO
Е. Использование триггера DDL с областью видимости в пределах сервера
В следующем примере триггер DDL используется для вывода сообщения при возникновении на данном экземпляре сервера любого из событий CREATE DATABASE, а функция EVENTDATA используется для получения текста соответствующей инструкции на языке Transact-SQL.
IF EXISTS (SELECT * FROM sys.server_triggers
WHERE name = 'ddl_trig_database')
DROP TRIGGER ddl_trig_database
ON ALL SERVER;
GO
CREATE TRIGGER ddl_trig_database
ON ALL SERVER
FOR CREATE_DATABASE
AS
PRINT 'Database Created.'
SELECT EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)')
GO
DROP TRIGGER ddl_trig_database
ON ALL SERVER;
GO
Ж. Использование триггера входа
В следующем примере триггера входа выполняется запрет попытки подключения к SQL Server в качестве члена login_test учетной записи, если под этой учетной записью уже запущено три сеанса.
USE master;
GO
CREATE LOGIN login_test WITH PASSWORD = '3KHJ6dhx(0xVYsdf' MUST_CHANGE,
CHECK_EXPIRATION = ON;
GO
GRANT VIEW SERVER STATE TO login_test;
GO
CREATE TRIGGER connection_limit_trigger
ON ALL SERVER WITH EXECUTE AS 'login_test'
FOR LOGON
AS
BEGIN
IF ORIGINAL_LOGIN()= 'login_test' AND
(SELECT COUNT(*) FROM sys.dm_exec_sessions
WHERE is_user_process = 1 AND
original_login_name = 'login_test') > 3
ROLLBACK;
END;
З. Просмотр событий, вызвавших срабатывание триггера
В следующем примере выполняются запросы к представлениям каталога sys.triggers и sys.trigger_events с целью определения, какие события языка Transact-SQL вызывали срабатывание триггера safety. Создание триггера safety показано в предыдущем примере.
SELECT TE.*
FROM sys.trigger_events AS TE
JOIN sys.triggers AS T
ON T.object_id = TE.object_id
WHERE T.parent_class = 0
AND T.name = 'safety'
GO

Завдання до лабораторної роботи
Виконайте всі пункти і запишіть відповіді на поставлені в них питання.
Створіть проект на .NET, опишіть функцію (UDF) згідно варіанту, каталогізуйте збірку та функцію у SQL Server, напишіть демонстраційний код T-SQL для використання цієї функції.
Створіть користувацький тип даних (UDT) згідно варіанту, каталогізуйте збірку, і приведіть демонстраційний T-SQL код. UDT має містити щонайменше 2 поля різного типу даних (їх можна розмежувати, наприклад знаком #, $ тощо).
Створіть тригер та продемонструйте його використання.
Варіанти
Користувацькі функції (UDF)
1. Замінити всі великі літери на малі.
2. Видалити всі повторні входження літер.
3. Додати до стрічки число, яке вказує її довжину.
4. Додати до дати поточну дату.
5. Додати до числа значення його квадрату.
6. Додати до числа суму його цифр.
7. Інвертувати стрічку.
8. Додати до дати 2 дні та 1 місяць.
9. Видалити в стрічці найбільше слово.
10. Продублювати всі літери в стрічці.
Користувацькі типи даних (UDT)
Адреса
Студент
Викладач
Студентська група
Автомобіль
Маршрут
Товар
Фігура
Інтернет сайт
Книга

ЛІТЕРАТУРА
1. Создание и запуск триггера CLR SQL Server
Создайте триггер SQL путем добавления элемента Триггер в проект SQL сервера. После успешного развертывания триггеры, созданные в управляемом коде, вызываются и выполняются как любой другой триггер T-SQL. Триггеры, написанные на управляемом языке, могут использовать класс SqlTriggerContext, чтобы получить доступ к тем же сведениям, что доступны для триггеров T-SQL.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Создание триггеров SQL Server
Создание триггера SQL Server
Откройте существующий Проект SQL Server или создайте новый. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/84b1se47.aspx" Практическое руководство. Создание проекта SQL Server.
В меню Проект выберите команду Добавить новый элемент.
Выберите Триггер в HYPERLINK "http://msdn.microsoft.com/ru-ru/library/74d99cwb.aspx" Диалоговое окно "Добавление нового элемента".
Введите Имя для нового триггера.
Добавьте код для выполнения при срабатывании триггера. Ознакомьтесь с первым примером, следующим за этой процедурой.
Для Visual Basic и Visual C# в окне Обозреватель решений откройте папку TestScripts и дважды щелкните файл Test.sql.
Для Visual C++ в окне Обозреватель решений дважды щелкните файл Debug.sql.
Добавьте код в файл Test.sql (debug.sql в Visual C++) для выполнение триггера. Ознакомьтесь со вторым примером, следующим за этой процедурой.
Нажмите кнопку F5 для сборки, развертывания и отладки триггера. Сведения о развертывании без отладки содержатся в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/dahcx0ww.aspx" Практическое руководство. Развертывание элементов проекта SQL Server на сервере SQL Server.
Просмотрите результаты, которые отображаются в окне HYPERLINK "http://msdn.microsoft.com/ru-ru/library/3hk6fby3.aspx" Окно выходных данных, и выберите Показать выходные данные от: Выходные данные БД.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Пример
В этом примере показан сценарии, в котором пользователи могут выбирать любое имя, но вы хотите знать, кто из них вводил адрес электронной почты в качестве имени. Триггер обнаруживает эту информацию и записывает ее в таблицу аудита.
Язык Visual Basic
Imports System.Data.SqlClient
Imports System.Text.RegularExpressions
Imports Microsoft.SqlServer.Server
Partial Public Class Triggers
<SqlTrigger(Name:="UserNameAudit", Target:="Users", Event:="FOR INSERT")> _
Public Shared Sub UserNameAudit()
Dim triggContext As SqlTriggerContext = SqlContext.TriggerContext()
Dim userName As New SqlParameter("@username", SqlDbType.NVarChar)
If triggContext.TriggerAction = TriggerAction.Insert Then
Using conn As New SqlConnection("context connection=true")
conn.Open()
Dim sqlComm As New SqlCommand
Dim sqlP As SqlPipe = SqlContext.Pipe()
sqlComm.Connection = conn
sqlComm.CommandText = "SELECT UserName from INSERTED"
userName.Value = sqlComm.ExecuteScalar.ToString()
If IsEMailAddress(userName.ToString) Then
sqlComm.CommandText = "INSERT UsersAudit(UserName) VALUES(username)"
sqlP.Send(sqlComm.CommandText)
sqlP.ExecuteAndSend(sqlComm)
End If
End Using
End If
End Sub
Public Shared Function IsEMailAddress(ByVal s As String) As Boolean
Return Regex.IsMatch(s, "^([\w-]+\.)*?[\w-]+@[\w-]+\.([\w-]+\.)*?[\w]+$")
End Function
End Class
Язык C#
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;
public partial class Triggers
{
[SqlTrigger(Name="UserNameAudit", Target="Users", Event="FOR INSERT")]
public static void UserNameAudit()
{
SqlTriggerContext triggContext = SqlContext.TriggerContext;
SqlParameter userName = new SqlParameter("@username", System.Data.SqlDbType.NVarChar);
if (triggContext.TriggerAction == TriggerAction.Insert)
{
using (SqlConnection conn = new SqlConnection("context connection=true"))
{
conn.Open();
SqlCommand sqlComm = new SqlCommand();
SqlPipe sqlP = SqlContext.Pipe;
sqlComm.Connection = conn;
sqlComm.CommandText = "SELECT UserName from INSERTED";
userName.Value = sqlComm.ExecuteScalar().ToString();
if (IsEMailAddress(userName.ToString()))
{
sqlComm.CommandText = "INSERT UsersAudit(UserName) VALUES(userName)";
sqlP.Send(sqlComm.CommandText);
sqlP.ExecuteAndSend(sqlComm);
}
}
}
}
public static bool IsEMailAddress(string s)
{
return Regex.IsMatch(s, "^([\\w-]+\\.)*?[\\w-]+@[\\w-]+\\.([\\w-]+\\.)*?[\\w]+$");
}
}
Язык Visual C++
#include "stdafx.h"
#using <System.dll>
#using <System.Data.dll>
#using <System.Xml.dll>
using namespace System;
using namespace System::Data;
using namespace System::Data::Sql;
using namespace System::Data::SqlClient;
using namespace System::Data::SqlTypes;
using namespace System::Text::RegularExpressions;
using namespace Microsoft::SqlServer::Server;
// In order to debug your Trigger, add the following to your debug.sql file:
//
// -- Insert one user name that is not an e-mail address and one that is
// INSERT INTO Users(UserName, Pass) VALUES(N'someone', N'cnffjbeq')
// INSERT INTO Users(UserName, Pass) VALUES(N'someone@example.com', N'cnffjbeq')
//
// -- check the Users and UsersAudit tables to see the results of the trigger
// SELECT * FROM Users
// SELECT * FROM UsersAudit
//
public ref class AddNewTrigger
{
public:
[SqlTrigger(Name="UserNameAudit", Target="Users", Event="FOR INSERT")]
static void UserNameAudit()
{
SqlTriggerContext ^triggContext = SqlContext::TriggerContext;
SqlParameter ^userName = gcnew SqlParameter("@username", System::Data::SqlDbType::NVarChar);
if (triggContext->TriggerAction == TriggerAction::Insert)
{
SqlConnection ^conn = gcnew SqlConnection("context connection=true");
conn->Open();
SqlCommand ^sqlComm = gcnew SqlCommand();
SqlPipe ^sqlP = SqlContext::Pipe;
sqlComm->Connection = conn;
sqlComm->CommandText = "SELECT UserName from INSERTED";
userName->Value = sqlComm->ExecuteScalar()->ToString();
if (IsEMailAddress(userName->ToString()))
{
sqlComm->CommandText = "INSERT UsersAudit(UserName) VALUES(userName)";
sqlP->Send(sqlComm->CommandText);
sqlP->ExecuteAndSend(sqlComm);
}
conn->Close();
}
}
static bool IsEMailAddress(String ^s)
{
return Regex::IsMatch(s, "^([\\w-]+\\.)*?[\\w-]+@[\\w-]+\\.([\\w-]+\\.)*?[\\w]+$");
}
};
Добавьте код для выполнения и проверьте триггер с помощью файла Test.sql (debug.sql в Visual C++) в папке TestScripts в проекте. Например, если вы развернули триггер, можно проверить его, выполнив сценарий, вставляющий новую строку в таблицу, на которой установлен этот триггер. Следующий код отладки предполагает, что существуют две таблицы со следующими определениями:
CREATE TABLE Users
(
UserName NVARCHAR(200) NOT NULL,
Pass NVARCHAR(200) NOT NULL
)
CREATE TABLE UsersAudit
(
UserName NVARCHAR(200) NOT NULL
)
-- Insert one user name that is not an e-mail address and one that is
INSERT INTO Users(UserName, Pass) VALUES(N'someone', N'cnffjbeq')
INSERT INTO Users(UserName, Pass) VALUES(N'someone@example.com', N'cnffjbeq')
-- check the Users and UsersAudit tables to see the results of the trigger
select * from Users
select * from UsersAudit
2.Создание и запуск пользовательской функции SQL Server в среде CLR
Создайте пользовательскую функцию SQL путем добавления Пользовательской функции в проект SQL Server. После успешного развертывания пользовательские функции можно вызывать и выполнять.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Создание пользовательских функций в СУБД SQL Server
Чтобы создать и развернуть эту функцию, используя Visual Studio:
Откройте существующий Проект SQL Server или создайте новый. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/84b1se47.aspx" Практическое руководство. Создание проекта SQL Server.
В меню Проект выберите команду Добавить новый элемент.
Выберите Пользовательская функция в HYPERLINK "http://msdn.microsoft.com/ru-ru/library/74d99cwb.aspx" Диалоговое окно "Добавление нового элемента".
Введите Имя для новой пользовательской функции.
Добавьте код для выполнения при запуске пользовательской функции. Ознакомьтесь с первым примером, следующим за этой процедурой.
Для Visual Basic и Visual C# в Обозревателе решений откройте папку TestScripts и дважды щелкните файл Test.sql, чтобы открыть его для редактирования. Добавьте код для выполнения пользовательской функции. Ознакомьтесь со вторым примером, следующим за этой процедурой.
Для Visual C++ в Обозревателе решений дважды щелкните файл debug.sql, чтобы открыть его для редактирования. Добавьте код для выполнения пользовательской функции. Ознакомьтесь со вторым примером, следующим за этой процедурой.
Развертывание пользовательской функции на SQL Server. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/dahcx0ww.aspx" Практическое руководство. Развертывание элементов проекта SQL Server на сервере SQL Server.
Нажмите клавишу F5 для отладки определяемой пользовательской функции, путем выполнения ее на SQL Server.
Описание
В следующем коде создается пользовательская скалярная функция с именем addTax, которая принимает цену в качестве параметра, добавляет налог с продаж и возвращает сумму цены и налога.
После создания функции разверните ее на SQL Server. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/dahcx0ww.aspx" Практическое руководство. Развертывание элементов проекта SQL Server на сервере SQL Server.
Код
Язык Visual Basic
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Partial Public Class UserDefinedFunctions
Public Const SALES_TAX As Double = 0.086
<SqlFunction()> _
Public Shared Function addTax(ByVal originalAmount As SqlDouble) As SqlDouble
Dim taxAmount As SqlDouble = originalAmount * SALES_TAX
Return originalAmount + taxAmount
End Function
End Class
Язык C#
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public partial class UserDefinedFunctions
{
public const double SALES_TAX = .086;
[SqlFunction()]
public static SqlDouble addTax(SqlDouble originalAmount)
{
SqlDouble taxAmount = originalAmount * SALES_TAX;
return originalAmount + taxAmount;
}
}
Язык Visual C++
#include "stdafx.h"
#using <System.dll>
#using <System.Data.dll>
#using <System.Xml.dll>
using namespace System;
using namespace System::Data;
using namespace System::Data::Sql;
using namespace System::Data::SqlTypes;
using namespace Microsoft::SqlServer::Server;
// In order to debug your Aggregate, add the following to your debug.sql file:
//
// SELECT dbo.addTax(10)
//
public ref class UserDefinedFunctions
{
public:
static initonly double SALES_TAX = 0.086;
[SqlFunction()]
static SqlDouble AddTax(SqlDouble originalAmount)
{
SqlDouble taxAmount = originalAmount * SALES_TAX;
return originalAmount + taxAmount;
}
};
Описание
Добавьте в файл Test.sql (debug.sql в Visual C++), находящийся в папке TestScripts проекта, код для тестирования пользовательской функции. Например, чтобы проверить эту функцию, используйте запрос "SELECT dbo.addTax(10)". Вы увидите возвращаемое значение "10,86".
Код
SELECT dbo.addTax(10)
3.Создание и запуск пользовательского типа SQL Server в среде CLR
Чтобы создать определяемый пользователем тип SQL, добавьте Пользовательский тип к проекту SQL Server. После успешного развертывания его можно использовать во всех контекстах, которые можно применять как системный тип. В это число входят определения столбцов, переменные, параметры, результаты функций, курсоры, триггеры и репликации. Пользовательский тип предоставляет пользователю расширяемость системы типов данных SQL Server, а также возможность определения комплексных структурированных типов.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Создание пользовательского типа
Для создания пользовательского типа SQL
Откройте существующий Проект SQL Server или создайте новый. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/84b1se47.aspx" Практическое руководство. Создание проекта SQL Server.
В меню Проект выберите команду Добавить новый элемент.
Выберите Пользовательский тип в HYPERLINK "http://msdn.microsoft.com/ru-ru/library/74d99cwb.aspx" Диалоговое окно "Добавление нового элемента".
Введите Имя для нового пользовательского типа.
Добавьте код для определения и создания пользовательского типа. Ознакомьтесь с первым примером, следующим за этой процедурой.
Для Visual Basic и Visual C# в окне Обозреватель решений откройте папку TestScripts и дважды щелкните файл Test.sql.
Для Visual C++ в окне Обозреватель решений дважды щелкните файл Debug.sql.
Добавьте код в файл test.sql (debug.sql в Visual C++) чтобы выполнить пользовательский тип. Ознакомьтесь со вторым примером, следующим за этой процедурой.
Нажмите клавишу F5, чтобы построить, развернуть и отладить пользовательский тип. Сведения о развертывании без отладки содержатся в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/dahcx0ww.aspx" Практическое руководство. Развертывание элементов проекта SQL Server на сервере SQL Server.
Просмотрите результаты, которые отображаются в окне HYPERLINK "http://msdn.microsoft.com/ru-ru/library/3hk6fby3.aspx" Окно выходных данных, и выберите Показать выходные данные от: Выходные данные БД.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Пример
В этом примере создается тип Point, который можно использовать как любой другой простой тип. Объявление класса параметризуется атрибутами Serializable и SqlUserDefinedTypeAttribute. Свойство Format, относящееся к SqlUserDefinedTypeAttribute, определяет формат хранения пользовательского типа. Тип реализует преобразование строк путем реализации методов Parse и ToString. Тип также реализует две процедуры свойств для получения и установки значений X и Y для точки, представленной этим классом.
Язык Visual Basic
Imports System
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
<Serializable()> _
<SqlUserDefinedType(Format.Native)> _
Public Structure Point
Implements INullable
Private m_x As Int32
Private m_y As Int32
Private is_Null As Boolean
Public Property X() As Int32
Get
Return (Me.m_x)
End Get
Set(ByVal Value As Int32)
m_x = Value
End Set
End Property
Public Property Y() As Int32
Get
Return (Me.m_y)
End Get
Set(ByVal Value As Int32)
m_y = Value
End Set
End Property
Public ReadOnly Property IsNull() As Boolean Implements INullable.IsNull
Get
Return is_Null
End Get
End Property
Public Shared ReadOnly Property Null() As Point
Get
Dim pt As Point = New Point
pt.is_Null = True
Return pt
End Get
End Property
Public Overrides Function ToString() As String
If Me.IsNull() Then
Return Nothing
Else
Return Me.m_x & ":" & Me.m_y
End If
End Function
Public Shared Function Parse(ByVal s As SqlString) As Point
If s = SqlString.Null Then
Return Null
End If
If s.ToString() = SqlString.Null.ToString() Then
Return Null
End If
If s.IsNull Then
Return Null
End If
'Parse input string here to separate out coordinates
Dim str As String = Convert.ToString(s)
Dim xy() As String = str.Split(":"c)
Dim pt As New Point()
pt.X = CType(xy(0), Int32)
pt.Y = CType(xy(1), Int32)
Return (pt)
End Function
Public Function Quadrant() As SqlString
If m_x = 0 And m_y = 0 Then
Return "centered"
End If
Dim stringResult As String = ""
Select Case m_x
Case 0
stringResult = "center"
Case Is > 0
stringResult = "right"
Case Is < 0
stringResult = "left"
End Select
Select Case m_y
Case 0
stringResult = stringResult & " center"
Case Is > 0
stringResult = stringResult & " top"
Case Is < 0
stringResult = stringResult & " bottom"
End Select
Return stringResult
End Function
End Structure
Язык C#
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable()]
[SqlUserDefinedType(Format.Native)]
public struct Point : INullable
{
private Int32 m_x;
private Int32 m_y;
private bool is_Null;
public Int32 X
{
get
{
return (this.m_x);
}
set
{
m_x = value;
}
}
public Int32 Y
{
get
{
return (this.m_y);
}
set
{
m_y = value;
}
}
public bool IsNull
{
get
{
return is_Null;
}
}
public static Point Null
{
get
{
Point pt = new Point();
pt.is_Null = true;
return (pt);
}
}
public override string ToString()
{
if (this.IsNull)
{
return "NULL";
}
else
{
return this.m_x + ":" + this.m_y;
}
}
public static Point Parse(SqlString s)
{
if (s.IsNull)
{
return Null;
}
// Parse input string here to separate out coordinates
string str = Convert.ToString(s);
string[] xy = str.Split(':');
Point pt = new Point();
pt.X = Convert.ToInt32(xy[0]);
pt.Y = Convert.ToInt32(xy[1]);
return (pt);
}
public SqlString Quadrant()
{
if (m_x == 0 && m_y == 0)
{
return "centered";
}
SqlString stringReturn = "";
if (m_x == 0)
{
stringReturn = "center";
}
else if (m_x > 0)
{
stringReturn = "right";
}
else if (m_x < 0)
{
stringReturn = "left";
}
if (m_y == 0)
{
stringReturn = stringReturn + " center";
}
else if (m_y > 0)
{
stringReturn = stringReturn + " top";
}
else if (m_y < 0)
{
stringReturn = stringReturn + " bottom";
}
return stringReturn;
}
}
Язык Visual C++
#include "stdafx.h"
#using <System.dll>
#using <System.Data.dll>
#using <System.Xml.dll>
using namespace System;
using namespace System::Data;
using namespace System::Data::Sql;
using namespace System::Data::SqlTypes;
using namespace Microsoft::SqlServer::Server;
// In order to debug your User-Defined Types, add the following to your debug.sql file:
//
// CREATE TABLE test_table (column1 Point)
//
// INSERT INTO test_table (column1) VALUES ('1:2')
// INSERT INTO test_table (column1) VALUES ('-2:3')
// INSERT INTO test_table (column1) VALUES ('-3:-4')
//
// SELECT column1.Quadrant() FROM test_table
//
// DROP TABLE test_table
//
[Serializable]
[SqlUserDefinedType(Format::Native)]
public value struct Point : public INullable
{
private:
Int32 m_x;
Int32 m_y;
bool is_Null;
public:
property Int32 X
{
Int32 get() { return (this->m_x); }
void set(Int32 value) { m_x = value; }
}
property Int32 Y
{
Int32 get() { return (this->m_y); }
void set(Int32 value) { m_y = value; }
}
virtual property bool IsNull
{
bool get() { return is_Null; }
}
static property Point Null
{
Point get()
{
Point pt;
pt.is_Null = true;
return (pt);
}
}
virtual String ^ToString() override
{
if (this->IsNull)
{
return "NULL";
}
else
{
return this->m_x + ":" + this->m_y;
}
}
static Point Parse(SqlString s)
{
if (s.IsNull)
{
return Null;
}
// Parse input string here to separate out coordinates
String ^str = Convert::ToString(s);
array<String ^> ^xy = str->Split(':');
Point pt;
pt.X = Convert::ToInt32(xy[0]);
pt.Y = Convert::ToInt32(xy[1]);
return (pt);
}
SqlString Quadrant()
{
if (m_x == 0 && m_y == 0)
{
return "centered";
}
SqlString stringReturn = "";
if (m_x == 0)
{
stringReturn = "center";
}
else if (m_x > 0)
{
stringReturn = "right";
}
else if (m_x < 0)
{
stringReturn = "left";
}
if (m_y == 0)
{
stringReturn = stringReturn + SqlString(" center");
}
else if (m_y > 0)
{
stringReturn = stringReturn + SqlString(" top");
}
else if (m_y < 0)
{
stringReturn = stringReturn + SqlString(" bottom");
}
return stringReturn;
}
};
Добавьте код для выполнения и проверки пользовательского типа (Point) в файл Test.sql (debug.sql в Visual C++) в папке проекта TestScripts. Например, для проверки нового типа создайте таблицу, которая использует этот тип. В следующем примере демонстрируется использование типа Point в создании таблицы.
CREATE TABLE test_table (column1 Point)
go
INSERT INTO test_table (column1) VALUES ('1:2')
INSERT INTO test_table (column1) VALUES ('-2:3')
INSERT INTO test_table (column1) VALUES ('-3:-4')
select column1.Quadrant() from test_table
4. Создание и выполнение статистических функций SQL Server в среде CLR
Создание статистических функций SQL с помощью добавления элементов Статистическая функция в проекты SQL Server. После успешного развертывания статистические функции, созданные в управляемом коде, вызываются и выполняются как любые другие статистические функции SQL Server.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Создание статистических функций SQL Server
Чтобы создать статистическую функцию SQL Server:
Откройте существующий Проект SQL Server или создайте новый. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/84b1se47.aspx" Практическое руководство. Создание проекта SQL Server.
В меню Проект выберите команду Добавить новый элемент.
Выберите Статистическая функция в HYPERLINK "http://msdn.microsoft.com/ru-ru/library/74d99cwb.aspx" Диалоговое окно "Добавление нового элемента".
Введите Имя для новой статистической функции.
Добавьте код для выполнения при запуске статистической функции. Код представлен в первом примере ниже.
Развертывание статистического выражения на сервере SQL Server. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/dahcx0ww.aspx" Практическое руководство. Развертывание элементов проекта SQL Server на сервере SQL Server.
Проведите отладку статистической функции, выполнив ее на SQL Server. Обратитесь ко второму примеру, представленному ниже.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Пример
Этот пример создает статистическую функцию для подсчета гласных. Эта статистическая функция подсчитывает гласные в столбце строкового типа данных. Статистическая функция содержит следующие четыре необходимых метода, которые могут выполняться в многопоточном режиме, Init, Accumulate, Merge иTerminate:
Язык Visual Basic
Imports System
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
<Serializable()> _
<SqlUserDefinedAggregate(Format.Native)> _
Public Structure CountVowels
' count only the vowels in the passed-in strings
Private countOfVowels As SqlInt32
Public Sub Init()
countOfVowels = 0
End Sub
Public Sub Accumulate(ByVal value As SqlString)
Dim stringChar As String
Dim indexChar As Int32
' for each character in the given parameter
For indexChar = 0 To Len(value.ToString()) - 1
stringChar = value.ToString().Substring(indexChar, 1)
If stringChar.ToLower() Like "[aeiou]" Then
' it is a vowel, increment the count
countOfVowels = countOfVowels + 1
End If
Next
End Sub
Public Sub Merge(ByVal value As CountVowels)
Accumulate(value.Terminate())
End Sub
Public Function Terminate() As SqlString
Return countOfVowels.ToString()
End Function
End Structure
Язык C#
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
[Serializable]
[SqlUserDefinedAggregate(Format.Native)]
public struct CountVowels
{
// count only the vowels in the passed-in strings
private SqlInt32 countOfVowels;
public void Init()
{
countOfVowels = 0;
}
public void Accumulate(SqlString value)
{
// list of vowels to look for
string vowels = "aeiou";
// for each character in the given parameter
for (int i=0; i < value.ToString().Length; i++)
{
// for each character in the vowels string
for (int j=0; j < vowels.Length; j++)
{
// convert parameter character to lowercase and compare to vowel
if (value.Value.Substring(i,1).ToLower() == vowels.Substring(j,1))
{
// it is a vowel, increment the count
countOfVowels+=1;
}
}
}
}
public void Merge(CountVowels value)
{
Accumulate(value.Terminate());
}
public SqlString Terminate()
{
return countOfVowels.ToString();
}
}
Язык Visual C++
#include "stdafx.h"
#using <System.dll>
#using <System.Data.dll>
#using <System.Xml.dll>
using namespace System;
using namespace System::Data;
using namespace System::Data::Sql;
using namespace System::Data::SqlTypes;
using namespace Microsoft::SqlServer::Server;
// In order to debug your Aggregate, add the following to your debug.sql file:
//
// SELECT LastName, COUNT(LastName) AS CountOfLastName, dbo.CountVowels(LastName) AS CountOfVowels
// FROM Person.Contact
// GROUP BY LastName
// ORDER BY LastName
//
[Serializable]
[SqlUserDefinedAggregate(Format::Native)]
public value struct CountVowels
{
public:
void Init()
{
countOfVowels = 0;
}
void Accumulate(SqlString value)
{
// list of vowels to look for
String ^vowels = "aeiou";
// for each character in the given parameter
for (int i=0; i < value.ToString()->Length; i++)
{
// for each character in the vowels string
for (int j=0; j < vowels->Length; j++)
{
// convert parameter character to lowercase and compare to vowel
if (value.Value->Substring(i, 1)->ToLower() == vowels->Substring(j, 1))
{
// it is a vowel, increment the count
countOfVowels+=1;
break;
}
}
}
}
void Merge(CountVowels value)
{
Accumulate(value.Terminate());
}
SqlTypes::SqlString Terminate()
{
return countOfVowels.ToString();
}
private:
// count only the vowels in the passed-in strings
SqlInt32 countOfVowels;
};
После развертывания статистической функции проверьте ее выполнение на SQL Server и проверьте правильность возвращаемых данных. Этот запрос возвращает результирующий набор — число гласных для всех значений в столбце LastNames в таблице Contact.
SELECT LastName, COUNT(LastName) AS CountOfLastName, dbo.CountVowels(LastName) AS CountOfVowels
FROM Person.Contact
GROUP BY LastName
ORDER BY LastName
5. Создание и запуск хранимой процедуры SQL Server в среде CLR
Можно создать сохраненные процедуры SQL путем добавления элементов Сохраненная процедура в проекты SQL Server. После успешного развертывания на сервере SQL Server сохраненные процедуры, созданные в управляемом коде, вызываются и выполняются как любые другие сохраненные процедуры.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Создание сохраненной процедуры SQL Server
Для создания сохраненной процедуры SQL Server:
Откройте существующий Проект SQL Server или создайте новый. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/84b1se47.aspx" Практическое руководство. Создание проекта SQL Server.
В меню Проект выберите команду Добавить новый элемент.
Выберите Сохраненная процедура в HYPERLINK "http://msdn.microsoft.com/ru-ru/library/74d99cwb.aspx" Диалоговое окно "Добавление нового элемента".
Введите Имя для новой сохраненной процедуры.
Добавьте код для запуска при выполнении сохраненной процедуры. См. пример, приведенный ниже.
Для Visual Basic и Visual C# в Обозревателе решений откройте папку TestScripts и дважды щелкните файл Test.sql.
Для Visual C++ в Обозревателе решений откройте файл debug.sql.
Добавьте код в файл Test.sql (debug.sql для Visual C++) для запуска сохраненной процедуры. Обратитесь ко второму примеру, представленному ниже.
Нажмите клавишу F5, чтобы скомпилировать, развернуть и выполнить отладку сохраненной процедуры. Сведения о развертывании без отладки содержатся в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/dahcx0ww.aspx" Практическое руководство. Развертывание элементов проекта SQL Server на сервере SQL Server.
Просмотрите результаты, которые отображаются в окне HYPERLINK "http://msdn.microsoft.com/ru-ru/library/3hk6fby3.aspx" Окно выходных данных, и выберите Показать вывод из: вывода базы данных.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Пример
В следующем примере кода создается сохраненная процедура, которая вставляет запись в таблицу Currency базы данных Adventure Works. После создания сохраненной процедуры разверните ее на SQL Server. Дополнительные сведения см. в разделе HYPERLINK "http://msdn.microsoft.com/ru-ru/library/dahcx0ww.aspx" Практическое руководство. Развертывание элементов проекта SQL Server на сервере SQL Server.
Язык Visual Basic
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server
Partial Public Class StoredProcedures
<SqlProcedure()> _
Public Shared Sub InsertCurrency( _
ByVal currencyCode As SqlString, ByVal name As SqlString)
Using conn As New SqlConnection("context connection=true")
Dim InsertCurrencyCommand As New SqlCommand()
Dim currencyCodeParam As New SqlParameter("@CurrencyCode", SqlDbType.NVarChar)
Dim nameParam As New SqlParameter("@Name", SqlDbType.NVarChar)
currencyCodeParam.Value = currencyCode
nameParam.Value = name
InsertCurrencyCommand.Parameters.Add(currencyCodeParam)
InsertCurrencyCommand.Parameters.Add(nameParam)
InsertCurrencyCommand.CommandText = _
"INSERT Sales.Currency (CurrencyCode, Name, ModifiedDate)" & _
" VALUES(@CurrencyCode, @Name, GetDate())"
InsertCurrencyCommand.Connection = conn
conn.Open()
InsertCurrencyCommand.ExecuteNonQuery()
conn.Close()
End Using
End Sub
End Class
Язык C#
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public partial class StoredProcedures
{
[SqlProcedure()]
public static void InsertCurrency_CS(
SqlString currencyCode, SqlString name)
{
using (SqlConnection conn = new SqlConnection("context connection=true"))
{
SqlCommand InsertCurrencyCommand = new SqlCommand();
SqlParameter currencyCodeParam = new SqlParameter("@CurrencyCode", SqlDbType.NVarChar);
SqlParameter nameParam = new SqlParameter("@Name", SqlDbType.NVarChar);
currencyCodeParam.Value = currencyCode;
nameParam.Value = name;
InsertCurrencyCommand.Parameters.Add(currencyCodeParam);
InsertCurrencyCommand.Parameters.Add(nameParam);
InsertCurrencyCommand.CommandText =
"INSERT Sales.Currency (CurrencyCode, Name, ModifiedDate)" +
" VALUES(@CurrencyCode, @Name, GetDate())";
InsertCurrencyCommand.Connection = conn;
conn.Open();
InsertCurrencyCommand.ExecuteNonQuery();
conn.Close();
}
}
}
Язык Visual C++
#include "stdafx.h"
#using <System.dll>
#using <System.Data.dll>
#using <System.Xml.dll>
using namespace System;
using namespace System::Data;
using namespace System::Data::Sql;
using namespace System::Data::SqlClient;
using namespace System::Data::SqlTypes;
using namespace Microsoft::SqlServer::Server;
// In order to debug your Stored Procedure, add the following to your debug.sql file:
//
// EXEC InsertCurrency_CPP 'AAA', 'Currency Test'
// SELECT * FROM Sales.Currency WHERE CurrencyCode = 'AAA'
public ref class StoredProcedures
{
public:
[SqlProcedure]
static void InsertCurrency_CPP(SqlString currencyCode, SqlString name)
{
SqlConnection ^conn = gcnew SqlConnection("context connection=true");
SqlCommand ^insertCurrencyCommand = gcnew SqlCommand();
SqlParameter ^currencyCodeParam =
gcnew SqlParameter("@CurrencyCode", SqlDbType::NVarChar);
SqlParameter ^nameParam =
gcnew SqlParameter("@Name", SqlDbType::NVarChar);
insertCurrencyCommand->CommandText =
"insert Sales.Currency(CurrencyCode, Name, ModifiedDate)" +
" values(@CurrencyCode, @Name)";
insertCurrencyCommand->Connection = conn;
conn->Open();
insertCurrencyCommand->ExecuteNonQuery();
conn->Close();
}
};
Добавьте код для запуска и тестирования сохраненной процедуры в файл Test.sql (debug.sql в Visual C++) в папке проекта TestScripts. Например, если развернута сохраненная процедура, запустите ее, вызвав EXEC <StoredProcedureName> и передав корректные параметры. Если сохраненная процедура не возвращает никаких значений, вставьте дополнительный код для проверки данных, на которые влияет сохраненная процедура.
EXEC InsertCurrency 'AAA', 'Currency Test'
SELECT * from Sales.Currency where CurrencyCode = 'AAA'
6. Атрибуты проектов SQL Server и объектов баз данных
К каждому проекту Microsoft SQL Server и ко всем файлам объектов базы данных, которые есть в проекте, должен быть применен атрибут.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Атрибут SqlAssembly.
Этот атрибут должен применяться ко всем сборкам, которые будут развернуты в базе данных Microsoft SQL Server 2005. Этот атрибут не имеет параметров. Он добавляется в файл AssemblyInfo при создании проекта SQL Server.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  SqlUserDefinedAggregate Attribute
Этот атрибут должен быть применен к статистическим файлам. Этот атрибут имеет два свойства: Format и MaxByteSize.
Format
Обязательный параметр. Формат хранения статистической функции. Поддерживаемые форматы:
Native — указывает, что SQL Server использует эффективное представление в машинном коде на диске. Этот формат является наиболее компактным и обеспечивает максимальную производительность. Требования для этого формата:
Атрибут StructLayout.LayoutKindSequential должен быть применен к статистической функции.
Все поля статистической функции должны быть непреобразуемыми, т. е. они должны иметь общее представление в управляемой и в неуправляемой памяти и не требовать специальной обработки маршаллером взаимодействия.
Статистическая функция не должна определять значение для MaxByteSize.
Статистическая функция не должна иметь полей [NonSerialized].
Поля не должны быть помечены как явный макет.
UserDefined — указывает, что пользователь имеет полный контроль над двоичным форматом. Требования для этого формата:
Статистическая функция должна реализовывать IBinarySerialize.
Статистическая функция должна определять значение для MaxByteSize.
MaxByteSize
Максимальный размер экземпляра этой статистической функции в байтах. Требуется, только если для свойства UserDefined задано значение Format. Не требуется указывать, если для свойства Native задано значение Format.
Этот пример указывает, что для свойства Native значением является Format агрегата.
Язык Visual Basic
<SqlUserDefinedAggregate(Format.Native)> _
Public Class SampleAggregate
'...
End Class
Язык C#
[SqlUserDefinedAggregate(Format.Native)]
public class SampleAggregate
{
//...
}
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Атрибут SqlProcedure
Этот атрибут должен быть применен к файлам сохраненных процедур. Этот атрибут имеет следующие параметры:
Name — необязательный. Задает имя, которое используется на сервере SQL Server для обращения к сохраненной процедуре.
Этот пример устанавливает, что к сохраненной процедуре обращаются по имени sp_sqlName.
Язык Visual Basic
Partial Public Class StoredProcedures
<SqlProcedure(Name:="sp_sqlName")> _
Public Shared Sub SampleProcedure(ByVal s As SqlString)
'...
End Sub
End Class
Язык C#
public partial class StoredProcedures
{
[SqlProcedure(Name="sp_sqlName")]
public static void SampleProcedure(SqlString s)
{
//...
}
}
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Атрибут SqlTrigger
Этот атрибут должен быть применен к файлам триггеров. Этот атрибут имеет следующие параметры:
Name — необязательный. Определяет имя, которое используется на сервере SQL Server для ссылок на триггер.
Target — обязательный. Указывает таблицу, к которой применяется этот триггер.
Event — обязательный. Указывает действие, которое активирует триггер.
В этом примере триггер активируется путем обновления существующих данных (UPDATE) в таблице authors.
Язык Visual Basic
Partial Public Class Triggers
<SqlTrigger(Target:="authors", Event:="FOR UPDATE")> _
Public Shared Sub AuthorsUpdateTrigger()
'...
End Sub
End Class
Язык C#
public partial class Triggers
{
[SqlTrigger(Target="authors", Event="FOR UPDATE")]
public static void AuthorsUpdateTrigger()
{
//...
}
}
В этом примере к триггеру обращаются по имени trig_onpubinsert. Триггер активизирован путем добавления новых данных (INSERT) в таблицу publishers.
Язык Visual Basic
Partial Public Class Triggers
<SqlTrigger(Name:="trig_onpubinsert", Target:="publishers", Event:="FOR INSERT")> _
Public Shared Sub PublishersInsertTrigger()
'...
End Sub
End Class
Язык C#
public partial class Triggers
{
[SqlTrigger(Name="trig_onpubinsert", Target="publishers", Event="FOR INSERT")]
public static void PublishersInsertTrigger()
{
//...
}
}
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Атрибут SqlFunction
Этот атрибут должен быть применен к файлам пользовательских функций, которые возвращают скалярное значение или таблицу. Этот атрибут имеет следующие параметры:
Name — необязательный. Указывает имя, используемое на сервере SQL Server для обращения к пользовательской функции.
В этом примере к пользовательской функции обращаются по имени sp_scalarFunc.
Язык Visual Basic
Partial Public Class UserDefinedFunctions
<SqlFunction(Name:="sp_scalarFunc")> _
Public Shared Function SampleScalarFunction(ByVal s As SqlString) As SqlString
'...
Return ""
End Function
End Class
Язык C#
public partial class UserDefinedFunctions
{
[SqlFunction(Name="sp_scalarFunc")]
public static SqlString SampleScalarFunction(SqlString s)
{
//...
return "";
}
}
В этом примере к пользовательской функции обращаются по имени sp_tableFunc. Свойство TableDefinition имеет значение letter nchar(1).
Язык Visual Basic
Partial Public Class UserDefinedFunctions
<SqlFunction(Name:="sp_tableFunc", TableDefinition:="letter nchar(1)")> _
Public Shared Function SampleTableFunction(ByVal s As SqlString) As IEnumerable
'...
Return New Char(2) {"a"c, "b"c, "c"c}
End Function
End Class
Язык C#
public partial class UserDefinedFunctions
{
[SqlFunction(Name="sp_tableFunc", TableDefinition="letter nchar(1)")]
public static IEnumerable SampleTableFunction(SqlString s)
{
//...
return new ArrayList(new char[3] {'a', 'b', 'c'});
}
}
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Атрибут SqlUserDefinedType
Этот атрибут должен применяться к файлам пользовательских типов. Этот атрибут имеет четыре свойства: Format, MaxByteSize, IsFixedLength и IsByteOrdered.
Format
Обязательный. Формат хранения пользовательского типа. Поддерживаемые форматы:
Native — указывает, что SQL Server использует эффективное представление в машинном коде на диске. Это параметр наибольшей компактности, обычно обладающий максимальной производительностью. Требования для этого формата:
Атрибут StructLayout.LayoutKindSequential должен применяться к типу.
Все поля пользовательского типа должны быть непреобразуемыми, т. е. они должны иметь общее представление в управляемой/неуправляемой памяти и не требовать специальной обработки маршаллером взаимодействия.
Тип не должен определять значение для MaxByteSize.
Тип не должен иметь полей [NonSerialized].
Поля не должны быть помечены как явный макет.
UserDefined — указывает, что пользователь имеет полный контроль над двоичным форматом. Требования для этого формата:
Тип должен реализовывать IBinarySerialize.
Тип должен определять значение для MaxByteSize.
MaxByteSize
Обязательный. Максимальный размер экземпляра этого типа в байтах.
IsFixedLength
Необязательно. Указывает, имеют ли все экземпляры типа одинаковую длину. Значение по умолчанию — false.
IsByteOrdered
Необязательный. Указывает, упорядочено ли двоичное представление этого типа, то есть может ли оно использоваться для сравнения экземпляров данного типа. Значение по умолчанию — false.
Этот пример устанавливает, что Format пользовательского типа является SerializedDataWithMetadata и MaxByteSize равен 8000 байт.
Язык Visual Basic
<SqlUserDefinedType(Format.Native, MaxByteSize:=8000)> _
Public Class SampleType
'...
End Class
Язык C#
[SqlUserDefinedType(Format.Native, MaxByteSize=8000)]
public class SampleType
{
//...
}
7. Преимущества использования управляемого кода для создания объектов баз данных
Можно использовать языки .NET в дополнение к языку программирования Transact-SQL для создания объектов базы данных и извлечения и обновления данных в базах данных Microsoft SQL Server 2005. В проектах Visual Basic, Visual C# или Visual C++ можно создать хранимые процедуры, триггеры, статистические выражения, пользовательские функции и пользовательские типы.
В следующем списке приводится сводка преимуществ использования языка .NET Framework вместо Transact-SQL.
Расширенная модель программирования   Языки .NET предлагают конструкции и возможности, ранее недоступные для разработчиков SQL.
Расширенная безопасность   Управляемый код выполняется в общеязыковой среде выполнения, размещенной в ядре базы данных. Это делает объекты платформы .NET Framework более безопасными, чем расширенные хранимые процедуры в более ранних версиях SQL Server.
Пользовательские типы и статистические выражения   Пользовательские типы и пользовательские статистические выражения — это два новых управляемых объекта базы данных, расширяющих возможности SQL Server по хранению и построению запросов.
Общая среда разработки   Разработка баз данных интегрирована в среду разработки Microsoft Visual Studio. Разработчики используют для разработки и отладки объектов баз данных и сценариев те же средства, что и для написания компонентов и служб платформы .NET Framework промежуточного или клиентского уровня.
Лучшая производительность   Некоторые функции, например, выполняющие математические операции над каждой строкой в базе данных, могут выполняться эффективнее, если они представляют собой скомпилированные сборки, построенные из проекта Visual Basic, Visual C# или Visual C++, чем если они написаны на языке Transact-SQL, который представляет собой интерпретируемый код. Например, будет повышена производительность функций, особенно тех, которые выполняют целочисленные операции. Однако хранимые процедуры, осуществляющие только доступ к данным, не будут выполняться эффективнее.
Набор операторов языков   Visual C++, Visual Basic и Visual C# предоставляет возможности, недоступные в языке Transact-SQL, такие как массивы, обработка сложных исключений и повторное использование кода.
Повторное использование кода   Библиотеки управляемых сборок могут создаваться и распространяться легче, чем сценарий Transact-SQL.
Расширяемость   С помощью Visual Basic, Visual C# или Visual C++, можно создать два объекта базы данных, которые не могут быть созданы с помощью языка Transact-SQL: статистические выражения и пользовательские типы.
Совершенствование существующих навыков   Можно использовать и повышать свои навыки в использовании языков и среды разработки, в работе с которыми пользователь уже имеет опыт, для создания объектов базы данных.
Обширный опыт разработки   При проектировании объектов базы данных с помощью шаблона проекта SQL Server имеет место полная интеграция с системой разработки проектов. Сюда входят построение, отладка и развертывание на нескольких серверах.
Стабильность и надежность   Объекты базы данных, создаваемые с помощью Visual Basic, Visual C# или Visual C++, являются более безопасными, стабильными и надежными, чем расширенные хранимые процедуры, которые могут вызывать утечки памяти или другие проблемы, снижающие производительность и надежность сервера. При запуске хранимых процедур, созданных в Visual Basic, Visual C# или Visual C++, управление памятью и работа с потоками хранимой процедурой не выполняются и, таким образом, обрабатываются более надежно.
Безопасность   При использовании объектов базы данных, созданных в Visual Basic, Visual C# или Visual C++, безопасность доступа к коду этих языков сочетается с пользовательскими разрешениями в SQL Server.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Хранимые процедуры и триггеры
Хранимые процедуры представляют собой предкомпилированную коллекцию программных инструкций, которая выполняет операции в базе данных, хранится под определенным именем и обрабатывается как единое целое единое. Дополнительные сведения о хранимых процедурах см. в документации по SQL Server.
Триггер — это особый тип хранимой процедуры, которая активируется при изменении данных в указанной таблице с помощью одной или нескольких операций изменения данных: UPDATE, INSERT или DELETE. Дополнительные сведения о триггерах см. в документации по SQL Server.
Разработка хранимых процедур и триггеров была улучшена за счет набора операторов языков Visual Basic, Visual C# и Visual C++, особенно при реализации сложной процедурной логики, необходимой для применения бизнес-правил. Кроме того, платформа.NET Framework содержит множество библиотек. Особый интерес представляют те, которые позволяют использовать многочисленные средства криптографии, обширные математические библиотеки и внешний доступ к веб-службам, файлам и коммуникационным системам типа "бизнес-бизнес".
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Функции
Функции могут воздействовать на одно или несколько значений для возврата либо скалярного значения, либо таблицы. Дополнительные сведения о типах функций, которые предоставляет язык программирования Transact-SQL, см. в документации по SQL Server.
Как и в случае с хранимыми процедурами и триггерами, разработка функций улучшена за счет набора операторов языков Visual Basic, Visual C# и Visual C++ и доступа к многочисленным библиотекам, содержащимся в платформе .NET Framework.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Статистические выражения
Статистические функции служат для подведения итогов для всех данных в таблице. Они выполняют вычисления над набором значений и возвращают одно скалярное значение. Дополнительные сведения о статистических функциях, предоставляемых службами программирования языка Transact-SQL, см. в документации по SQL Server.
Чтобы дополнить эти статистические функции, можно определить новые статистические функции, выполняющие более сложные арифметические операции. Например, можно выполнять вычисления над данными в нескольких строках и вернуть одно значение или создать сцепленную строку.
INCLUDEPICTURE "http://i.msdn.microsoft.com/Global/Images/clear.gif" \* MERGEFORMATINET  Пользовательские типы
Типы указывают на природу данных. Сведения о наборе системных типов данных, поставляемых вместе с SQL Server, см. в документации по SQL Server.
С помощью Visual Basic, Visual C# и Visual C++ можно определить новые типы, чтобы больше не быть ограниченным предопределенными типами, поставляемыми вместе с SQL Server. Можно создавать простые типы, например почтовые коды, или более сложные типы для разбора сведений, возвращаемых из транзакции над кредитными картами. Кроме того, при работе с пользовательскими типами данные могут интерпретироваться и обрабатываться как на SQL-клиенте, так и на SQL Server; с помощью ADO.NET можно загрузить сборку, содержащую определение типа из SQL Server и использовать его для проверки данных на SQL-клиенте.
8.Определение триггера в стандарте языка SQL
Триггеры являются одной из разновидностей хранимых процедур. Их исполнение происходит при выполнении для таблицы какого-либо оператора языка манипулирования данными (DML). Триггеры используются для проверки целостности данных, а также для отката транзакций.
Триггер – это откомпилированная SQL-процедура, исполнение которой обусловлено наступлением определенных событий внутри реляционной базы данных. Применение триггеров большей частью весьма удобно для пользователей базы данных. И все же их использование часто связано с дополнительными затратами ресурсов на операции ввода/вывода. В том случае, когда тех же результатов (с гораздо меньшими непроизводительными затратами ресурсов) можно добиться с помощью хранимых процедур или прикладных программ, применение триггеров нецелесообразно.
Триггеры – особый инструмент SQL-сервера, используемый для поддержания целостности данных в базе данных. С помощью ограничений целостности, правил и значений по умолчанию не всегда можно добиться нужного уровня функциональности. Часто требуется реализовать сложные алгоритмы проверки данных, гарантирующие их достоверность и реальность. Кроме того, иногда необходимо отслеживать изменения значений таблицы, чтобы нужным образом изменить связанные данные. Триггеры можно рассматривать как своего рода фильтры, вступающие в действие после выполнения всех операций в соответствии с правилами, стандартными значениями и т.д.
Триггер представляет собой специальный тип хранимых процедур, запускаемых сервером автоматически при попытке изменения данных в таблицах, с которыми триггеры связаны. Каждый триггер привязывается к конкретной таблице. Все производимые им модификации данных рассматриваются как одна транзакция. В случае обнаружения ошибки или нарушения целостности данных происходит откат этой транзакции. Тем самым внесение изменений запрещается. Отменяются также все изменения, уже сделанные триггером.
Создает триггер только владелец базы данных. Это ограничение позволяет избежать случайного изменения структуры таблиц, способов связи с ними других объектов и т.п.
Триггер представляет собой весьма полезное и в то же время опасное средство. Так, при неправильной логике его работы можно легко уничтожить целую базу данных, поэтому триггеры необходимо очень тщательно отлаживать.
В отличие от обычной подпрограммы, триггер выполняется неявно в каждом случае возникновения триггерного события, к тому же он не имеет аргументов. Приведение его в действие иногда называют запуском триггера. С помощью триггеров достигаются следующие цели:
проверка корректности введенных данных и выполнение сложных ограничений целостности данных, которые трудно, если вообще возможно, поддерживать с помощью ограничений целостности, установленных для таблицы;
выдача предупреждений, напоминающих о необходимости выполнения некоторых действий при обновлении таблицы, реализованном определенным образом;
накопление аудиторской информации посредством фиксации сведений о внесенных изменениях и тех лицах, которые их выполнили;
поддержка репликации.
Основной формат команды CREATE TRIGGER показан ниже:
<Определение_триггера>::=
CREATE TRIGGER имя_триггера
BEFORE | AFTER <триггерное_событие>
ON <имя_таблицы>
[REFERENCING
<список_старых_или_новых_псевдонимов>]
[FOR EACH { ROW | STATEMENT}]
[WHEN(условие_триггера)]
<тело_триггера>
триггерные события состоят из вставки, удаления и обновления строк в таблице. В последнем случае для триггерного события можно указать конкретные имена столбцов таблицы. Время запуска триггера определяется с помощью ключевых слов BEFORE (триггер запускается до выполнения связанных с ним событий) или AFTER (после их выполнения).
Выполняемые триггером действия задаются для каждой строки (FOR EACH ROW), охваченной данным событием, или только один раз для каждого события (FOR EACH STATEMENT).
Обозначение <список_старых_или_новых_псевдонимов> относится к таким компонентам, как старая или новая строка (OLD / NEW) либо старая или новая таблица (OLD TABLE / NEW TABLE). Ясно, что старые значения не применимы для событий вставки, а новые – для событий удаления.
При условии правильного использования триггеры могут стать очень мощным механизмом. Основное их преимущество заключается в том, что стандартные функции сохраняются внутри базы данных и согласованно активизируются при каждом ее обновлении. Это может существенно упростить приложения. Тем не менее следует упомянуть и о присущих триггеру недостатках:
сложность: при перемещении некоторых функций в базу данных усложняются задачи ее проектирования, реализации и администрирования;
скрытая функциональность: перенос части функций в базу данных и сохранение их в виде одного или нескольких триггеров иногда приводит к сокрытию от пользователя некоторых функциональных возможностей. Хотя это в определенной степени упрощает его работу, но, к сожалению, может стать причиной незапланированных, потенциально нежелательных и вредных побочных эффектов, поскольку в этом случае пользователь не в состоянии контролировать все процессы, происходящие в базе данных;
влияние на производительность: перед выполнением каждой команды по изменению состояния базы данных СУБД должна проверить триггерное условие с целью выяснения необходимости запуска триггера для этой команды. Выполнение подобных вычислений сказывается на общей производительности СУБД, а в моменты пиковой нагрузки ее снижение может стать особенно заметным. Очевидно, что при возрастании количества триггеров увеличиваются и накладные расходы, связанные с такими операциями.
Неправильно написанные триггеры могут привести к серьезным проблемам, таким, например, как появление "мертвых" блокировок. Триггеры способны длительное время блокировать множество ресурсов, поэтому следует обратить особое внимание на сведение к минимуму конфликтов доступа.
Реализация триггеров в среде MS SQL Server
В реализации СУБД MS SQL Server используется следующий оператор создания или изменения триггера:
<Определение_триггера>::=
{CREATE | ALTER} TRIGGER имя_триггера
ON {имя_таблицы | имя_просмотра }
[WITH ENCRYPTION ]
{
{ { FOR | AFTER | INSTEAD OF }
{ [ DELETE] [,] [ INSERT] [,] [ UPDATE] }
[ WITH APPEND ]
[ NOT FOR REPLICATION ]
AS
sql_оператор[...n]
} |
{ {FOR | AFTER | INSTEAD OF } { [INSERT] [,]
[UPDATE] }
[ WITH APPEND]
[ NOT FOR REPLICATION]
AS
{ IF UPDATE(имя_столбца)
[ {AND | OR} UPDATE(имя_столбца)] [...n]
|
IF (COLUMNS_UPDATES(){оператор_бит_обработки}
бит_маска_изменения)
{оператор_бит_сравнения }бит_маска [...n]}
sql_оператор [...n]
}
}
Триггер может быть создан только в текущей базе данных, но допускается обращение внутри триггера к другим базам данных, в том числе и расположенным на удаленном сервере.
Рассмотрим назначение аргументов из команды CREATE | ALTER TRIGGER.
Имя триггера должно быть уникальным в пределах базы данных. Дополнительно можно указать имя владельца.
При указании аргумента WITH ENCRYPTION сервер выполняет шифрование кода триггера, чтобы никто, включая администратора, не мог получить к нему доступ и прочитать его. Шифрование часто используется для скрытия авторских алгоритмов обработки данных, являющихся интеллектуальной собственностью программиста или коммерческой тайной.
Типы триггеров
В SQL Server существует два параметра, определяющих поведение триггеров:
AFTER. Триггер выполняется после успешного выполнения вызвавших его команд. Если же команды по какой-либо причине не могут быть успешно завершены, триггер не выполняется. Следует отметить, что изменения данных в результате выполнения запроса пользователя и выполнение триггера осуществляется в теле одной транзакции: если произойдет откат триггера, то будут отклонены и пользовательские изменения. Можно определить несколько AFTER-триггеров для каждой операции (INSERT, UPDATE, DELETE). Если для таблицы предусмотрено выполнение нескольких AFTER-триггеров, то с помощью системной хранимой процедуры sp_settriggerorder можно указать, какой из них будет выполняться первым, а какой последним. По умолчанию в SQL Server все триггеры являются AFTER-триггерами.
INSTEAD OF. Триггер вызывается вместо выполнения команд. В отличие от AFTER-триггера INSTEAD OF-триггер может быть определен как для таблицы, так и для просмотра. Для каждой операции INSERT, UPDATE, DELETE можно определить только один INSTEAD OF-триггер.
Триггеры различают по типу команд, на которые они реагируют.
Существует три типа триггеров:
INSERT TRIGGER – запускаются при попытке вставки данных с помощью команды INSERT.
UPDATE TRIGGER – запускаются при попытке изменения данных с помощью команды UPDATE.
DELETE TRIGGER – запускаются при попытке удаления данных с помощью команды DELETE.
Конструкции [ DELETE] [,] [ INSERT] [,] [ UPDATE] и FOR | AFTER | INSTEAD OF } { [INSERT] [,] [UPDATE] определяют, на какую команду будет реагировать триггер. При его создании должна быть указана хотя бы одна команда. Допускается создание триггера, реагирующего на две или на все три команды.
Аргумент WITH APPEND позволяет создавать несколько триггеров каждого типа.
При создании триггера с аргументом NOT FOR REPLICATION запрещается его запуск во время выполнения модификации таблиц механизмами репликации.
Конструкция AS sql_оператор[...n] определяет набор SQL- операторов и команд, которые будут выполнены при запуске триггера.
Отметим, что внутри триггера не допускается выполнение ряда операций, таких, например, как:
создание, изменение и удаление базы данных;
восстановление резервной копии базы данных или журнала транзакций.
Выполнение этих команд не разрешено, так как они не могут быть отменены в случае отката транзакции, в которой выполняется триггер. Это запрещение вряд ли может каким-то образом сказаться на функциональности создаваемых триггеров. Трудно найти такую ситуацию, когда, например, после изменения строки таблицы потребуется выполнить восстановление резервной копии журнала транзакций.
Программирование триггера
При выполнении команд добавления, изменения и удаления записей сервер создает две специальные таблицы: inserted и deleted. В них содержатся списки строк, которые будут вставлены или удалены по завершении транзакции. Структура таблиц inserted и deleted идентична структуре таблиц, для которой определяется триггер. Для каждого триггера создается свой комплект таблиц inserted и deleted, поэтому никакой другой триггер не сможет получить к ним доступ. В зависимости от типа операции, вызвавшей выполнение триггера, содержимое таблиц inserted и deleted может быть разным:
команда INSERT – в таблице inserted содержатся все строки, которые пользователь пытается вставить в таблицу; в таблице deleted не будет ни одной строки; после завершения триггера все строки из таблицы inserted переместятся в исходную таблицу;
команда DELETE – в таблице deleted будут содержаться все строки, которые пользователь попытается удалить; триггер может проверить каждую строку и определить, разрешено ли ее удаление; в таблице inserted не окажется ни одной строки;
команда UPDATE – при ее выполнении в таблице deleted находятся старые значения строк, которые будут удалены при успешном завершении триггера. Новые значения строк содержатся в таблице inserted. Эти строки добавятся в исходную таблицу после успешного выполнения триггера.
Для получения информации о количестве строк, которое будет изменено при успешном завершении триггера, можно использовать функцию @@ROWCOUNT; она возвращает количество строк, обработанных последней командой. Следует подчеркнуть, что триггер запускается не при попытке изменить конкретную строку, а в момент выполнения команды изменения. Одна такая команда воздействует на множество строк, поэтому триггер должен обрабатывать все эти строки.
Если триггер обнаружил, что из 100 вставляемых, изменяемых или удаляемых строк только одна не удовлетворяет тем или иным условиям, то никакая строка не будет вставлена, изменена или удалена. Такое поведение обусловлено требованиями транзакции – должны быть выполнены либо все модификации, либо ни одной.
Триггер выполняется как неявно определенная транзакция, поэтому внутри триггера допускается применение команд управления транзакциями. В частности, при обнаружении нарушения ограничений целостности для прерывания выполнения триггера и отмены всех изменений, которые пытался выполнить пользователь, необходимо использовать команду ROLLBACK TRANSACTION.
Для получения списка столбцов, измененных при выполнении команд INSERT или UPDATE, вызвавших выполнение триггера, можно использовать функцию COLUMNS_UPDATED(). Она возвращает двоичное число, каждый бит которого, начиная с младшего, соответствует одному столбцу таблицы (в порядке следования столбцов при создании таблицы). Если бит установлен в значение "1", то соответствующий столбец был изменен. Кроме того, факт изменения столбца определяет и функция UPDATE (имя_столбца).
Для удаления триггера используется команда
DROP TRIGGER {имя_триггера} [,...n]
Приведем примеры использования триггеров.
Пример 14.1. Использование триггера для реализации ограничений на значение. В добавляемой в таблицу Сделка записи количество проданного товара должно быть не меньше, чем его остаток из таблицы Склад.
Команда вставки записи в таблицу Сделка может быть, например, такой:
INSERT INTO Сделка
VALUES (3,1,-299,'01/08/2002')
Создаваемый триггер должен отреагировать на ее выполнение следующим образом: необходимо отменить команду, если в таблице Склад величина остатка товара оказалась меньше продаваемого количества товара с введенным кодом (в примере код товара=3). Во вставляемой записи количество товара указывается со знаком "+", если товар поставляется, и со знаком "-", если он продается. Представленный триггер настроен на обработку только одной добавляемой записи.
CREATE TRIGGER Триггер_ins
ON Сделка FOR INSERT
AS
IF @@ROWCOUNT=1
BEGIN
IF NOT EXISTS(SELECT *
FROM inserted
WHERE -inserted.количество<=ALL(SELECT
Склад.Остаток
FROM Склад,Сделка
WHERE Склад.КодТовара=
Сделка.КодТовара))
BEGIN
ROLLBACK TRAN
PRINT
'Отмена поставки: товара на складе нет'
END
END
Пример 14.1. Использование триггера для реализации ограничений на значение.
Пример 14.2. Использования триггера для сбора статистических данных.
Создать триггер для обработки операции вставки записи в таблицу Сделка, например, такой команды:
INSERT INTO Сделка
VALUES (3,1,200,'01/08/2002')
поставляется товар с кодом 3 от клиента с кодом 1 в количестве 200 единиц.
При продаже или получении товара необходимо соответствующим образом изменить количество его складского запаса. Если товара на складе еще нет, необходимо добавить соответствующую запись в таблицу Склад. Триггер обрабатывает только одну добавляемую строку.
ALTER TRIGGER Триггер_ins
ON Сделка FOR INSERT
AS
DECLARE @x INT, @y INT
IF @@ROWCOUNT=1
--в таблицу Сделка добавляется запись
--о поставке товара
BEGIN
--количество проданного товара должно быть не
--меньше, чем его остаток из таблицы Склад
IF NOT EXISTS(SELECT *
FROM inserted
WHERE -inserted.количество<
=ALL(SELECT Склад.Остаток
FROM Склад,Сделка
WHERE Склад.КодТовара=
Сделка.КодТовара))
BEGIN
ROLLBACK TRAN
PRINT 'откат товара нет '
END
--если записи о поставленном товаре еще нет,
--добавляется соответствующая запись
--в таблицу Склад
IF NOT EXISTS ( SELECT *
FROM Склад С, inserted i
WHERE С.КодТовара=i.КодТовара )
INSERT INTO Склад (КодТовара,Остаток)
ELSE
--если запись о товаре уже была в таблице
--Склад, то определяется код и количество
--товара издобавленной в таблицу Сделка записи
BEGIN
SELECT @y=i.КодТовара, @x=i.Количество
FROM Сделка С, inserted i
WHERE С.КодТовара=i.КодТовара
--и производится изменения количества товара в
--таблице Склад
UPDATE Склад
SET Остаток=остаток+@x
WHERE КодТовара=@y
END
END
Пример 14.2. Использования триггера для сбора статистических данных.
Пример 14.3. Создать триггер для обработки операции удаления записи из таблицы Сделка, например, такой команды:
DELETE FROM Сделка WHERE КодСделки=4
Для товара, код которого указан при удалении записи, необходимо откорректировать его остаток на складе. Триггер обрабатывает только одну удаляемую запись.
CREATE TRIGGER Триггер_del
ON Сделка FOR DELETE
AS
IF @@ROWCOUNT=1 -- удалена одна запись
BEGIN
DECLARE @y INT,@x INT
--определяется код и количество товара из
--удаленной из таблицы Склад записи
SELECT @y=КодТовара, @x=Количество
FROM deleted
--в таблице Склад корректируется количество
--товара
UPDATE Склад
SET Остаток=Остаток-@x
WHERE КодТовара=@y
END
Пример 14.3. Триггер для обработки операции удаления записи из таблицы
Пример 14.4. Создать триггер для обработки операции изменения записи в таблице Сделка, например, такой командой:
UPDATE Сделка SET количество=количество-10
WHERE КодТовара=3
во всех сделках с товаром, имеющим код, равный 3, уменьшить количество товара на 10 единиц.
Указанная команда может привести к изменению сразу нескольких записей в таблице Сделка. Поэтому покажем, как создать триггер, обрабатывающий не одну запись. Для каждой измененной записи необходимо для старого (до изменения) кода товара уменьшить остаток товара на складе на величину старого (до изменения) количества товара и для нового (после изменения) кода товара увеличить его остаток на складе на величину нового (после изменения) значения. Чтобы обработать все измененные записи, введем курсоры, в которых сохраним все старые (из таблицы deleted) и все новые значения (из таблицы inserted).
CREATE TRIGGER Триггер_upd
ON Сделка FOR UPDATE
AS
DECLARE @x INT, @x_old INT, @y INT, @y_old INT
-- курсор с новыми значениями
DECLARE CUR1 CURSOR FOR
SELECT КодТовара,Количество
FROM inserted
-- курсор со старыми значениями
DECLARE CUR2 CURSOR FOR
SELECT КодТовара,Количество
FROM deleted
OPEN CUR1
OPEN CUR2
-- перемещаемся параллельно по обоим курсорам
FETCH NEXT FROM CUR1 INTO @x, @y
FETCH NEXT FROM CUR2 INTO @x_old, @y_old
WHILE @@FETCH_STATUS=0
BEGIN
--для старого кода товара уменьшается его
--количество на складе
UPDATE Склад
SET Остаток=Остаток-@y_old
WHERE КодТовара=@x_old
--для нового кода товара, если такого товара
--еще нет на складе, вводится новая запись
IF NOT EXISTS (SELECT * FROM Склад
WHERE КодТовара=@x)
INSERT INTO Склад(КодТовара,Остаток)
VALUES (@x,@y)
ELSE
--иначе для нового кода товара увеличивается
--его количество на складе
UPDATE Склад
SET Остаток=Остаток+@y
WHERE КодТовара=@x
FETCH NEXT FROM CUR1 INTO @x, @y
FETCH NEXT FROM CUR2 INTO @x_old, @y_old
END
CLOSE CUR1
CLOSE CUR2
DEALLOCATE CUR1
DEALLOCATE CUR2
Пример 14.4. триггер для обработки операции изменения записи в таблице
В рассмотренном триггере отсутствует сравнение количества товара при изменении записи о сделке с его остатком на складе.
Пример 14.5. Исправим этот недостаток. Для генерирования сообщения об ошибке используем в теле триггера команду MS SQL Server RAISERROR, аргументами которой являются текст сообщения, уровень серьезности и статус ошибки.
ALTER TRIGGER Триггер_upd
ON Сделка FOR UPDATE
AS
DECLARE @x INT, @x_old INT, @y INT,
@y_old INT ,@o INT
DECLARE CUR1 CURSOR FOR
SELECT КодТовара,Количество
FROM inserted
DECLARE CUR2 CURSOR FOR
SELECT КодТовара,Количество
FROM deleted
OPEN CUR1
OPEN CUR2
FETCH NEXT FROM CUR1 INTO @x, @y
FETCH NEXT FROM CUR2 INTO @x_old, @y_old
WHILE @@FETCH_STATUS=0
BEGIN
SELECT @o=остаток
FROM Склад
WHERE кодтовара=@x
IF @o<-@y
BEGIN
RAISERROR('откат',16,10)
CLOSE CUR1
CLOSE CUR2
DEALLOCATE CUR1
DEALLOCATE CUR22
ROLLBACK TRAN
RETURN
END
UPDATE Склад
SET Остаток=Остаток-@y_old
WHERE КодТовара=@x_old
IF NOT EXISTS (SELECT * FROM Склад
WHERE КодТовара=@x)
INSERT INTO Склад(КодТовара,Остаток)
VALUES (@x,@y)
ELSE
UPDATE Склад
SET Остаток=Остаток+@y
WHERE КодТовара=@x
FETCH NEXT FROM CUR1 INTO @x, @y
FETCH NEXT FROM CUR2 INTO @x_old, @y_old
END
CLOSE CUR1
CLOSE CUR2
DEALLOCATE CUR1
DEALLOCATE CUR2
Пример 14.5. Исправленный вариант триггера для обработки операции изменения записи в таблице
Пример 14.6. В примере HYPERLINK "http://its.luguniv.edu.ua/%7EKramarenko/BD&IS/DopMateriali/SQL/intuit/sql_14.html" \l "example.14.5" 14.5 происходит отмена всех изменений при невозможности реализовать хотя бы одно из них. Создадим триггер, позволяющий отменять изменение только некоторых записей и выполнять изменение остальных.
В этом случае триггер выполняется не после изменения записей, а вместо команды изменения.
ALTER TRIGGER Триггер_upd
ON Сделка INSTEAD OF UPDATE
AS
DECLARE @k INT, @k_old INT
DECLARE @x INT, @x_old INT, @y INT
DECLARE @y_old INT ,@o INT
DECLARE CUR1 CURSOR FOR
SELECT КодСделки, КодТовара,Количество
FROM inserted
DECLARE CUR2 CURSOR FOR
SELECT КодСделки, КодТовара,Количество
FROM deleted
OPEN CUR1
OPEN CUR2
FETCH NEXT FROM CUR1 INTO @k,@x, @y
FETCH NEXT FROM CUR2 INTO @k_old,@x_old,
@y_old
WHILE @@FETCH_STATUS=0
BEGIN
SELECT @o=остаток
FROM Склад
WHERE КодТовара=@x
IF @o>=-@y
BEGIN
RAISERROR('изменение',16,10)
UPDATE Сделка SET количество=@y,
КодТовара=@x
WHERE КодСделки=@k
UPDATE Склад
SET Остаток=Остаток-@y_old
WHERE КодТовара=@x_old
IF NOT EXISTS (SELECT * FROM Склад
WHERE КодТовара=@x)
INSERT INTO Склад(КодТовара, Остаток)
VALUES (@x,@y)
ELSE
UPDATE Склад
SET Остаток=Остаток+@y
WHERE КодТовара=@x
END
ELSE
RAISERROR('запись не изменена',16,10)
FETCH NEXT FROM CUR1 INTO @k,@x, @y
FETCH NEXT FROM CUR2 INTO @k_old,@x_old,
@y_old
END
CLOSE CUR1
CLOSE CUR2
DEALLOCATE CUR1
DEALLOCATE CUR2
Пример 14.6. Триггер, позволяющий отменять изменение только некоторых записей и выполнять изменение остальных.