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

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

MS SQL

і

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

|

41IF ((LEN @bad records act future) > 0) OR

42(LEN @bad_records_deact_past) > 0 ) OR

43(LEN @bad records act greater than deact) > 0 )

44BEGIN

45SET @msg = 'Some records were NOT inserted!';

46IF (LEN @bad records act future 1 > 0

47BEGIN

48SET @msg = CONCAT @msg CHAR 13 , CHAR(10 ,

49'The following activation dates are in the future: ',

50@bad records act future ;

51END;

52IF (LEN @bad records deact_past) > 0

53BEGIN

54SET @msg = CONCAT @msg CHAR 13 , CHAR(10 ,

55'The following deactivation dates are in the past: ',

56@bad_records_deact_past ;

57END;

58IF (LEN @bad records act greater than deact > 0

59BEGIN

60SET @msg = CONCAT @msg CHAR 13 , CHAR(10 ,

61'The following deactivation dates are less than activation dates: ',

62@bad records act greater than deact ;

63END;

64RAISERROR @msg, 16, 1);

65END;

66

 

 

67

SELECT @good records = STUFF((SELECT ', ' +

68

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

69

CAST([sb finish] AS NVARCHAR)

70

FROM [inserted]

 

71

WHERE ( [sb start] <= CONVERT(date, GETDATE())) AND

72

[sb finish] >= CONVERT(date, GETDATE())) AND

73

[sb finish] >= [sb start]))

74

ORDER BY [sb start]

[sb finish]

75

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

76

1 2. '');

 

77

 

 

78IF LEN @good records > 0

79BEGIN

80SET IDENTITY INSERT [subscriptions] ON;

81INSERT INTO [subscriptions]

82

[sb id],

83

[sb subscriber],

84

[sb book],

85

[sb start],

86

[sb finish],

87

[sb is active])

88

SELECT ( CASE

89

WHEN [sb id] IS NULL

90

OR [sb id] = 0 THEN IDENT CURRENT('subscriptions')

91

+ IDENT INCR('subscriptions')

92

+ ROW NUMBER() OVER (ORDER BY

93

(SELECT 1))

94

- 1

95

ELSE [sb id]

96

END ) AS [sb id],

97

[sb subscriber],

98

[sb book],

99

[sb start],

100[sb finish],

101[sb is active]

102FROM [inserted]

103WHERE ( [sb start] <= CONVERT(date, GETDATE())) AND

104

([sb finish] >= CONVERT(date, GETDATE())) AND

105

([sb finish] >= [sb start] );

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

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

MS SQL

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

106

............. SET IDENTITY_INSERT

.........................[Subscriptions]

107

............. ...........

OFF;

108SET @msg =

109CONCAT('Subscriptions with the following activation/deactivation dates

110were inserted successfully: ', @good_records);

111PRINT @msg;

END; 112 GO

Код в строках 41 -65 проверяет, были ли обнаружены «плохие» записи и, если да, формирует сообщение об ошибке, учитывающее все три условия задачи. Поскольку после отправки сообщения об ошибке (строка 64) мы не откатываем транзакцию и не выходим из тела триггера, выполнение продолжается дальше, и мы получаем возможность произвести вставку в таблицу «хороших» записей.

В строках 67-76 формируется список таких «хороших» записей, данные в которых не нарушают ни одного из условий задачи. Если такие записи были обнаружены, в строках 78-111 мы выполняем их вставку в таблицу и выводим сообщение (просто сообщение, не сообщение об ошибке) со списком их дат. Легко догадаться, что эта операция вставки не приводит к повторной активации INSTEAD OF триггера (иначе мы получили бы бесконечную рекурсию).

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

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

Выполним вставку данных с явно указанными значениями первичного ключа и частью записей, удовлетворяющей условиям задачи, а частью — не удовлетворяющей:

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

1SET IDENTITY INSERT [subscriptions] ON;

2INSERT INTO [subscriptions]

3

 

([sb id] ,

4

 

[sb subscriber],

5

 

[sb book]

6

 

[sb start],

7

 

[sb finish],

8

 

[sb is active])

9

VALUES

500,

10

 

3

11

 

3

12

 

'2020-01-12',

13

 

'2020-02-12',

14

 

'N'),

15

 

(600

16

 

3

17

 

4

18

 

'2021-01-12',

19

 

'2021-02-12',

20

 

'N'),

21

 

(700

22

 

4

23

 

4

24

 

'2001-01-12',

25

 

'2021-02-12',

26

 

'N');

27

SET IDENTITY_ INSERT [subscriptions] OFF;

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

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

Полученные сообщения:

• В первом варианте решения — сообщение об ошибке:

The following subscriptions' activation dates are in the future: 500 (2020- 01-12), 600 (2021-01-12)

• Во втором варианте решения: о Сообщение об ошибке:

Some records were NOT inserted! The following activation dates are in

the future: 2020-01-12,

2021-01-12

о Информационное сообщение:

Subscriptions with the following activation/deactivation dates were inserted successfully: 2001-01-12/2021-02-12

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

MS SQL

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

1

INSERT INTO [subscriptions]

2

 

 

([sb subscriber]

3

 

 

[sb book],

4

 

 

[sb start],

5

 

 

[sb finish]

6

 

 

[sb is active]

7

VALUES

(3,

8

 

 

3,

9

 

 

'2020-01-12',

10

 

 

'2020-02-12',

11

 

 

'N'),

12

 

 

(3,

13

 

 

4,

14

 

 

'2021-01-12',

15

 

 

'2021-02-12',

16

 

 

'N'),

17

 

 

(4,

18

 

 

4,

19

 

 

'2001-01-12',

20

 

 

'2021-02-12',

21

 

 

'N' )

 

 

 

 

Полученные сообщения:

В первом варианте решения — сообщение об ошибке:

The following subscriptions' deactivation dates are in the past: 704 (2001- 01-12), 705 (2002-01-12)

Во втором варианте решения:

оСообщение об ошибке:

Some records were NOT inserted! The following deactivation dates are

in the past: 2001-02-12,

2002-02-12

о Информационное сообщение:

Subscriptions with the following activation/deactivation dates were inserted successfully: 2001-01-12/2021-02-12

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

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

Выполним вставку данных, удовлетворяющих всем условиям задачи:

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

1

INSERT INTO [subscriptions]

2

 

([sb subscriber],

3

 

[sb

book]

4

 

[sb

start],

5

 

[sb

finish],

6

 

[sb

is active])

7

VALUES

4 ,

 

8

 

4 ,

 

9

 

'2001-01-12',

10

 

'2021-02-12',

11

 

'N'

)

 

 

 

 

Полученные сообщения:

В первом варианте решения: никаких сообщений от триггера нет.

Во втором варианте решения:

оСообщение об ошибке: отсутствует.

оИнформационное сообщение:

Subscriptions with the following activation/deactivation dates were inserted successfully: 2001-01-12/2021-02-12

Выполним обновление данных с нарушением одного из условия задачи:

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

1

UPDATE [subscriptions]

2

SET

[sb_finish] = '2005-01-01'

3

WHERE

[sb start] > '2011-01-01'

Триггер во втором варианте решения не реагирует на операцию обновления, а от триггера в первом варианте решения поступит следующее сообщение об ошибке:

The following subscriptions' deactivation dates are less than activation dates: 2

(act: 2011-01-12, deact: 2005-01-01),

3 (act: 2012-05-17, deact: 2005-01-01),

42 (act: 2012-06-11, deact: 2005-01-01),

57 (act: 2012-06-11, deact: 2005-0101),

 

61

(act: 2014-08-03, deact: 2005-01-01),

 

62

(act: 2014-08-03, deact: 200501-01),

 

86

(act: 2014-08-03, deact: 2005-01-01),

 

91

(act: 2015-10-07, deact:

2005-01-01), 95 (act: 2015-10-07, deact:

2005-01-01), 99 (act: 2015-10-08, deact:

2005-01-01), 100 (act: 2011-01-12, deact: 2005-01-01)

Выполним обновление данных с соблюдением всех условий задачи:

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

1

UPDATE

[subscriptions]

2

SET

[sb_finish] = '2002-01-01'

3

WHERE

[sb start] = '2001-01-12';

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

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

Поскольку Oracle не поддерживает псевдотаблицы deleted и inserted, мы реализуем ту же логику, что и в решении для MySQL, используя триггеры уровня записи.

Таким образом, отличие в решении для Oracle от решения для MySQL будет только в способе отмена операции (с одновременным выводом сообщения об ошибке): в Oracle для таких задач удобно использовать функцию RAISE_APPLICA-

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

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

TION_ERROR.

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

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

В остальном решения для Oracle и MySQL полностью идентичны.

Oracl

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

|

 

 

e

 

 

 

 

 

 

 

 

1

-- Реакция на добавление выдачи книги.

 

 

 

2

CREATE TRIGGER "subscriptions control ins"

 

 

 

3

AFTER INSERT

 

 

 

 

4

ON "subscriptions"

 

 

 

 

5

FOR EACH ROW

 

 

 

 

6

BEGIN

 

 

 

 

7

 

 

 

 

 

 

8

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

 

9

IF

new "sb start" > TRUNC(SYSDATE)

 

 

 

10

THEN

 

 

 

 

11

RAISE APPLICATION ERROR( 20001

'Date ' ||

new "sb start" ||

12

 

' for subscription ' ||

new "sb id" ||

13

 

' activation is in the future.');

14

END IF;

 

 

 

 

15

 

 

 

 

 

 

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

17IF new "sb finish" < TRUNC(SYSDATE)

18THEN

19

RAISE APPLICATION ERROR( 20002 'Date ' || new "sb finish" ||

20

' for subscription ' || new "sb id" ||

21

' deactivation is in the past.');

22

END IF;

23

 

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

25IF new "sb finish" < new "sb start"

26THEN

27

RAISE APPLICATION ERROR( 20003 'Date ' ||

new "sb finish" ||

28

'

for subscription ' || new "sb id" ||

29

'

deactivation is less than the date

30

 

for its activation (' ||

31

 

new "sb start" ||

').');

32END IF;

33END;

34

35-- Реакция на обновление выдачи книги.

36CREATE TRIGGER "subscriptions_control_upd"

37AFTER UPDATE

38ON "subscriptions"

39FOR EACH ROW

40BEGIN

41

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

43IF new "sb start" > TRUNC(SYSDATE)

44THEN

45

RAISE APPLICATION ERROR( 20001 'Date ' ||

new "sb start" ||

46

' for subscription

' || new "sb id" ||

47

' activation is in

the future.');

48

END IF;

 

49

 

 

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

51IF new "sb finish" < new "sb start"

52THEN

53

RAISE APPLICATION ERROR( 20003 'Date ' || new "sb finish" ||

54

' for subscription ' || new "sb id" ||

55

' deactivation is less than the date

56

for its activation (' ||

57

new "sb start" || ').');

58

END IF;

59

 

60

END;

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

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

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

нии для MySQL).

Oracle

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

1--

2Деактивация триггера, формирующего значение автоинкрементируемого ПК:

3

ALTER TRIGGER

"TRG_subscriptions_sb_id" DISABLE;

4

 

 

5-- Добавление выдачи книги с датой активации в будущем:

6INSERT

7VALUES

8

 

 

9

INTO "subscriptions" 500, 1 1 , TO_DATE('2020-01-12', 'YYYY-MM-

10

DD'),

 

11

TO_DATE('2020-02-12', 'YYYY-MM-DD'),

12

'N');

 

13

 

 

14

--

Активация триггера,

15

формирующего значение

автоинкрементируемого ПК:

16

ALTER TRIGGER

"TRG_subscriptions_sb_id" ENABLE;

17

 

 

18

--

Добавление выдачи книги с датой

19

активации

в будущем

20-- (без указания значения первичного ключа):

21INSERT

22

 

 

23

 

 

24

 

 

25

 

INTO "subscriptions" "sb_subscriber", "sb_book" "sb_start"

26

 

"sb_finish", "sb_is_active"

27

VALUES

3 ,

28

 

3 ,

29

 

TO_DATE('2020-01-12', 'YYYY-MM-DD'),

30

 

TO_DATE('2020-02-12', 'YYYY-MM-DD'),

31

 

'N');

32

 

 

33-- Добавление выдачи книги с датой возврата в прошлом:

34INSERT

35

 

 

 

36

 

 

 

37

 

 

 

38

 

INTO "subscriptions" "sb_subscriber", "sb_book" "sb_start"

39

 

"sb_finish", "sb_is_active"

40

VALUES

1,

 

41

 

1

 

42

 

TO_DATE('2000-01-12', 'YYYY-MM-DD'),

43

 

TO_DATE('2000-02-12',

'YYYY-MM-DD'),

 

 

 

44

 

'N');

 

 

 

 

45

 

 

 

46

-- Добавление выдачи книги без нарушения условий задачи:

47

INSERT

 

 

48

 

 

 

49

 

 

 

50

 

 

 

51

 

INTO "subscriptions" "sb_subscriber", "sb_book" "sb_start"

 

 

 

52

 

"sb_finish", "sb_is_active"

 

 

 

53

VALUES

1,

 

54

 

1

 

55

 

TO_DATE('2000-01-12',

'YYYY-MM-DD'),

 

 

 

56

 

TO_DATE('2020-02-12',

'YYYY-MM-DD'),

57

'N');

 

58

 

59--Обновление добавленной выдачи книги таким образом, чтобы дата

60--её активации оказалась в будущем:

UPDATE "subscriptions"

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

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

SET "sb_start" = TO_DATE('2020-01-01', 'YYYY-MM-DD')

WHERE "sb id" = 104;

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

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

Oracle I

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

|

61-- Обновление добавленной выдачи книги таким образом, чтобы

62-- дата её активации оказалась позже даты возврата:

63UPDATE "subscriptions"

64

SET

"sb start" = TO DATE('2010-01-01', 'YYYY-MM-DD'),

65

 

"sb

finish" =

TO DATE('2005-01-

'YYYY-MM-DD' )

66

WHERE

"sb

id" = 104

 

 

67

 

 

 

 

 

68-- Обновление добавленной выдачи книги таким образом, чтобы

69-- дата её возврата была в прошлом (для операции обновления

70-- такое разрешено):

71UPDATE "subscriptions"

72

SET

"sb start" = TO DATE('2005-01-01', 'YYYY-MM-DD'),

73

 

"sb finish" = TO DATE('2006-01-

'YYYY-MM-DD' )

74

WHERE

"sb id" = 104;

 

75

 

 

 

76

-- Обновление добавленной выдачи книги без нарушения условий задачи:

77

UPDATE

"subscriptions"

 

78

SET

"sb start" = TO DATE('2005-01-01', 'YYYY-MM-DD'),

79

 

"sb finish" = TO DATE('2010-01-

'YYYY-MM-DD')

80

WHERE

"sb id" = 104;

 

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

■ЛУ Решение 4.2.1.b{315}.

На примере этой (достаточно простой) задачи продемонстрируем типичное неправильное решение, которое часто первым приходит в голову. Оно состоит в том, чтобы в AFTER-триггере проверить, существуют ли читатели, для которых нарушается условие задачи (выборкой по всем читателям) и, если да, «откатить транзакцию». На достаточно объёмной базе данных такое решение может приводить к очень заметному падению производительности.

Правильное же решение состоит в том, чтобы в BEFORE-триггере произво-

дить проверку выполнения условия задачи только для того читателя (тех читателей

— в MS SQL Server), для которого сейчас выполняется операция вставки или обновления записи в таблице subscriptions.

Итак, для всех трёх СУБД представим неправильное и правильное решение и сравним скорость их работы на базе данных «Большая библиотека».

Внеправильном решении для MySQL создадим INSERT- и UPDATE-триггеры

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

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

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

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

 

MySQL 1

Решение 4.2.1.b (неправильное решение)

|

1

DELIMITER $$

 

2

 

 

 

3

CREATE TRIGGER 'sbs cntrl 10 books ins WRONG'

4

AFTER INSERT

 

5

ON 'subscriptions'

 

6

FOR EACH ROW

 

7

 

BEGIN

 

8

 

 

 

9

 

SET @msg = IFNULL((SELECT GROUP CONCAT(

10

CONCAT('(id=', 's id', ', ', 's name',

11

', books=', 's books', ')') SEPARATOR ', ')

12

AS 'list'

13

FROM

(SELECT 's id',

14

 

 

's name',

15

 

 

COUNT('sb book') AS 's books'

16

FROM

'subscribers'

17

 

 

JOIN 'subscriptions'

18

 

 

ON 's id' = 'sb subscriber'

19

WHERE

'sb is active' = 'Y'

20

GROUP

BY 'sb subscriber'

21

HAVING 's books' > 10) AS 'prepared data'),

22

 

 

'');

23

 

 

 

24IF (LENGTH @msg > 0)

25THEN

26SET @msg = CONCAT('The following readers have more books

27

than allowed (10 allowed): ', @msg ;

28SIGNAL SQLSTATE '45001' SET MESSAGE TEXT = @msg, MYSQL ERRNO = 1001;

29END IF;

30

31END;

32$$

33

34CREATE TRIGGER 'sbs_cntrl_10_books_upd_WRONG'

35AFTER UPDATE

36ON 'subscriptions'

37FOR EACH ROW

38BEGIN

39

 

 

 

40

SET @msg = IFNULL((SELECT GROUP CONCAT(

41

CONCAT('(id=', 's id', ', ', 's name',

42

', books=', 's books', ')') SEPARATOR ', ')

43

AS 'list'

 

 

 

 

44

FROM

(SELECT 's id',

45

 

 

's name',

46

 

 

COUNT('sb book') AS 's books'

47

FROM

'subscribers'

48

 

 

JOIN 'subscriptions'

49

 

 

ON 's id' = 'sb subscriber'

50

WHERE

'sb is active' = 'Y'

51

GROUP

BY 'sb subscriber'

52

HAVING 's books' > 10) AS 'prepared data'),

53

 

 

'');

54

 

 

 

55IF (LENGTH @msg > 0)

56THEN

57SET @msg = CONCAT('The following readers have more books

58

than allowed (10 allowed): ', @msg ;

59SIGNAL SQLSTATE '45001' SET MESSAGE TEXT = @msg, MYSQL ERRNO = 1001;

60END IF;

61

62END;

63$$

64

65 DELIMITER ;

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