Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
10
Добавлен:
15.01.2021
Размер:
10.35 Mб
Скачать

Пример 32: обеспечение консистентности данных

MS SQL

Решение 4.1.2.b (триггеры для таблицы m2m_books_genres) (продолжение)

38-- Реакция на удаление связи между книгами и жанрами:

39CREATE TRIGGER [g_has_books_on_m2m_b_g_del]

40ON [m2m_books_genres]

41AFTER DELETE

42AS

43UPDATE [genres]

44

 

SET

[g_books] = [g_books] - [g_old_books]

45

 

FROM

[genres]

 

46

 

 

JOIN (SELECT

[g_id],

47

 

 

 

COUNT([b_id]) AS [g_old_books]

48

 

 

FROM

[deleted]

49

 

 

GROUP

BY [g_id]) AS [prepared_data]

50ON [genres].[g_id] = [prepared_data].[g_id];

51GO

Логика UPDATE-триггера чуть более сложная. Чтобы не выполнять два отдельных обновления таблицы genres, мы сначала в строках 26-34 запроса получаем «сводную таблицу» по удалённым и добавленным связям между книгами и жанрами. Эта таблица в некоторой гипотетической ситуации может выглядеть так:

g_id

delta

3

4

5

1

1

-3

3

-6

6

2

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

g_id

delta

3

-2

5

1

1

-3

6

2

В строке 22 запроса эти данные используются для изменения значения счётчика связей между жанрами и книгами.

Проверить работоспособность полученного решения можно с помощью следующих запросов (которые подробно рассмотрены в решении для MySQL).

MS SQL Решение 4.1.2.b (проверка работоспособности)

1-- Добавление двух связей к жанру «Наука» (идентификатор жанра равен 4):

2INSERT INTO [m2m_books_genres]

3

 

 

([b_id],

4

 

 

[g_id])

5

 

VALUES

(1,

4),

6

 

 

(2,

4);

 

 

 

 

 

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 310/545

Пример 32: обеспечение консистентности данных

MS SQL Решение 4.1.2.b (проверка работоспособности) (продолжение)

7-- Изменение в добавленных связях значения идентификаторов книг,

8-- без изменения значения идентификаторов жанров:

9UPDATE [m2m_books_genres]

10

 

SET

[b_id] = 3

11WHERE [b_id] = 1

12AND [g_id] = 4;

13

 

 

 

14

 

UPDATE

[m2m_books_genres]

15

 

SET

[b_id] = 4

16WHERE [b_id] = 2

17AND [g_id] = 4;

18

19-- Изменение в добавленных связях значения идентификаторов жанров,

20-- без изменения значения идентификаторов книг:

21UPDATE [m2m_books_genres]

22

 

SET

[g_id] = 5

23WHERE [b_id] = 3

24AND [g_id] = 4;

25

 

 

 

26

 

UPDATE

[m2m_books_genres]

27

 

SET

[g_id] = 5

28WHERE [b_id] = 4

29AND [g_id] = 4;

30

31-- Изменение в добавленных связях значения идентификаторов жанров,

32-- и идентификаторов книг одновременно:

33UPDATE [m2m_books_genres]

34

 

SET

[b_id] = 1,

 

 

 

 

35[g_id] = 4

36WHERE [b_id] = 3

37AND [g_id] = 5;

38

 

 

 

39

 

UPDATE

[m2m_books_genres]

40

 

SET

[b_id] = 2,

41[g_id] = 4

42WHERE [b_id] = 4

43AND [g_id] = 5;

44

45-- Удаление ранее созданных связей:

46DELETE FROM [m2m_books_genres]

47WHERE [b_id] = 1

48AND [g_id] = 4;

49

50DELETE FROM [m2m_books_genres]

51WHERE [b_id] = 2

52AND [g_id] = 4;

53

54-- Удаление книг с идентификаторами 1 и 2 (обе эти книги одновременно

55-- относятся к жанрам «Поэзия» и «Классика»):

56DELETE FROM [books]

57WHERE [b_id] IN (1, 2);

Итак, решение для MySQL завершено и проверено.

Переходим к решению для Oracle. Т.к. данная СУБД не поддерживает псевдотаблицы inserted и deleted, мы будем опираться на логику решения для MySQL. Все соответствующие подробности этого решения уже описаны выше, потому здесь будет представлен только SQL-код.

Также отметим, что поскольку в Oracle триггеры активируются каскадными операциями, в данном решении (в отличие от решения для MySQL) не потребуется создавать DELETE-триггер на таблице books.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 311/545

Пример 32: обеспечение консистентности данных

Oracle Решение 4.1.2.b (модификация таблицы и инициализация данных)

1-- Модификация таблицы:

2ALTER TABLE "genres"

3ADD ("g_books" NUMBER(10) DEFAULT 0 NOT NULL);

4

5-- Инициализация данных:

6UPDATE "genres" "outer"

 

7

 

SET

"g_books" =

 

8

 

 

 

NVL((SELECT COUNT("b_id") AS "g_has_books"

 

9

 

 

 

FROM "m2m_books_genres"

 

10

 

 

 

WHERE "outer"."g_id" = "g_id"

 

11

 

 

 

GROUP BY "g_id"), 0);

 

 

 

 

 

 

Oracle

 

 

Решение 4.1.2.b (триггеры для таблицы m2m_books_genres)

1-- Реакция на добавление связи между книгами и жанрами:

2CREATE TRIGGER "g_has_bks_on_m2m_b_g_ins"

3AFTER INSERT

4ON "m2m_books_genres"

5FOR EACH ROW

6BEGIN

7UPDATE "genres"

8

 

SET

"g_books" = "g_books" + 1

 

 

 

 

9WHERE "g_id" = :new."g_id";

10END;

11

12-- Реакция на обновление связи между книгами и жанрами:

13CREATE TRIGGER "g_has_bks_on_m2m_b_g_upd"

14AFTER UPDATE

15ON "m2m_books_genres"

16FOR EACH ROW

17BEGIN

18UPDATE "genres"

19

 

SET

"g_books" = "g_books" + 1

20WHERE "g_id" = :new."g_id";

21UPDATE "genres"

22

 

SET

"g_books" = "g_books" - 1

23WHERE "g_id" = :old."g_id";

24END;

25

26-- Реакция на удаление связи между книгами и жанрами:

27CREATE TRIGGER "g_has_bks_on_m2m_b_g_del"

28AFTER DELETE

29ON "m2m_books_genres"

30FOR EACH ROW

31BEGIN

32UPDATE "genres"

33

 

SET

"g_books" = "g_books" - 1

34WHERE "g_id" = :old."g_id";

35END;

Проверить работоспособность полученного решения можно с помощью следующих запросов (которые подробно рассмотрены в решении для MySQL).

Oracle Решение 4.1.2.b (проверка работоспособности)

1-- Добавление двух связей к жанру «Наука» (идентификатор жанра равен 4):

2INSERT INTO "m2m_books_genres"

3

 

 

("b_id", "g_id")

4

 

VALUES

(1, 4);

5

 

 

 

6

 

INSERT INTO "m2m_books_genres"

7

 

 

("b_id", "g_id")

8

 

VALUES

(2, 4);

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 312/545

Пример 32: обеспечение консистентности данных

Oracle Решение 4.1.2.b (проверка работоспособности)

9-- Изменение в добавленных связях значения идентификаторов книг,

10-- без изменения значения идентификаторов жанров:

11UPDATE "m2m_books_genres"

12

 

SET

"b_id" = 3

13WHERE "b_id" = 1

14AND "g_id" = 4;

15

 

 

 

16

 

UPDATE

"m2m_books_genres"

17

 

SET

"b_id" = 4

18WHERE "b_id" = 2

19AND "g_id" = 4;

20

21-- Изменение в добавленных связях значения идентификаторов жанров,

22-- без изменения значения идентификаторов книг:

23UPDATE "m2m_books_genres"

24

 

SET

"g_id" = 5

25WHERE "b_id" = 3

26AND "g_id" = 4;

27

 

 

 

28

 

UPDATE

"m2m_books_genres"

29

 

SET

"g_id" = 5

30WHERE "b_id" = 4

31AND "g_id" = 4;

32

33-- Изменение в добавленных связях значения идентификаторов жанров,

34-- и идентификаторов книг одновременно:

35UPDATE "m2m_books_genres"

36

 

SET

"b_id" = 1,

 

 

 

 

37"g_id" = 4

38WHERE "b_id" = 3

39AND "g_id" = 5;

40

 

 

 

41

 

UPDATE

"m2m_books_genres"

42

 

SET

"b_id" = 2,

43"g_id" = 4

44WHERE "b_id" = 4

45AND "g_id" = 5;

46

47-- Удаление ранее созданных связей:

48DELETE FROM "m2m_books_genres"

49WHERE "b_id" = 1

50AND "g_id" = 4;

51

52DELETE FROM "m2m_books_genres"

53WHERE "b_id" = 2

54AND "g_id" = 4;

55

56-- Удаление книг с идентификаторами 1 и 2 (обе эти книги одновременно

57-- относятся к жанрам «Поэзия» и «Классика»):

58DELETE FROM "books"

59WHERE "b_id" IN (1, 2);

На этом решение данной задачи завершено.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 313/545

Пример 32: обеспечение консистентности данных

Задание 4.1.2.TSK.A: доработать триггеры из решений{292}, {305} задач 4.1.2.a{292} и 4.1.2.b{292} таким образом, чтобы ни при каких манипуляциях с данными значения полей s_books (в таблице subscribers) и g_books (в таблице genres) не могли оказаться отрицательными.

Задание 4.1.2.TSK.B: модифицировать схему базы данных «Библиотека» таким образом, чтобы таблица subscribers хранила информацию о том, сколько раз читатель брал в библиотеке книги (этот счётчик должен инкрементироваться каждый раз, когда читателю выдаётся книга; уменьшение значения этого счётчика не предусмотрено).

Задание 4.1.2.TSK.C: оптимизировать код UPDATE-триггера из решения{292} задачи 4.1.2.a{292} для MS SQL Server так, чтобы выполнялась одна операция обновления таблицы subscribers (а не две отдельных операции, как это реализовано сейчас).

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 314/545

Пример 33: контроль операций модификации данных

4.2.Контроль операций с данными с использованием триггеров

4.2.1. Пример 33: контроль операций модификации данных

Задача 4.2.1.a{315}: создать триггер, не позволяющий добавить в базу данных информацию о выдаче книги, если выполняется хотя бы одно из условий:

дата выдачи находится в будущем;

дата возврата находится в прошлом (только для вставки данных);

дата возврата меньше даты выдачи.

Задача 4.2.1.b{328}: создать триггер, не позволяющий выдать книгу читателю, у которого на руках находится десять и более книг.

Задача 4.2.1.c{335}: создать триггер, не позволяющий изменять значение поля sb_is_active таблицы subscriptions со значения N на значение Y.

Ожидаемый результат 4.2.1.a.

При попытке внести в базу данных изменения, противоречащие условию задачи, операция (транзакция) должна быть отменена. Также должно быть выведено сообщение об ошибке, наглядно поясняющее суть проблемы, например:

“Date 2038.01.12 for subscription 145 activation is in the future”.

“Date 1983.01.12 for subscription 155 deactivation is in the past”.

“Date 2000.01.12 for subscription 165 deactivation is less than date for its activation (2015.01.12)”.

Ожидаемый результат 4.2.1.b.

При попытке внести в базу данных изменения, противоречащие условию задачи, операция (транзакция) должна быть отменена. Также должно быть выведено сообщение об ошибке, наглядно поясняющее суть проблемы, например: “Subscriber

Иванов И.И. (id = 1) already has 23 books out of 10 allowed.”

Ожидаемый результат 4.2.1.c.

При попытке внести в базу данных изменения, противоречащие условию задачи, операция (транзакция) должна быть отменена. Также должно быть выведено сообщение об ошибке, наглядно поясняющее суть проблемы, например: “It is prohibited to activate previously deactivated subscriptions (rule violated for subscriptions 34,

89, 12).”

Решение 4.2.1.a{315}.

Для решения данной задачи нам понадобятся только INSERT- и UPDATE- триггеры, т.к. в процессе удаления данных невозможно нарушить ни одно из контролируемых условий.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 315/545

Пример 33: контроль операций модификации данных

Код INSERT-триггера для MySQL выглядит следующим образом.

MySQL Решение 4.2.1.a (триггеры для таблицы subscriptions)

1 DELIMITER $$

2

3CREATE TRIGGER `subscriptions_control_ins`

4AFTER INSERT

5ON `subscriptions`

6FOR EACH ROW

7BEGIN

8

9-- Блокировка выдач книг с датой выдачи в будущем.

10IF NEW.`sb_start` > CURDATE()

11THEN

12SET @msg = CONCAT('Date ', NEW.`sb_start`, ' for subscription ',

13

NEW.`sb_id`, ' activation is in the future.');

14SIGNAL SQLSTATE '45001' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1001;

15END IF;

16

17-- Блокировка выдач книг с датой возврата в прошлом.

18IF NEW.`sb_finish` < CURDATE()

19THEN

20SET @msg = CONCAT('Date ', NEW.`sb_finish`, ' for subscription ',

21

 

NEW.`sb_id`, ' deactivation is in the past.');

22SIGNAL SQLSTATE '45002' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1002;

23END IF;

24

25-- Блокировка выдач книг с датой возврата меньшей, чем дата выдачи.

26IF NEW.`sb_finish` < NEW.`sb_start`

27THEN

28SET @msg = CONCAT('Date ', NEW.`sb_finish`, ' for subscription ',

29

 

NEW.`sb_id`,

30

 

' deactivation is less than the date for its activation (',

31

 

NEW.`sb_start`, ').');

 

 

 

32SIGNAL SQLSTATE '45003' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1003;

33END IF;

34

35END;

36$$

37

38DELIMITER ;

Сточки зрения функциональности в данном случае можно было бы использовать и BEFORE-триггер, но в случае с AFTER-триггером сообщение об ошибке по-

лучается более информативным, т.к. уже содержит в себе корректное значение автоинкрементируемого первичного ключа (в BEFORE-триггере это значение не определено, и потому в сообщении об ошибке превращается в 0).

Код в строках 25-33 в настоящий момент не нужен — две предшествующих проверки не допустят возникновения проверяемой этим кодом ситуации. Но если в будущем эти проверки будут модифицированы или убраны, код в строках 25-33 будет срабатывать.

Поскольку в MySQL триггер не может явно отменить транзакцию, мы порождаем исключительную ситуацию (строки 14, 22, 23), при возникновении которой отменяется транзакция, активировавшая срабатывание триггера.

Код UPDATE-триггера будет даже чуть более простым, т.к. в нём по условию задачи нет необходимости проверять, находится ли дата возврата в прошлом.

При этом код в строках 17-25 (ранее отмеченный как бесполезный для IN- SERT-триггера) здесь будет срабатывать, т.к. по условию задачи при выполнении операции обновления допускается установка в поле sb_finish даты из прошлого, что позволяет нарушить третье условие задачи.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 316/545

Пример 33: контроль операций модификации данных

MySQL Решение 4.2.1.a (триггеры для таблицы subscriptions)

1 DELIMITER $$

2

3CREATE TRIGGER `subscriptions_control_upd`

4AFTER UPDATE

5ON `subscriptions`

6FOR EACH ROW

7BEGIN

8

9-- Блокировка выдач книг с датой выдачи в будущем.

10IF NEW.`sb_start` > CURDATE()

11THEN

12SET @msg = CONCAT('Date ', NEW.`sb_start`, ' for subscription ',

13

 

NEW.`sb_id`, ' activation is in the future.');

14SIGNAL SQLSTATE '45001' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1001;

15END IF;

16

17-- Блокировка выдач книг с датой возврата меньшей, чем дата выдачи.

18IF NEW.`sb_finish` < NEW.`sb_start`

19THEN

20SET @msg = CONCAT('Date ', NEW.`sb_finish`, ' for subscription ',

21

 

NEW.`sb_id`,

22

 

' deactivation is less than the date for its activation (',

23

 

NEW.`sb_start`, ').');

24SIGNAL SQLSTATE '45003' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1003;

25END IF;

26

27END;

28$$

29

30 DELIMITER ;

Проверим работоспособность полученного решения. Будем выполнять следующие запросы и следить за реакцией СУБД.

Попытаемся добавить выдачу книги с датой активации в будущем:

MySQL

Решение 4.2.1.a (проверка работоспособности)

1

INSERT INTO `subscriptions`

2

VALUES

(500,

3

 

 

1,

4

 

 

1,

5

 

 

'2020-01-12',

6

 

 

'2020-02-12',

7

 

 

'N')

СУБД запретит эту операцию, вернув следующее сообщение об ошибке:

Error Code: 1001. Date 2020-01-12 for subscription 500 activation is in the future.

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

MySQL

Решение 4.2.1.a (проверка работоспособности)

1

INSERT INTO `subscriptions`

2

 

 

(`sb_id`,

3

 

 

`sb_subscriber`,

4

 

 

`sb_book`,

5

 

 

`sb_start`,

6

 

 

`sb_finish`,

7

 

 

`sb_is_active`)

8

VALUES

(NULL,

9

 

 

3,

10

 

 

3,

11

 

 

'2020-01-12',

12

 

 

'2020-02-12',

13

 

 

'N');

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 317/545

Пример 33: контроль операций модификации данных

СУБД запретит эту операцию, вернув следующее сообщение об ошибке:

Error Code: 1001. Date 2020-01-12 for subscription 501 activation is in the future.

Попытаемся добавить выдачу книги с датой возврата в прошлом:

MySQL

Решение 4.2.1.a (проверка работоспособности)

1

INSERT INTO `subscriptions`

2

VALUES

(502,

3

 

 

1,

4

 

 

1,

5

 

 

'2000-01-12',

6

 

 

'2000-02-12',

7

 

 

'N')

 

 

 

 

СУБД запретит эту операцию, вернув следующее сообщение об ошибке:

Error Code: 1002. Date 2000-02-12 for subscription 502 deactivation is in the past.

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

MySQL

Решение 4.2.1.a (проверка работоспособности)

1

INSERT INTO `subscriptions`

2

VALUES

(503,

3

 

 

1,

4

 

 

1,

5

 

 

'2000-01-12',

6

 

 

'2020-02-12',

7

 

 

'N')

СУБД позволит выполнить вставку.

Попытаемся обновить добавленную выдачу книги так, чтобы дата её активации оказалась в будущем:

MySQL

 

Решение 4.2.1.a (проверка работоспособности)

1

UPDATE `subscriptions`

2

SET

`sb_start` = '2020-01-01'

3

WHERE

`sb_id` = 503

 

 

 

 

СУБД запретит эту операцию, вернув следующее сообщение об ошибке:

Error Code: 1001. Date 2020-01-01 for subscription 503 activation is in the future.

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

 

MySQL

 

Решение 4.2.1.a (проверка работоспособности)

 

1

 

UPDATE `subscriptions`

 

2

 

SET

`sb_start` = '2010-01-01',

3`sb_finish` = '2005-01-01'

4WHERE `sb_id` = 503

СУБД запретит эту операцию, вернув следующее сообщение об ошибке:

Error Code: 1003. Date 2005-01-01 for subscription 503 deactivation is less than the

date for its activation (2010-01-01).

Попытаемся обновить добавленную выдачу книги так, чтобы дата её возврата была в прошлом (для операции обновления такое разрешено):

 

MySQL

 

Решение 4.2.1.a (проверка работоспособности)

 

1

 

UPDATE `subscriptions`

2

 

SET

`sb_start` = '2005-01-01',

3`sb_finish` = '2006-01-01'

4WHERE `sb_id` = 503

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 318/545

Пример 33: контроль операций модификации данных

СУБД позволит выполнить обновление.

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

 

MySQL

 

Решение 4.2.1.a (проверка работоспособности)

 

1

 

UPDATE `subscriptions`

2

 

SET

`sb_start` = '2005-01-01',

3`sb_finish` = '2010-01-01'

4WHERE `sb_id` = 503

СУБД позволит выполнить обновление.

Итак, решение данной задачи для MySQL готово и проверено. Переходим к решению для MS SQL Server.

В MS SQL Server нам доступны только триггеры уровня выражения, потому их внутренняя логика будет иной, нежели в MySQL. Также несколько иначе будут выглядеть возвращаемые триггерами сообщения об ошибках, т.к. в них нужно будет отразить информацию обо всех выдачах книг: в одном варианте реализации — только о «плохих», во втором — и о «плохих», и о «хороших».

Первый вариант реализации триггера полностью блокирует операцию, если хотя бы одна из записей нарушает условия задачи. Информация о таких записях аккумулируется в строковой переменной @bad_records (пояснение о логике работы функции STUFF см. в решении{72} задачи 2.2.2.a{71}).

Далее проверяется длина полученной строки: если она не равна нулю, значит, «плохие» записи обнаружены, и триггер должен отправить клиенту сообщение об ошибке (строки 23, 48, 67) и отменить операцию («откатить транзакцию») (строки

24, 49, 68).

В строках 28-32 мы получаем информацию о количестве записей в псевдотаблицах inserted и deleted, чтобы затем в строках 42-43 определить, выполнялась ли операция вставки (её признак: нет записей в deleted, есть записи в inserted).

MS SQL Решение 4.2.1.a (триггеры для таблицы subscriptions, первый вариант решения)

1-- Вариант с полной блокировкой операции.

2CREATE TRIGGER [subscriptions_control]

3ON [subscriptions]

4AFTER INSERT, UPDATE

5AS

6-- Переменные для хранения списка "плохих записей" и сообщения об ошибке.

7DECLARE @bad_records NVARCHAR(max);

8DECLARE @msg NVARCHAR(max);

9

10-- Блокировка выдач книг с датой выдачи в будущем.

11SELECT @bad_records = STUFF((SELECT ', ' + CAST([sb_id] AS NVARCHAR) +

12

 

 

' (' + CAST([sb_start] AS NVARCHAR) + ')'

13

 

FROM

[inserted]

14

 

WHERE

[sb_start] > CONVERT(date, GETDATE())

15

 

ORDER

BY [sb_id]

16

 

FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'),

17

 

1, 2,

'');

 

 

 

 

18IF LEN(@bad_records) > 0

19BEGIN

20SET @msg =

21CONCAT('The following subscriptions'' activation dates are

22in the future: ', @bad_records);

23RAISERROR (@msg, 16, 1);

24ROLLBACK TRANSACTION;

25RETURN

26END;

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 319/545