- •Содержание
- •Введение
- •1Лабораторная работа № 1 Создание концептуальной модели предметной области
- •1.5Критерии результативности выполнения лабораторной работы
- •Контрольные вопросы
- •2Лабораторная работа № 2 Создание и заполнение реляционной базы данных с помощью языка sql
- •2.1Цель лабораторной работы
- •2.2Задачи лабораторной работы
- •2.3Задание
- •2.4Методика выполнения задания
- •Этап 1. Создание структуры реляционной базы данных и ее модификация
- •Этап 2. Выполнение операций вставки, обновления и удаления данных
- •Этап 3. Выполнение операций выборки данных из одной таблицы
- •2.5Критерии результативности выполнения лабораторной работы
- •Контрольные вопросы
- •3.4Методика выполнения задания
- •Этап 1. Сортировка и встроенные функции
- •Этап 2. Вложенные запросы и операция соединения
- •Этап 3. Создание и обновление представлений
- •3.5Критерии результативности выполнения лабораторной работы
- •Контрольные вопросы
- •4Лабораторная работа № 4 Разработка триггеров и хранимых процедур
- •4.5Курсоры
- •4.6Хранимые процедуры
- •4.7Триггеры
- •4.8Методика выполнения задания
- •4.9Требования к оформлению и содержанию отчета
- •4.10 Критерии результативности выполнения лабораторной работы
- •Контрольные вопросы
- •Список литературы
- •Приложение 1 Варианты заданий для лабораторной работы № 1
- •1. Страховая компания
- •2. Гостиница
- •3. Ломбард
- •4. Реализация готовой продукции
- •5. Ведение заказов
- •6. Бюро по трудоустройству
- •7. Нотариальная контора
- •8. Химчистка
- •9. Курсы повышения квалификации
- •10. Сдача в аренду торговых площадей
- •11. Распределение учебной нагрузки
- •12. Распределение дополнительных обязанностей
- •13. Техническое обслуживание станков
- •14. Туристическая фирма
- •15. Грузовые перевозки
- •Приложение 2 Построение концептуальной модели предметной области в пакете AllFusion eRwin Data Modeler Введение
- •5Описание интерфейса
- •6Создание логической модели данных
- •6.1Сущности и атрибуты
- •6.2Связи
- •6.3Ключи
- •1. Табельный номер,
- •7Создание физической модели данных
- •7.1Выбор сервера
- •7.2Таблицы и колонки
- •Приложение 3. Примеры кода на языке sql
- •Операторы ddl (Data Definition Language)
- •Определение первичных и альтернативных ключей с помощью оператора alter
- •Выборка данных – select
- •Внешние соединения
- •Представления
4.7Триггеры
SQL Server поддерживает только замещающие и завершающие триггеры. Предваряющие триггеры этой СУБД не поддерживаются. Таблица может иметь один или несколько завершающих триггеров вставки, обновления и удаления; завершающие триггеры не могут назначаться представлениям. Представление или таблица может иметь не более одного замещающего триггера на каждое из возможных действий – вставку, обновление или удаление.
В SQL Server триггеры могут производить откат транзакций, повлекших их запуск. Когда триггер выполняет команду ROLLBACK, все действия, произведенные транзакцией, которая привела к запуску триггера, отменяются. Если в тексте триггера за командой ROLLBACK следуют какие-либо инструкции, эти инструкции будут выполнены. Однако никакие инструкции, следующие за оператором, который вызвал запуск триггера, выполняться не будут.
Для триггеров вставки и обновления новые значения каждого столбца обрабатываемой таблицы хранятся в псевдотаблице под названием inserted. Если, например, в таблицы ARTIST добавляется новая строка, то псевдотаблица inserted будет содержать четыре столбца: Name, Nationality, BirthDate и DeceasedDate. Аналогичным образом, в случае обновления или удаления старые значения каждого столбца будут храниться в псевдотаблице с именем deleted. В приведенных далее примерах вы увидите, как используются эти таблицы.
Далее будут рассмотрены примеры, каждый из которых демонстрирует одно из возможных применений триггеров. Триггеры можно создавать, записывая их в текстовые файлы (первым оператором в таких файлах должен быть оператор CREATE TRIGGER или ALTER TRIGGER), Также их можно создать в программе Enterprise Manager, щелкнув правой кнопкой мыши на имени таблицы и выбрав All Tasks ► Manage Triggers (Все задачи ► Управление триггерами) или в Management Sdudio Express (рис. 4.6). В последнем случае появится окно, в которое вы можете ввести текст триггера. Однако вы не сможете сохранить триггер в базе данных, если в нем будут синтаксические ошибки.
Рис. 4.6 Создание триггера в Management Sdudio Express
4.7.1 Триггер, реализующий бизнес-правило. Галерея View Ridge ведет список «проблемных» клиентов – тех, кто не оплатил вовремя покупку или представил какие-то другие проблемы для галереи. Когда в базу данных вводится покупатель, фигурирующий в списке таких клиентов, менеджер галереи хотел бы знать об этом.
Для реализации этой политики галерея создала таблицу PROBLEM_ACCOUNT: в нее записывается информация о «проблемных» клиентах. При вставке или обновлении данных о клиенте триггер проверяет, не фигурирует ли клиент в таблице PROBLEM_ACCOUNT. Если да, то выполняется откат вставки или обновления и выводится соответствующее сообщение.
Триггер CheckForProblemAccount в листинге 6 реализует эту политику. Хотя в тексте триггера не указано явно, что он является завершающим, он именно завершающий, поскольку в SQL Server не поддерживаются предваряющие триггеры, а в заголовке триггера отсутствуют ключевые слова INSTEAD OF, которые обозначают замещающий триггер.
Листинг 6. Триггер CheckForProblemAccount
CREATE TRIGGER [CheckForProblemAccount] ON [dbo].[CUSTOMER]
FOR INSERT, UPDATE
AS
/* Пытаемся соединить данные из inserted со строкой в таблице PROBLEM_ACCOUNT */
SELECT *
FROM inserted I JOIN PROBLEM_ACCOUNT P
ON I.Name = P.Name
AND I.AreaCode = P.AreaCode
AND I.PhoneNumber = P.PhoneNumber
/*В следующем операторе необходимо проверить @@rowCount. Если результат соединения не пуст, выполняем откат транзакции и выдаем сообщение. */
If @@rowCount > 0
BEGIN
ROLLBACK
Print 'Клиент принадлежит к числу "проблемных".'
Print 'Вставка и обновление не разрешены.'
END
Триггер CheckForProblemAccount пытается соединить новые значения в псевдотаблице inserted по очереди со всеми строками таблицы PROBLEM_ACCOUNT. Если соединение возвращает какие-либо результаты, значит, данный клиент присутствует в таблице PROBLEM_ACCOUNT. В этом случае следует выполнить откат транзакции и вывести предупреждение.
Данный триггер использует системную функцию @@rowCount, которая возвращает количество строк, обработанных предыдущим оператором T/SQL. Значение этой функции необходимо проверять сразу после выполнения оператора или сохранять в переменной для последующей проверки. Здесь триггер выполняет эту проверку сразу.
Если @@rowCount равняется нулю, то данный клиент отсутствует в таблице PROBLEM_ACCOUNT и ничего делать не требуется. Если же @@rowCount имеет положительное значение, значит, информация о данном клиенте содержится в таблице PROBLEM_ACCOUNT, поэтому транзакцию необходимо отменить, а пользователю выдать предупреждающее сообщение.
Между прочим, в предыдущих двух процедурах вместо подсчета строк в переменной @rowCount можно было бы использовать функцию @@rowCount. В данном случае сделано по-другому, чтобы была возможность изучить альтернативные способы получения информации о количестве строк.
Чтобы проверить, что триггер работает корректно, в таблицу PROBLEM_ACCOUNT была добавлена строка со значениями 'Nicole Not Pay', '213', '555-1234'. После этого был выполнен оператор INSERT, показанный на рис. 4.7. Как вы можете видеть, пользователю было выдано соответствующее сообщение. Была также проверена таблица CUSTOMER, чтобы убедиться, что вставка не была выполнена (эти действия не показаны).
Рис. 4.7. Тестирование триггера CheckForProblemAccount
Использование таблицы допустимых (или недопустимых) значений обеспечивает большую гибкость и динамичность, чем указание этих значений в ограничениях CHECK. Рассмотрим, например, ограничение CHECK, определяющее допустимые значения столбца Nationality в таблице ARTIST. Если менеджер галереи хочет расширить географию художников, с произведениями которых работает галерея, ему придется изменить ограничение с помощью оператора ALTER TABLE. В реальности для этого менеджеру пришлось бы нанимать консультанта.
Более удачный подход — свести все допустимые значения столбца Nationality в таблицу. Такая таблица могла бы называться, например, ALLOWED_NATIONALITY (допустимая национальность). Затем можно написать триггер, подобный изображенному в листинге 6, который при вставке и обновлении ограничивал бы возможные значения столбца Nationality множеством значений в таблице ALLOWED_ NATIONALITY. Если владелец галереи захочет изменить список национальностей, ему достаточно будет добавить или удалить строки из таблицы ALLOWED_NATIONALITY.
4.7.2 Триггер, присваивающий значения по умолчанию. Триггеры можно использовать для присвоения начальных значений, расчет которых слишком сложен, чтобы его можно было выполнить с помощью ограничения DEFAULT в определении столбца. Например, согласно ценовой политике галереи View Ridge, запрашиваемая цена произведения, которая устанавливается по умолчанию, зависит от того, появлялось ли это произведение в галерее в прошлом. Если нет, она устанавливается равной удвоенной стоимости приобретения. Если произведение уже появлялось в галерее прежде, запрашиваемая цена устанавливается равной большему из двух значений – удвоенной стоимости приобретения или стоимости приобретения плюс средняя чистая прибыль от продажи произведения в прошлом.
Завершающий триггер, показанный в листинге 7, реализует эту ценовую политику. Сначала из таблицы inserted получаются значения WorkID и AcquisitionPrice. Затем выполняется оператор SELECT FROM dbo.TRANS для подсчета количества строк с данным значением WorkID. Переменная @countPriorRows устанавливается равной @@rowCount минус единица, поскольку данный триггер является завершающим, и новая строка будет уже добавлена в базу данных. Следовательно, количество удовлетворяющих условию строк в таблице TRANS, которое было в базе данных до выполнения вставки, равно (@@rowCount - 1).
Листинг 7. Триггер SetAskingPrice
CREATE TRIGGER [SetAskingPrice] ON [dbo].[TRANS]
FOR INSERT
AS
DECLARE
@WorkID as int,
@TransID as int,
@aqPrice as numeric(8, 2),
@SumNetPrice numeric(8, 2),
@countPriorRows as int,
@newPrice as numeric(8, 2)
/* Получаем новые значения WorkID и AcquisitionPrice */
SELECT @WorkID = WorkID, @aqPrice = AcquisitionPrice, @TransID = TransactionID
FROM inserted
/* Проверяем, не появлялось ли произведение в галерее ранее */
SELECT *
FROM dbo.TRANS T
WHERE Т.WorkID = @WorkID
/* Это завершающий триггер, поэтому число строк будет включать только что вставленную строку. Вычитаем единицу, чтобы учесть это. */
Set @countPriorRows = @rowCount - 1
/* Устанавливаем newPrice и при необходимости корректируем ее значение*/
Set @newPrice = 2*@aqPrice
If @countPriorRows > 0
BEGIN
/* Функция AVG не подходит, поскольку в делителе будет учтена текущая строка. Используем @@rowCount - 1 из предыдущих вычислений. */
SELECT @sumNetPrice = SUM(SalesPrice)
FROM dbo.TRANS T
WHERE T.WorkID = @WorkID
GROUP BY T.WorkID
/* Если необходимо, корректируем newPrice */
If @newPrice < (@sumNetPrice/@countPriorRows) + @aqPrice
Set @newPrice = (@sumNetPrice/@countPriorRows) + @aqPrice
END
/* Теперь записываем в таблицу TRANS новое значение AskingPrice */
UPDATE dbo.TRANS
SET AskingPrice = @newPrice
WHERE TransactionID = @TransID
Далее переменная @newPrice устанавливается равной удвоенному значению AcquisitionPrice; при необходимости ее значение будет скорректировано ниже. Если @countPriorRows больше нуля, то в таблице TRANS до выполнения вставки уже были записи о транзакциях с данным произведением, и с помощью представления ArtistWorkNet вычисляется суммарная чистая прибыль от продажи этого произведения. Встроенную функцию усреднения использовать нельзя, поскольку она будет вычислять среднее на базе значения @@rowCount, а не @countPriorRows. Затем текущее значение @newPrice сравнивается с суммой средней чистой прибыли и стоимости приобретения. Если оно оказывается меньше, переменная @newPrice устанавливается равной сумме средней чистой прибыли и стоимости приобретения. Наконец, только что вставленная строка обновляется, в процессе чего в столбец AskingPrice записывается вычисленное значение @newPrice.
Для тестирования этого триггера необходимо проверить следующие три случая:
произведение не появлялось в галерее ранее;
произведение появлялось в галерее прежде, но сумма средней чистой прибыли и стоимости приобретения меньше или равна удвоенной стоимости приобретения;
произведение появлялось в галерее прежде, но сумма средней чистой прибыли и стоимости приобретения превышает удвоенную стоимость приобретения.
Этот триггер выполняет для галереи полезную функцию, избавляя персонал от значительного количества ручной работы, а также повышая точность результатов.
4.7.3 Триггер, реализующий процедуру обеспечения ссылочной целостности. Согласно рис. 4.2, таблица WORK в базе данных View Ridge имеет обязательного потомка в виде таблицы TRANSACTION. Это означает, что всякий раз, когда в таблицу WORK вставляется строка, необходимо вставить соответствующую ей строку в таблицу TRANSACTION.
Реализовать такие ограничения ссылочной целостности непросто. В листингах 8, а и б, показана пара триггеров, создающих строку в таблице TRANS при вставке в таблицу WORK и удаляющих ее, когда (и если) приложение самостоятельно вставляет строку в таблице TRANS после создания строки в таблице WORK. Триггер в листинге 8, а, создает строку в таблице TRANS по умолчанию. Сначала он получает новое значение WorkID из псевдотаблицы inserted, а затем проверяет, нет ли доступной строки в таблице TRANS. Такой строки быть не должно, поскольку транзакция, создавшая строку в таблице WORK, еще не получила шанса вставить соответствующую строку в таблицу TRANS. После этого, если @@rowCount равняется нулю, триггер создает в таблице TRANS строку по умолчанию.
Листинг 8, а. Триггеры для реализации ограничения обязательного потомка: триггер EnforceTransChild
CREATE TRIGGER EnforceTransChild ON [dbo].[WORK]
FOR INSERT
AS
DECLARE @newWorkID as int
/* Получаем новый WorkID */
SELECT @newWorkID = WorkID
FROM inserted
/* Проверяем, имеется ли доступная строка в таблице TRANS */
SELECT *
FROM dbo.TRANS
WHERE WorkID=@newWorkID AND CustomerID IS NULL
/* Если подходящей строки не найдено, вставляем строку */
If @@rowCount = 0
INSERT INTO dbo.TRANS (DateAcquired, WorkID) VALUES (GetDate(), @newWorkID)
Триггер в листинге 8, б, запускается при вставке в таблицу TRANS. Назначение данного триггера – удалять из этой таблицы созданную по умолчанию строку, если приложение собирается создать такую строку самостоятельно. Если приложение не запросит вставку такой строки, триггер запущен не будет, и данные о транзакции для нового произведения будут записаны в строку, созданную предыдущим триггером.
Листинг 8, б. Триггеры для реализации ограничения обязательного потомка: триггер RemoveDupTrans
CREATE TRIGGER RemoveDupTrans ON [dbo].[TRANS]
FOR INSERT
AS
DECLARE @WorkID as int, @TransID as int
SELECT @WorkID = WorkID, @TransID = TransactionID
FROM inserted
/* Удаляем прочие доступные строки из таблицы TRANS */
DELETE FROM dbo.TRANS
WHERE WorkID =@WorkID AND CustomerID IS NULL AND TransactionID <> @TransID
