Сигнали в тригери. Симулиране на check чрез сигнал в тригер

Освен грешките, които сървърът генерира в различни ситуации, тази възможност я има и програмистът. Той може да генерира предупреждения и грешки. В предна статия обяснихме как се прихващат грешки от т.нар. Handler-и. Потребителски-дефинираните грешки също се прихващат от тях. Грешки и предупреждения от съхранени процедури, тригери и събития се генерират посредством генерирането на сигнал. Сигналът е специален обект, който носи информация за типа на грешката (SQLSTATE) и някакво съобщение.

Синтаксис:

SIGNAL  <SQLSTATE или condition_name>

< condition_information_item_name>;

След ключовата дума SIGNAL следва определен SQLSTATE(виж статията Грешки при съхранени…) или т.нар. condition, който можете и сами да декларирате. Condition представлява дефинирана от потребителя грешка, която си има определено име. За нея можем да създадем handler, с който да я прихванем след като я генерираме чрез сигнал. Аналогично в програмирането е: генерираме си наше изключение и пишем catch блок, с който да го прихванем. Писането на condition-и прави процедурата/тригерът доста по четим. Например: SQLSTATE-ът, който връща сървъра при опит за изтриване на несъществуваща таблица е 1051. Ако решим да си направим hanler за него, то той би изглеждал така:

 

use school_sport_clubs;
drop procedure testCondition;
delimiter |
create procedure testCondition()
begin
DECLARE CONTINUE HANDLER FOR 1051
  BEGIN
   SELECT 'No such table';
  END;
	drop table tbmc;
  end;
  |
  delimiter ;

Извикваме процедурата и резултатът е:

4

Нека сега да създадем condition за този SQLSTATE, който да има по-значещо име:

use school_sport_clubs;
drop procedure if exists testConditionWithCondition;
delimiter |
create procedure testConditionWithCondition()
begin
DECLARE no_such_table CONDITION for 1051;

DECLARE CONTINUE HANDLER FOR no_such_table
  BEGIN
   SELECT 'No such table';
  END;
	drop table tbmc;
  end;
  |
  delimiter ;

Така с no_such_table всъщност дадохме по-значещо име на 1051. Резултатът, разбира се, е същият.

Да се върнем на синтаксиса на SIGNAL. След като уточнихме какъв ще бъде типа на сигнала – дали чрез SQLSTATE или чрез condition_name. След това се подава информация чрез използване на SET и някоя от дефинираните в mysql condition_information_item. А те са:

  • SUBCLASS_ORIGIN
  • MESSAGE_TEXT
  • MYSQL_ERRNO
  • CONSTRAINT_CATALOG
  • CONSTRAINT_SCHEMA
  • CONSTRAINT_NAME
  • CATALOG_NAME
  • SCHEMA_NAME
  • TABLE_NAME
  • COLUMN_NAME
  • CURSOR_NAME

Нека дадем пример, който илюстрира казаното по-горе:

 

drop procedure if exists signalTest;  
delimiter |
CREATE PROCEDURE signalTest(IN testVar INT)
BEGIN
  DECLARE testCondition CONDITION FOR SQLSTATE '45000';
  IF testVar = 0 THEN
    SIGNAL SQLSTATE '01000'; # warning
	ELSEIF testVar = 1 THEN
		SIGNAL SQLSTATE '45000'
		SET MESSAGE_TEXT = 'Erro from case 1 - signal with handled SQLSTATE.';
	ELSEIF testVar = 2 THEN
		SIGNAL testCondition
		SET MESSAGE_TEXT = 'Error from case 2 - signal with handled condition.';
  ELSE
    SIGNAL SQLSTATE '01000'
      SET MESSAGE_TEXT = 'Warning! It will not be terminated. The procedure will continue.', MYSQL_ERRNO = 1000;
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'After the warning, the error reset the message and it apper on the screen!', MYSQL_ERRNO = 1001;
  END IF;
END ;
|
DELIMITER ;

5

 

Стандартното съобщение за потребителска грешка (user-define error) е 45000.

Ако подадете 0: Възниква WARNING. Той не терминира процедурата.

Ако подадете 1: Генерира се сигнал с SQLSTATE: ‘45000’. Терминира процедурата и извежда съобщението.

Ако подадете 2: Генерира се сигнал със създадения по-горе condition и се извежда съобщението.

Всички останали случаи: Ще се генерира WARNING. От предупреждението не се терминира процедурата, но стигне ли се до грешката, процедурата се терминира. Съобщението за грешката и номерът и, зададени по-рано се заменят от тези, които грешката решава да сложи.

Може да пробвате вътре да си дефинирате handler за SQLSTATE ‚45000‘ или за testCondition, но не го правете и за двете, защото второто е просто по-потребителско ориентирано име на първото. Например:

 

drop procedure if exists signalTest;  
delimiter |
CREATE PROCEDURE signalTest(IN testVar INT)
BEGIN
  DECLARE testCondition CONDITION FOR SQLSTATE '45000';
  DECLARE EXIT handler for testCondition SELECT 'Hi, from handler for testCondition';
  IF testVar = 0 THEN
    SIGNAL SQLSTATE '01000'; # warning
	ELSEIF testVar = 1 THEN
		SIGNAL SQLSTATE '45000'
		SET MESSAGE_TEXT = 'Erro from case 1 - signal with handled SQLSTATE.';
	ELSEIF testVar = 2 THEN
		SIGNAL testCondition
		SET MESSAGE_TEXT = 'Error from case 2 - signal with handled condition.';
  ELSE
    SIGNAL SQLSTATE '01000'
      SET MESSAGE_TEXT = 'Warning! It will not be terminated. The procedure will continue.', MYSQL_ERRNO = 1000;
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'After the warning, the error reset the message and it apper on the screen!', MYSQL_ERRNO = 1001;
  END IF;
END ;
|
DELIMITER ;


CALL  signalTest(1);
CALL  signalTest(2);

И за двете резултатът е:

6

7

Съобщението за грешка вече не присъства, защото грешката е прихваната от Handler-a. И процедурата е завършила с нормален EXIT, а не принудително както по-горе.
Нека да реализираме един пример. Искаме да валидираме входа на таблица coaches- егн на треньорите не може да бъде по-малко от 10 символа. Ако е така, то въвеждането да се прекрати. За целта ще създадем тригер, който се активира преди INSERT:

DROP TRIGGER if exists before_coaches_insert;
delimiter |
CREATE TRIGGER before_coaches_insert BEFORE INSERT ON coaches
FOR EACH ROW
BEGIN
IF(CHAR_LENGTH(NEW.egn) < 10)
THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'The egn must be 10 characters.';
end if;
END;
|
delimiter ;

Тестваме с невалидно EGN:

INSERT INTO `school_sport_clubs`.`coaches` (`name`, `egn`, `month_salary`, `hour_salary`) VALUES ('Ivan Iordanov Petrov', '7452', '1500', '14');

Резултатът е:

8

Съответно записът не е въведен в таблицата. Но пък може да бъде въведен при UPDATE, затова може да направите и такъв тригер.

С разгледаното по -горе ние всъщност симулирахме ограниченито check, за което казахме че в MySQL не работи, но съществува синтактично. На практика чрез сигнал в тригера можем да спрем вкарването на некоректни данни.

Реализиране на повече от един тригер за един и същи trigger event в един и същи action time:

Споменахме, че след версия 5.7.2 на MySQL вече може да се правят повече от 1 тригери за един и същи trigger event и един и същи action time. За да се случи това, трябва да се въведе последователност  на тригерите- кой след кой ще се извиква. За целта въвеждаме двете ключови думи FOLLOWS и PRECEDES след FOR EACH ROW. Останалата част от синтаксиса не се променя. Само уточняваме след или преди кой друг тригер да се изпълни новия. Нека да направим още един тригер, който да се изпълни след before_coaches_insert.

 

DROP TRIGGER if exists before_coaches_insert2;
delimiter |
CREATE TRIGGER before_coaches_insert2 BEFORE INSERT ON coaches
FOR EACH ROW FOLLOWS before_coaches_insert
BEGIN
IF(CHAR_LENGTH(NEW.name) = 0)
THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'The name is empty.';
end if;
END;
|
delimiter ;

Доста излишно е да се създадат два тригера, които да сработват един след друг, защото спокойно може да обедините заявките си в тялото на единия. Така или иначе то може да съдържа множество заявки между BEGIN и END.

Даниел Джолев

Leave a Reply

Your email address will not be published. Required fields are marked *