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

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

 

Oracl

і

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

|

e

 

 

 

 

9

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

10

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

11

UPDATE "m2m books genres"

 

12

SET

 

"b id" = 3

 

13

WHERE

"b id" = 1

 

14

 

AND "g_id" = 4;

 

15

 

 

 

 

16

UPDATE "m2m books genres"

 

17

SET

 

"b id" = 4

 

18WHERE "b id" = 2

19AND "g_id" = 4;

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

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

23UPDATE "m2m books genres"

24

SET

"g id" = 5

25WHERE "b id" = 3

26AND "g_id" = 4;

28

UPDATE "m2m books

genres"

29

SET

"g id" = 5

 

30WHERE "b id" = 4

31AND "g_id" = 4;

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

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

35UPDATE "m2m books genres"

36

SET

"b id" = 1,

37"g id" = 4

38WHERE "b id" = 3

39AND "g_id" = 5;

41

UPDATE "m2m books genres"

42

SET

"b id" = 2,

43"g id" = 4

44WHERE "b id" = 4

45AND "g_id" = 5;

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 Стр: 330/545

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

{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.A: доработать триггеры из решений ,

сколько раз читатель брал в библиотеке книги (этот счётчик должен инкрементироваться каждый раз, когда читателю выдаётся книга; уменьшение значения этого счётчика не предусмотрено).

& Задание 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 Стр: 331/545

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

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

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

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

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

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

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

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

укоторого на руках находится десять и более книг.

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

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

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

“Date 2038.01.12 for subscription 145

activation isin the future”.

“Date 1983.01.12 for subscription 155

deactivationis in the past”.

“Date 2000.01.12 for subscription 165

deactivationis less than date for its activa

 

tion (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).”

xA?

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

Для решения данной задачи нам понадобятся только INSERT- и UPDATE -

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

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

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

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

 

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

[

I

DELIMITER. $$ ..............................

‘ ..............................

2

 

 

CREATE TRIGGER 'subscriptions_control_ins'

4AFTER INSERT

5ON 'subscriptions'

6FOR EACH ROW

7BEGIN 8

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

10IF NEW.'sb_start' > CURDATE()

II

THEN

12

SET @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 Стр: 333/545

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

MySQL I

Решение 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 .а (проверка

 

 

 

1

 

работоспособности)

INSERT INTO

 

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 I

’ешение 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 Стр: 334/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' (502

2

VALUES

1, 1, '2000-01-12', '2000-02-12', 'N' )

3

4

5

6

7

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

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 .а (проверка

1

UPDATEработоспособности) :

2

SET

'subscriptions'

3'sb_start' = '2005-01-01',

4WHERE 'sb_finish' = '2006-01-01'

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

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

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

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

MySQL

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

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.а{71}).

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

24, 49, 68).

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

MS SQL

Решение 4.2.1 .а (триггеры для таблицы 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);

23 RAISERROR @msg 16 1);

24ROLLBACK TRANSACTION;

25RETURN

26END;

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

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

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

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

MS SQL I

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

|

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

28DECLARE @deleted records INT;

29DECLARE @inserted records INT;

30

31SELECT @deleted records = COUNT(*) FROM [deleted];

32SELECT @inserted records = COUNT(*) FROM [inserted];

34SELECT @bad records = STUFF((SELECT ', ' + CAST [sb id] AS NVARCHAR) +

35

' (' + CAST([sb start] AS NVARCHAR) + ')'

36

FROM

[inserted]

37

WHERE [sb finish] < CONVERT(date, GETDATE())

38

ORDER BY [sb id]

39

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

40

1, 2

'');

41IF ((LEN @bad records > 0) AND

42@deleted records = 0 AND

43@inserted records > 0))

44BEGIN

45SET @msg =

46CONCAT('The following subscriptions'' deactivation dates are

47in the past: ', @bad records);

48 RAISERROR @msg 16 1 ;

49ROLLBACK TRANSACTION;

50RETURN

51END;

52

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

54SELECT @bad records = STUFF((SELECT ', ' + CAST [sb id] AS NVARCHAR) +

55' (act: ' + CAST [sb start] AS NVARCHAR) + ', deact: ' +

56CAST [sb finish] AS NVARCHAR) + ')'

57

FROM

[inserted]

 

 

58

WHERE [sb finish]

< [sb

start]

59

ORDER BY [sb id]

 

 

60

FOR XML PATH(''),

TYPE)

value('.', 'nvarchar(max)'),

61

1, 2

'');

 

 

62IF LEN(@bad records) > 0

63BEGIN

64SET @msg =

65CONCAT('The following subscriptions'' deactivation dates are less

66

than activation dates: ', @bad records ;

67

RAISERROR @msg 16 1 ;

68ROLLBACK TRANSACTION;

69RETURN

70END;

71GO

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

Чтобы добиться такого эффекта мы будем использовать INSTEAD OF триг-

гер, который активируется вместо соответствующей операции с данными. Если в теле такого триггера не выполнять никаких действий, то исходная операция с данными не выполнится по определению, и даже нет надобности «откатывать транзакцию». Чтобы операция выполнилась, в теле триггера нужно будет выполнить соответствующий запрос на модификацию данных в таблице.

К сожалению, в таблице subscriptions есть внешние кличи с каскадным обновлением, потому MS SQL Server не позволит создать INSTEAD OF UPDATE

триггер, но продемонстрировать общую логику решения можно и на одной лишь операции вставки.

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

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

Если бы перед нами остро стояла задача применить именно этот вариант решения и для операции обновления, мы могли бы изменить свойства внешних ключей, убрав там операцию каскадного обновления и реализовав её отдельным триггером.

Обратите внимание на то, как в данном варианте решения реализована последовательность действий: поскольку мы не отменяем операцию и не выходим из тела триггера (как это реализовано в строках 24-25, 49-50, 68-69 первого варианта решения), триггер доработает до конца своего тела, что вынуждает нас одновременно учитывать все три условия задачи при принятии решения о том, «хорошая» ли нам попалась запись или «плохая».

В первую очередь мы получаем три списка «плохих» записей — по каждому из условий задачи (строки 14-40). В INSTEAD OF триггере нам неизвестны значения автоинкрементируемого первичного ключа (IDENTITY-ПОЛЯ sb_id), потому мы со-

бираем только сами значения дат. Однако, для реальной вставки данных (строки 81105) эти значения нам понадобятся: в решении{246} задачи 3.2.1.a{245} подробно объяснена логика их получения и использования.

MS

QL |

решение 4.2.1 .a (триггер для таолицы subscriptions, второй вариант решения)

І

 

1

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

 

2

CREATE TRIGGER [subscriptions_control]

 

3

ON [subscriptions] INSTEAD OF INSERT AS

 

4

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

 

5

DECLARE @bad_records_act_future NVARCHAR(max);

 

6

DECLARE @bad_records_deact_past NVARCHAR(max);

 

7

DECLARE @bad_records_act_greater_than_deact NVARCHAR(max);

 

8

 

 

 

9

DECLARE @good_records NVARCHAR(max);

 

10

DECLARE @msg NVARCHAR(max);

 

11

 

 

 

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

13SELECT @bad_records_act_future =

14

STUFF((SELECT ', ' + CAST [sb_start] AS NVARCHAR)

15

FROM

[inserted]

16

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

17

ORDER BY

[sb_start]

18

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

19

1, 2

'');

 

20

 

 

 

23

STUFF((SELECT ', ' + CAST [sb_finish] AS NVARCHAR)

 

 

 

24

FROM

[inserted]

 

 

 

25

WHERE

[sb_finish] < CONVERT(date, GETDATE())

 

 

 

26

ORDER BY

[sb_finish]

 

 

 

27

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

 

 

 

28

1, 2

'');

 

 

 

 

29

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

30

SELECT @bad_records_act_greater_than_deact =

31

STUFF((SELECT ', (act: ' + CAST([sb_start] AS NVARCHAR) +

32

 

', deact: ' + CAST [sb_finish] AS NVARCHAR) + ')'

33

 

FROM

[inserted]

34

WHERE

[sb_finish] < [sb_start]

35

ORDER BY

[sb_start], [sb_finish]

36

21

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

22

SELECT @bad_records_deact_past =

 

 

 

37

 

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

 

 

'nvarchar(max)'

38

 

 

 

)

 

39

 

 

1, 2

'');

 

40

 

 

 

 

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