Материал: Using_MySql,_MS_SQL_Server_and_Oracle(1)

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Пример 35: прозрачное исправление ошибок в данных

MySQL Решение 4.2.3.a (триггеры для таблицы subscribers) (продолжение)

18CREATE TRIGGER `sbsrs_name_lp_upd`

19BEFORE UPDATE

20ON `subscribers`

21FOR EACH ROW

22BEGIN

23IF (SUBSTRING(NEW.`s_name`, -1) <> '.')

24THEN

25SET @new_value = CONCAT(NEW.`s_name`, '.');

26SET @msg = CONCAT('Value [', NEW.`s_name`, '] was automatically

27

 

changed to [', @new_value ,']');

28SET NEW.`s_name` = @new_value;

29SIGNAL SQLSTATE '01000' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1000;

30END IF;

31END;

32$$

33

34DELIMITER ;

ВMS SQL Server так же просто и красиво подменить некорректное значение корректным не получится, т.к. в этой СУБД нет триггеров уровня записи.

Потому нам придётся создавать INSTEAD OF триггеры (со всеми вытекаю-

щими отсюда проблемами и ограничениями в виде «ручной» генерации значения автоинкрементируемого первичного ключа на вставке данных и запрета изменения значения первичного ключа на обновлении данных).

Зато в MS SQL Server можно очень легко и удобно передавать сообщения из триггеров. Для демонстрации этих возможностей в представленном ниже коде реализовано два варианта поведения:

В строках 18 и 68: с помощью конструкции PRINT (которая просто выводит текстовое сообщение в консоль).

В строках 19 и 69: с помощью функции RAISERROR (которая при таких параметрах (см. документацию17) генерирует сообщение, не приводящее к остановке операции; к тому же мы не «откатываем» транзакцию в теле триггера).

MS SQL Решение 4.2.3.a (триггеры для таблицы subscribers)

1CREATE TRIGGER [sbsrs_name_lp_ins]

2ON [subscribers]

3INSTEAD OF INSERT

4AS

5DECLARE @bad_records NVARCHAR(max);

6DECLARE @msg NVARCHAR(max);

7

 

 

 

8

 

SELECT @bad_records = STUFF((SELECT ', ' + '['

+ [s_name] + '] -> [' +

9

 

[s_name] +

'.]'

10

 

FROM

[inserted]

11

 

WHERE

RIGHT([s_name], 1) <> '.'

12

 

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

13

 

1, 2, '');

 

14

 

 

 

15IF (LEN(@bad_records) > 0)

16BEGIN

17SET @msg = CONCAT('Some values were changed: ', @bad_records);

18PRINT @msg;

19RAISERROR (@msg, 16, 0);

20END;

17 https://msdn.microsoft.com/en-us/library/ms178592.aspx

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

Пример 35: прозрачное исправление ошибок в данных

MS SQL Решение 4.2.3.a (триггеры для таблицы subscribers) (продолжение)

21SET IDENTITY_INSERT [subscribers] ON;

22INSERT INTO [subscribers]

23

 

([s_id],

24

 

[s_name])

25

 

SELECT ( CASE

26

 

WHEN [s_id] IS NULL

27

 

OR [s_id] = 0 THEN IDENT_CURRENT('subscribers')

28

 

+ IDENT_INCR('subscribers')

29

 

+ ROW_NUMBER() OVER (ORDER BY

30

 

(SELECT 1))

31

 

- 1

32

 

ELSE [s_id]

33

 

END ) AS [s_id],

34

 

( CASE

35

 

WHEN RIGHT([s_name], 1) <> '.'

36

 

THEN CONCAT([s_name], '.')

37

 

ELSE [s_name]

38

 

END ) AS [s_name]

39

 

FROM [inserted];

40SET IDENTITY_INSERT [subscribers] OFF;

41GO

42

43CREATE TRIGGER [sbsrs_name_lp_upd]

44ON [subscribers]

45INSTEAD OF UPDATE

46AS

47DECLARE @bad_records NVARCHAR(max);

48DECLARE @msg NVARCHAR(max);

49

50IF (UPDATE([s_id]))

51BEGIN

52RAISERROR ('Please, do NOT update surrogate PK

53

 

on table [subscribers]!', 16, 1);

54ROLLBACK TRANSACTION;

55RETURN;

56END;

57

 

 

 

 

58

 

SELECT @bad_records = STUFF((SELECT

', ' + '['

+ [s_name] + '] -> [' +

59

 

 

[s_name] +

'.]'

60

 

FROM

[inserted]

 

61

 

WHERE

RIGHT([s_name], 1) <> '.'

62

 

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

63

 

1, 2, '');

 

 

64

 

 

 

 

65IF (LEN(@bad_records) > 0)

66BEGIN

67SET @msg = CONCAT('Some values were changed: ', @bad_records);

68PRINT @msg;

69RAISERROR (@msg, 16, 0);

70END;

71

 

 

 

 

72

 

UPDATE

[subscribers]

73

 

SET

[subscribers].[s_name] =

74

 

 

( CASE

 

75

 

 

WHEN

RIGHT([inserted].[s_name], 1) <> '.'

76

 

 

THEN

CONCAT([inserted].[s_name], '.')

77

 

 

ELSE

[inserted].[s_name]

78

 

 

END )

 

79

 

FROM

[subscribers]

80JOIN [inserted]

81ON [subscribers].[s_id] = [inserted].[s_id];

82GO

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

Пример 35: прозрачное исправление ошибок в данных

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

Чтобы увидеть выводимое сообщение необходимо предварительно выполнить команду SET SERVEROUTPUT ON, включающую показ таких данных.

Oracle

Решение 4.2.3.a (триггеры для таблицы subscribers)

1CREATE TRIGGER "sbsrs_name_lp_ins_upd"

2BEFORE INSERT OR UPDATE

3ON "subscribers"

4FOR EACH ROW

5DECLARE

6new_value NVARCHAR2(150);

7BEGIN

8IF (SUBSTR(:new."s_name", -1) <> '.')

9THEN

10new_value := CONCAT(:new."s_name", '.');

11DBMS_OUTPUT.PUT_LINE('Value [' || :new."s_name" ||

12'] was automatically changed to [' || new_value || ']');

13:new."s_name" := new_value;

14END IF;

15END;

На этом решение данной задачи завершено. Убедиться в его корректности вы можете самостоятельно, выполнив запросы к таблице subscribers на вставку и обновление данных — как нарушающие условие задачи, так и не нарушающие.

Решение 4.2.3.b{344}.

Общая логика решения данной задачи повторяет логику решения{344} задачи 4.2.3.a{344}, и достойным отдельного упоминания здесь можно считать только следующее:

в MySQL и MS SQL Server приходится делать два отдельных триггера (MySQL не умеет «объединять» объявление триггеров для нескольких операций, а в MS SQL Server различается внутреннее поведение INSERT- и UP- DATE-триггера), в то время как в Oracle получается компактный одинаковый код, актуальный для обеих операций;

в MS SQL Server без доработки модели БД можно создать только INSTEAD OF INSERT триггер, а для создания INSTEAD OF UPDATE триггера придётся отключить каскадное обновление на внешних ключах таблицы subscriptions и реализовать соответствующие операции по обеспечению ссылочной целостности в самом триггере;

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

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

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

Пример 35: прозрачное исправление ошибок в данных

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

1 DELIMITER $$

2

3CREATE TRIGGER `sbscs_date_tm_ins`

4BEFORE INSERT

5ON `subscriptions`

6FOR EACH ROW

7BEGIN

8IF (NEW.`sb_finish` < NEW.`sb_start`) OR (NEW.`sb_finish` < CURDATE())

9THEN

10SET @new_value = DATE_ADD(CURDATE(), INTERVAL 2 MONTH);

11SET @msg = CONCAT('Value [', NEW.`sb_finish`, '] was automatically

12

changed to [', @new_value ,']');

13SET NEW.`sb_finish` = @new_value;

14SIGNAL SQLSTATE '01000' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1000;

15END IF;

16END;

17$$

18

19CREATE TRIGGER `sbscs_date_tm_upd`

20BEFORE UPDATE

21ON `subscriptions`

22FOR EACH ROW

23BEGIN

24IF (NEW.`sb_finish` < NEW.`sb_start`) OR (NEW.`sb_finish` < CURDATE())

25THEN

26SET @new_value = DATE_ADD(CURDATE(), INTERVAL 2 MONTH);

27SET @msg = CONCAT('Value [', NEW.`sb_finish`, '] was automatically

28

 

changed to [', @new_value ,']');

 

 

 

29SET NEW.`sb_finish` = @new_value;

30SIGNAL SQLSTATE '01000' SET MESSAGE_TEXT = @msg, MYSQL_ERRNO = 1000;

31END IF;

32END;

33$$

34

35 DELIMITER ;

MS SQL Решение 4.2.3.b (триггеры для таблицы subscriptions)

1CREATE TRIGGER [sbscs_date_tm_ins]

2ON [subscriptions]

3INSTEAD OF INSERT

4AS

5DECLARE @bad_records NVARCHAR(max);

6DECLARE @msg NVARCHAR(max);

7

8SELECT @bad_records =

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

10

 

 

'] -> [' + FORMAT(DATEADD(month, 2, GETDATE()),

11

 

 

'yyyy-MM-dd') + ']'

12

 

FROM

[inserted]

13

 

WHERE

([sb_finish] < [sb_start]) OR

14

 

 

([sb_finish] < GETDATE())

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

161, 2, '');

17

18IF (LEN(@bad_records) > 0)

19BEGIN

20SET @msg = CONCAT('Some values were changed: ', @bad_records);

21PRINT @msg;

22RAISERROR (@msg, 16, 0);

23END;

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

Пример 35: прозрачное исправление ошибок в данных

MS SQL Решение 4.2.3.b (триггеры для таблицы subscriptions) (продолжение)

24SET IDENTITY_INSERT [subscriptions] ON;

25INSERT INTO [subscriptions]

26

 

([sb_id],

27

 

[sb_subscriber],

28

 

[sb_book],

29

 

[sb_start],

30

 

[sb_finish],

31

 

[sb_is_active])

32

 

SELECT ( CASE

33

 

WHEN [sb_id] IS NULL

34

 

OR [sb_id] = 0 THEN IDENT_CURRENT('subscriptions')

35

 

+ IDENT_INCR('subscriptions')

36

 

+ ROW_NUMBER() OVER (ORDER BY

37

 

(SELECT 1))

38

 

- 1

39

 

ELSE [sb_id]

40END ) AS [sb_id],

41[sb_subscriber],

42

 

[sb_book],

43

 

[sb_start],

44

 

( CASE

45

 

WHEN (([sb_finish] < [sb_start]) OR

46

 

([sb_finish] < GETDATE()))

47

 

THEN DATEADD(month, 2, GETDATE())

48

 

ELSE [sb_finish]

 

 

 

49END ) AS [sb_finish],

50[sb_is_active]

51 FROM [inserted];

52SET IDENTITY_INSERT [subscriptions] OFF;

53GO

54

55-- Внимание! Чтобы этот триггер можно было создать, необходимо

56-- отключить каскадное обновление на внешних

57-- ключах таблицы [subscriptions].

58-- Правда, тогда придётся доработать триггер так, чтобы с его

59-- помощью обеспечивать ссылочную целостность.

60CREATE TRIGGER [sbscs_date_tm_upd]

61ON [subscriptions]

62INSTEAD OF UPDATE

63AS

64DECLARE @bad_records NVARCHAR(max);

65DECLARE @msg NVARCHAR(max);

66

67IF (UPDATE([sb_id]))

68BEGIN

69RAISERROR ('Please, do NOT update surrogate PK

70

 

on table [subscriptions]!', 16, 1);

71ROLLBACK TRANSACTION;

72RETURN;

73END;

74

75SELECT @bad_records =

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

77

 

 

'] -> [' + FORMAT(DATEADD(month, 2, GETDATE()),

78

 

 

'yyyy-MM-dd') + ']'

79

 

FROM

[inserted]

80

 

WHERE

([sb_finish] < [sb_start]) OR

81

 

 

([sb_finish] < GETDATE())

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

831, 2, '');

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

Источник: https://studfile.net/preview/16418462/