Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Базы данных. Практический курс. В 2 частях. Ч.1. Объектно-реляционные базы данных на примере PostgreSQL 9.5. Учебное пособие.pdf
X
- •Введение
- •Контрольные вопросы
- •Создание базы данных
- •Создание таблиц
- •Создание последовательностей
- •Ввод данных
- •Оператор CREATE TABLE
- •Оператор ALTER TABLE
- •Операторы DROP TABLE и DROP
- •Создание индексов
- •Контрольные вопросы
- •Средства определения данных языка SQL
- •Средства запроса данных языка SQL
- •Чтение заданных столбцов из одиночной таблицы
- •Чтение заданных строк из одиночной таблицы
- •Чтение заданных строк и столбцов из одиночной таблицы
- •Сортировка результатов
- •Агрегатные функции и группировка
- •Оконные функции
- •Чтение данных из нескольких таблиц с применением вложенных запросов
- •Чтение данных из нескольких таблиц с помощью операции соединения
- •Объединения
- •Средства модификации данных языка SQL
- •Вставка данных, оператор INSERT
- •Изменение данных, оператор UPDATE
- •Удаление данных, оператор DELETE
- •Транзакции
- •SQL-представления
- •Использование представлений для скрытия столбцов и строк
- •Использование представлений для отображения вычисляемых столбцов
- •Использование представлений для скрытия сложного синтаксиса
- •SQL-запросы в прикладных программах
- •Хранимые процедуры
- •Триггеры
- •Использование триггеров для проверки допустимости вводимых данных
- •Триггер, обновляющий представление
- •Триггер, обеспечивающий ссылочную целостность
- •Модуль TABLEFUNC
- •Словарь метаданных
- •Контрольные вопросы
- •Функция ROW_NUMBER
- •Функция COALESCE
- •Числовые функции
- •Тригонометрические функции
- •Контрольные вопросы
- •Заключение
- •Библиографический список

Глава 4. Применение SQL 4.4. Триггеры
пользователю об ошибке, разработчик должен воспользоваться выходным параметром или сгенерировать исключение.
Оставшаяся часть процедуры в листинге 4.1 вставляет данные
о новом клиенте и затем перебирает всех художников выбранной
национальности. Обратите внимание на использование специальной конструкции PL/pgSQL FOR artist IN artistcursor LOOP. Эта
конструкция выполняет несколько задач. Прежде всего, она открывает курсор и считывает первую строку. Затем она последовательно
обрабатывает все строки под курсором и по окончании обработки
передает управление следующему после FOR оператору. Заметьте
также, что обращение к столбцу ArtistID текущей строки происходит с использованием синтаксиса artist.ArtistID, где artist – это
имя переменной цикла FOR, а не курсора.
После того как процедура написана, ее необходимо скомпилировать и сохранить в базе данных. Если текст процедуры сохранен
в файле, то процедура будет скомпилирована и сохранена в базе
данных автоматически после ввода команды psql
\i путь< до sql файла с текстом процедуры>
Если вы что-то ввели неправильно, у вас могут возникнуть
ошибки компиляции. Если синтаксических ошибок не было, вы
получите сообщение «CREATE FUNCTION» (Процедура создана).
Теперь вы можете вызвать эту процедуру с помощью запроса.
Например:
SELECT customer_insert(
’Michael Bench’, ’203’, ’555-2014’, ’US’);
4.4 Триггеры
Триггер (trigger) – это специальная программа, назначаемая
таблице или представлению. Триггер вызывается СУБД, когда пользователь запрашивает вставку, обновление или удаление строки из
таблицы или представления, которому принадлежит данный триггер. С каждым триггером связана собственная триггерная функция.
PostgreSQL поддерживает три вида триггеров: предваряющие
(BEFORE), замещающие (INSTEAD OF) и завершающие (AFTER). Как и
логично было бы ожидать, предваряющие триггеры вызываются
81

4.4. Триггеры Глава 4. Применение SQL
перед обработкой запроса на вставку, обновление, удаление или
опустошение, замещающие – вместо него, а завершающие – после
обработки запроса. Всего имеется двенадцать возможных типов
триггеров: предваряющий триггер вставки, обновления, удаления
и опустошения, замещающий триггер вставки, обновления, удаления и опустошения и завершающий триггер вставки, обновления,
удаления и опустошения.
При запуске триггера СУБД предоставляет доступ к вставляемым, обновляемым или удаляемым из тела триггера данным. В случае вставки триггеру доступны значения столбцов новой строки, в
случае удаления – значения столбцов удаляемой строки, а в случае
обновления триггер может оперировать как новыми, так и старыми
значениями.
В PostgreSQL для получения нового состояния строки необходимо использовать переменную NEW. Так, например, при вставке в
таблицу CUSTOMER переменная NEW.Name содержит значение столбца
Name вставляемой строки. В случае обновления переменная NEW.Name
содержит значение, которое будет иметь столбец Name после выполнения запроса. Аналогичным образом, для получения старых значений необходимо использовать переменную OLD. Например, при
удалении переменная OLD.Name содержит значение столбца Name
удаляемой строки. В случае обновления эта переменная содержит
значение столбца Name до выполнения запроса.
Триггеры имеют множество применений. Основные из них:
проверка допустимости вводимых данных, присваивание значений
по умолчанию, обновление представлений и обеспечение ссылочной целостности.
4.4.1 Использование триггеров для проверки допустимости вводимых данных
Предположим, у галереи (см. описание практического примера в гл. 2) есть правило, что ни одна работа не может быть
продана менее чем за 90% от запрошенной цены. Чтобы обеспечить
выполнение этого правила, можно написать триггер обновления
для таблицы TRANSACTIO N, сравнивающий значения AskingPrice и
SalesPrice. Если правило нарушается, в столбец SalesPrice ставит-
82

Глава 4. Применение SQL 4.4. Триггеры
ся значение столбца AskingPrice, а в столбец AskingPrice исходное
значение.
Можно использовать две стратегии. Одна заключается в том,
чтобы написать предваряющий триггер, который проверяет и переустанавливает, если необходимо, значение столбца SalesPrice
до выполнения обновления. Вторая стратегия – написать завершающий триггер, проверяющий и переписывающий строку таблицы
TRANSACTION после обновления.
Листинг 4.2 соответствует второй стратегии. Если есть необходимость в комментариях, то они помещаются в скобки вида ’/* ... */’
либо перед текстом комментария ставится символ ’--’.
Листинг 4.2.
CREATE OR REPLACE FUNCTION sales_price_check()
RETURNS trigger AS $sales_price_check$
BEGIN
IF NEW.SalesPrice < 0.9 * OLD.AskingPrice THEN
NEW.SalesPrice = OLD.AskingPrice;
NEW.AskingPrice = OLD.AskingPrice;
END IF;
RETURN NEW;
END;
$sales_price_check$ LANGUAGE plpgsql;
CREATE TRIGGER sales_price_check
BEFORE UPDATE ON TRANSACTION
FOR EACH ROW EXECUTE PROCEDURE sales_price_check();
Логика работы триггера очевидна. Если новая продажная цена составляет менее 90% от запрашиваемой цены, продажная цена
устанавливается равной запрашиваемой цене. Обратите внимание,
что новая продажная цена сравнивается со старой запрашиваемой
ценой; в противном случае можно было бы, изменив обе цены,
успешно совершить обновление, нарушающее данное ограничение.
На тот случай, если именно так и произошло, столбец AskingPrice
в операторе UPDATE устанавливается равным OLD.AskingPrice.
83

4.4. Триггеры Глава 4. Применение SQL
4.4.2 Использование триггеров для присвоения
значений по умолчанию
Столбцам таблицы могут присваиваться значения по умолчанию с помощью квалификатора DEFAULT. В качестве таких значений
можно задавать константы или результаты вычисления простых выражений. Если же задание значения по умолчанию требует более
сложной логики, необходимо использовать триггер.
В случае нашего примера предположим, что у галереи имеется правило, согласно которому запрашиваемая цена произведения
устанавливается равной удвоенной стоимости его приобретения
или сумме общей стоимости приобретения и чистой выручки от
продажи этого произведения в прошлом. Это правило реализуется
с помощью завершающего триггера, показанного в листинге 4.3.
Представление, которое используется в рассматриваемом триггере, имеет следующий вид:
CREATE VIEW ArtistWorkNet AS
SELECT W.WorkID, Name, Title, Copy, AcquisitionPrice,
SalesPrice, (SalesPrice - AcquisitionPrice) AS NetPrice
FROM TRANSACTION T
JOIN WORK W
ON T.WorkID = W.WorkID
JOIN ARTIST A
ON W.ArtistID = A.ArtistID;
Листинг 4.3.
CREATE OR REPLACE FUNCTION set_asking_price()
RETURNS trigger AS $set_asking_price$
DECLARE
avgNetPrice numeric(8,2);
newPrice numeric(8,2);
rowcount integer;
BEGIN
SELECT Count(*) INTO rowcount
FROM TRANSACTION
WHERE WorkID = NEW.WorkID;
IF rowcount = 0 THEN
NEW.AskingPrice = 2 * (NEW.AcquisitionPrice);
ELSE
SELECT AVG(NetPrice) INTO avgNetPrice
84

Глава 4. Применение SQL 4.4. Триггеры
FROM ArtistWorkNet AW
WHERE AW.WorkID = NEW.WorkID
GROUP BY AW.WorkID;
newPrice = avgNetPrice + NEW.AcquisitionPrice;
IF newPrice > 2 * (NEW.AcquisitionPrice) THEN
NEW.AskingPrice = newPrice;
ELSE
NEW.AskingPrice = 2 * (NEW.AcquisitionPrice);
END IF;
END IF;
RETURN NEW;
END;
$set_asking_price$ LANGUAGE plpgsql;
CREATE TRIGGER set_asking_price
BEFORE INSERT ON TRANSACTION
FOR EACH ROW EXECUTE PROCEDURE set_asking_price();
Триггер сначала подсчитывает количество строк в таблице
TRANSACTION, в которых значение WorkID равно NEW.WorkID. Поскольку
это предваряющий триггер, произведение еще не добавлено в базу
данных, и количество будет равным нулю, если это произведение
не появлялось в галерее ранее. В этом случае NEW.AskingPrice устанавливается равным удвоенному значению AcquisitionPrice.
Если произведение появлялось в галерее в прошлом, рассчитывается средняя чистая прибыль от его продажи с помощью
представления ArtistWorkNet. После этого вычисляется переменная newPrice как сумма средней чистой прибыли и стоимости приобретения. Наконец, NEW.AskingPrice присваивается большее из двух
значений – newPrice или удвоенное значение AcquisitionPrice. Так
как триггер предваряющий, для усреднения можно использовать
встроенную функцию AVG: новая строка еще не добавлена в таблицу
WORK, поэтому она не будет учтена при расчете среднего значения.
Следует обратить внимание на один нюанс: если в какой-либо
из строк представления ArtistWorkNet столбец SalesPrice или
AcquisitionPrice является пустым, это может вызвать проблемы
при вычислениях в триггере.
85

4.4. Триггеры Глава 4. Применение SQL
Рассмотренный триггер выполняет полезную функцию, избавляя персонал галереи от значительного количества ручной работы,
а также повышая точность результатов.
4.4.3 Триггер, обновляющий представление
Обновление представлений в ряде случаев может оказаться
затруднительным. Одна из таких проблем касается представлений,
созданных при помощи операции соединения: как правило, СУБД
не способна обновлять таблицы, лежащие в основе таких представлений. Однако, зная специфику конкретного приложения, можно
определить, как следует интерпретировать запрос на обновление
соединенного представления.
Рассмотрим представление CustomerInterests:
CREATE VIEW CustomerInterests AS
SELECT C.Name AS Customer, A.Name AS Artist
FROM CUSTOMER C
JOIN CUSTOMER_ARTIST_INT CI
ON C.CustomerID = CI.CustomerID
JOIN ARTIST A
ON CI.ArtistID = A.ArtistID;
Данное представление содержит строки таблиц CUSTOMER
и ARTIST, соединенные через таблицу пересечения. Столбцу
CUSTOMER.Name дан псевдоним Customer, а столбцу ARTIST.Name –
Artist.
Запрос на изменение имени клиента в представлении
CustomerInterests можно интерпретировать как запрос на
изменение столбца Name в таблице CUSTOMER. Такой запрос, однако,
может быть обработан лишь в том случае, если это имя является
уникальным в таблице CUSTOMER. В противном случае невозможно
будет определить, какую из строк следует обновлять.
В листинге 4.4 приведен текст замещающего триггера, который обновляет имя клиента, если это имя является уникальным
в базе данных. Вместо того чтобы подсчитывать количество строк
с данным именем клиента и выполнять обновление только в том
случае, если такая строка всего одна, триггер обусловливает обновление ключевым словом NOT EXISTS. Такая конструкция триггера
86

Глава 4. Применение SQL 4.4. Триггеры
позволяет PostgreSQL оптимизировать SQL-запрос и приводит к
лучшей производительности.
Листинг 4.4.
CREATE OR REPLACE FUNCTION customer_interests_update()
RETURNS trigger AS $customer_interests_update$
BEGIN
UPDATE CUSTOMER C1
SET Name = NEW.Customer
WHERE C1.Name = OLD.Customer
AND NOT EXISTS
(
SELECT *
FROM CUSTOMER C2
WHERE C2.Name = C1.Name
AND C2.CustomerID <> C1.CustomerID
);
RETURN NEW;
END;
$customer_interests_update$ LANGUAGE plpgsql;
CREATE TRIGGER customer_interests_update
INSTEAD OF UPDATE ON CustomerInterests
FOR EACH ROW EXECUTE PROCEDURE customer_interests_update();
4.4.4 Триггер, обеспечивающий ссылочную целостность
Согласно рис. 2.1, таблица WORK рассматриваемого в пособии
примера имеет обязательного потомка в таблице TRANSACTION. Это
означает, что всякий раз, когда в таблицу WORK вставляется строка, необходимо вставить соответствующую ей строку в таблицу
TRANSACTION.
Реализовать такие ограничения ссылочной целостности
непросто. Первое, что приходит в голову, – это создать
завершающий триггер для таблицы WORK, который бы проверял,
что приложение создало требуемую строку в таблице TRANSACTION.
Однако любой такой триггер будет запускаться непосредственно
после вставки строки в таблицу WORK, то есть до того как
приложение получит шанс вставить новую строку в таблицу
87

4.4. Триггеры Глава 4. Применение SQL
TRANSACTION. Таким образом, триггер не сможет увидеть строку,
которую собирается вставить приложение.
Другое решение – потребовать от приложения создать строку
в таблице TRANSACTION, перед тем как вставить новую строку в
таблицу WORK. Но в этом случае мы переопределяем связь так, что
теперь строка в таблице TRANSACTION требует строки в таблице WORK.
Поэтому СУБД откажется вставлять в таблицу TRANSACTION строку,
не имеющую родителя, и сделает она это еще до того, как будет
вызван какой-либо триггер таблицы TRANSACTION. Таким образом,
триггер, определенный для таблицы TRANSACTION, никогда не получит возможности создать требуемую строку в таблице WORK.
Одно возможное, хотя и не очень красивое, решение заключается в том, чтобы создать пару триггеров. Первый из них будет
создавать строку в таблице TRANSACTION при вставке строки в таблицу WORK. Второй триггер будет удалять строку, вставленную по
умолчанию, когда приложение захочет вставить строку в таблицу
TRANSACTION. При таком подходе, если приложение не создаст требу-
емой строки, будет использоваться строка, вставленная триггером.
Если приложение создаст новую строку в таблице TRANSACTION, то
вставленная по умолчанию строка будет предварительно удалена.
Эта пара триггеров представлена в листингах 4.5 и 4.6. Первый триггер (листинг 4.5) создает строку по умолчанию в таблице
TRANSACTION. Сначала он проверяет, нет ли в этой таблице существу-
ющей строки с таким WorkID; если нет, он вставляет новую строку.
Второй триггер (листинг 4.6) удаляет строку, созданную первым
триггером, когда пользователь вставляет соответствующую строку
в таблицу TRANSACTION.
Листинг 4.5.
CREATE OR REPLACE FUNCTION enforce_trans_child()
RETURNS trigger AS $enforce_trans_child$
DECLARE
rowcount int;
NewID int;
BEGIN
NewID = NEW.WorkID;
SELECT Count(*) INTO rowcount
88

Глава 4. Применение SQL 4.4. Триггеры
FROM TRANSACTION
WHERE WorkID = NewID
AND CustomerID IS NULL;
IF rowcount = 0 THEN
INSERT INTO TRANSACTION
(TransactionID, DateAcquired, WorkID)
VALUES (nextval(’seq_transaction’), NOW(), NewID);
END IF;
RETURN NEW;
END;
$enforce_trans_child$ LANGUAGE plpgsql;
CREATE TRIGGER enforce_trans_child
AFTER INSERT ON WORK
FOR EACH ROW EXECUTE PROCEDURE enforce_trans_child();
Листинг 4.6.
CREATE OR REPLACE FUNCTION remove_dup_trans()
RETURNS trigger AS $remove_dup_trans$
BEGIN
DELETE FROM TRANSACTION
WHERE WorkID = NEW.WorkID
AND CustomerID IS NULL
AND TransactionID <> NEW.TransactionID;
RETURN NEW;
END;
$remove_dup_trans$ LANGUAGE plpgsql;
CREATE TRIGGER remove_dup_trans
BEFORE INSERT ON TRANSACTION
FOR EACH ROW EXECUTE PROCEDURE remove_dup_trans();
Тем не менее это решение нельзя назвать удовлетворительным. Еще одна альтернатива, более сложная в реализации, но эстетически более привлекательная, – потребовать от приложения,
чтобы новые произведения добавлялись в базу данных через представление, содержащее все необходимые данные для таблицы WORK
и TRANSACTION. В этом случае вставку данных из представления
WorkTransaction в таблицы WORK и TRANSACTION осуществлял бы триг-
гер, а прямую вставку строк в эти таблицы следовало бы запретить.
89

4.5. Правила Глава 4. Применение SQL
4.5 Правила
Правила в PostgreSQL используются для изменения дерева запроса. Перед началом выполнения запроса PostgreSQL формирует
так называемое абстрактное синтаксическое дерево данного запроса, в котором ветви сопоставлены с SQL-операторами, а листья –
с соответствующими значениями.
Рассмотрим пример использования правил для решения задачи обновления представления (см. пункт 4.4.3). В данном случае
правило работает аналогично триггеру из пункта 4.4.3 и «объясняет» PostgreSQL, каким образом применять оператор UPDATE к представлению CustomerInterests. Правила в PostgreSQL создаются с
помощью оператора CREATE RULE (см. листинг 4.7).
Листинг 4.7.
CREATE RULE update_customer_interests AS
ON UPDATE TO CustomerInterests DO INSTEAD
UPDATE CUSTOMER C1
SET Name = NEW.Customer
WHERE C1.Name = OLD.Customer
AND NOT EXISTS
(
SELECT *
FROM CUSTOMER C2
WHERE C2.Name = C1.Name
AND C2.CustomerID <> C1.CustomerID
);
4.6 Модуль TABLEFUNC
Модуль TABLEFUNC содержит методы для формирования сводных таблиц.
Рассмотрим пример формирования сводной таблицы для отслеживания количества продаж картин в каждом месяце по годам:
SELECT YEAR,
SUM(JAN) AS JAN, SUM(FEB) AS FEB, SUM(MAR) AS MAR,
SUM(APR) AS APR, SUM(MAY) AS MAY, SUM(JUN) AS JUN,
SUM(JUL) AS JUL, SUM(AUG) AS AUG, SUM(SEP) AS SEP,
SUM(OCT) AS OCT, SUM(NOV) AS NOV, SUM(DEC) AS DEC
FROM crosstab(
90
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
