Gatilhos que causam inserções a falhar? Possível?
Pergunta
Na limpeza esta resposta Eu aprendi um pouco sobre TRIGGER
s e procedimentos armazenados em MySQL, mas ficou surpreso que, enquanto BEFORE INSERT
e BEFORE UPDATE
Os gatilhos podem modificar os dados, aparentemente não podiam fazer com que a inserção/atualização falhe (por exemplo, validação). Nesse caso em particular, consegui fazer isso funcionar manipulando os dados de forma a causar uma duplicata de chave primária, que nesse caso em particular fazia sentido, mas não necessariamente faz sentido em um sentido geral.
Esse tipo de funcionalidade é possível no MySQL? Em qualquer outro RDBMS (minha experiência é limitada a MySQL tristemente)? Talvez a THROW EXCEPTION
sintaxe de estilo?
Solução
A partir disso Postagem do blog
MySQL gatilhos: Como você aborta uma inserção, atualização ou exclua com um gatilho? No #MySQL da EFNET, alguém perguntou:
Como faço para fazer um gatilho abortar a operação se minha regra de negócios falhar?
No MySQL 5.0 e 5.1, você precisa recorrer a alguns truques para fazer um gatilho falhar e entregar uma mensagem de erro significativa. O procedimento armazenado MySQL FAQ diz isso sobre o tratamento de erros:
SP 11. O SPS tem uma declaração de "aumento" para "aumentar os erros de inscrição"? Desculpe, não atualmente. O sinal padrão SQL e as declarações resignais estão no TODO.
Talvez o MySQL 5.2 inclua a instrução SINAL, que tornará obsoleto esse hackeado do MySQL Programação de procedimentos armazenados. O que é o hack? Você vai forçar o MySQL a tentar usar uma coluna que não existe. Feio? Sim. Funciona? Claro.
CREATE TRIGGER mytabletriggerexample BEFORE INSERT FOR EACH ROW BEGIN IF(NEW.important_value) < (fancy * dancy * calculation) THEN DECLARE dummy INT; SELECT Your meaningful error message goes here INTO dummy FROM mytable WHERE mytable.id=new.id END IF; END;
Outras dicas
Aqui está a maneira como eu fiz isso. Note o SET NEW='some error';
. O MySQL informará que "variável 'novo' não pode ser definida como o valor de 'Erro: não é possível excluir este item. Existem registros na tabela de vendas com este item.'"
Você pode prender isso no seu código e depois mostrar o erro resultante :)
DELIMITER $$
DROP TRIGGER IF EXISTS before_tblinventoryexceptionreasons_delete $$
CREATE TRIGGER before_tblinventoryexceptionreasons_delete
BEFORE DELETE ON tblinventoryexceptionreasons
FOR EACH ROW BEGIN
IF (SELECT COUNT(*) FROM tblinventoryexceptions WHERE tblinventoryexceptions.idtblinventoryexceptionreasons = old.idtblinventoryexceptionreasons) > 0
THEN
SET NEW='Error: Cannot delete this item. There are records in the inventory exception reasons table with this item.';
END IF;
END$$
DELIMITER ;
DELIMITER $$
DROP TRIGGER IF EXISTS before_storesalesconfig_delete $$
CREATE TRIGGER before_storesalesconfig_delete
BEFORE DELETE ON tblstoresalesconfig
FOR EACH ROW BEGIN
IF (SELECT COUNT(*) FROM tblstoresales WHERE tblstoresales.idtblstoresalesconfig=old.idtblstoresalesconfig) > 0
THEN
SET NEW='Error: Cannot delete this item. There are records in the sales table with this item.';
END IF;
IF (SELECT COUNT(*) FROM tblinventory WHERE tblinventory.idtblstoresalesconfig=old.idtblstoresalesconfig) > 0
THEN
SET NEW='Error: Cannot delete this item. There are records in the inventory table with this item.';
END IF;
IF (SELECT COUNT(*) FROM tblinventoryexceptions WHERE tblinventoryexceptions.idtblstoresalesconfig=old.idtblstoresalesconfig) > 0
THEN
SET NEW='Error: Cannot delete this item. There are records in the inventory exceptions table with this item.';
END IF;
IF (SELECT COUNT(*) FROM tblinvoicedetails WHERE tblinvoicedetails.idtblstoresalesconfig=old.idtblstoresalesconfig) > 0
THEN
SET NEW='Error: Cannot delete this item. There are records in the inventory details table with this item.';
END IF;
END$$
DELIMITER ;
DELIMITER $$
DROP TRIGGER IF EXISTS before_tblinvoice_delete $$
CREATE TRIGGER before_tblinvoice_delete
BEFORE DELETE ON tblinvoice
FOR EACH ROW BEGIN
IF (SELECT COUNT(*) FROM tblinvoicedetails WHERE tblinvoicedetails.idtblinvoice = old.idtblinvoice) > 0
THEN
SET NEW='Error: Cannot delete this item. There are records in the inventory details table with this item.';
END IF;
END$$
DELIMITER ;
Como este artigo aparece no topo quando procuro o manuseio de erros nos gatilhos do MySQL, pensei em compartilhar algum conhecimento.
Se houver um erro, você pode forçar o MySQL a usar um SINAL, mas se você não especificar isso como uma classe como sqlexception, nada acontecerá, pois nem todos os sqlstates são considerados ruins, e mesmo assim você teria que se certificar de Resignal Se você tiver algum bloco de início/final aninhado.
Como alternativa, e provavelmente mais simples ainda, dentro do seu gatilho, declare um manipulador de saída e renuncia a exceção.
CREATE TRIGGER `my_table_AINS` AFTER INSERT ON `my_table` FOR EACH ROW
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
RESIGNAL;
DECLARE EXIT HANDLER FOR SQLWARNING
RESIGNAL;
DECLARE EXIT HANDLER FOR NOT FOUND
RESIGNAL;
-- Do the work of the trigger.
END
E se no seu corpo ocorrer um erro, ele será jogado de volta à parte superior e sairá com um erro. Isso também pode ser usado em procedimentos armazenados e outros enfeites.
Isso funciona com qualquer coisa versão 5.5+.
Isso abortará sua inserção levantando uma exceção (de http://www.experts-exchange.com/database/mysql/q_23788965.html)
DROP PROCEDURE IF EXISTS `MyRaiseError`$$
CREATE PROCEDURE `MyRaiseError`(msg VARCHAR(62))
BEGIN
DECLARE Tmsg VARCHAR(80);
SET Tmsg = msg;
IF (CHAR_LENGTH(TRIM(Tmsg)) = 0 OR Tmsg IS NULL) THEN
SET Tmsg = 'ERROR GENERADO';
END IF;
SET Tmsg = CONCAT('@@MyError', Tmsg, '@@MyError');
SET @MyError = CONCAT('INSERT INTO', Tmsg);
PREPARE stmt FROM @MyError;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
Uso:
call MyRaiseError('Here error message!');
Não funciona em gatilhos (o SQL dinâmico não é permitido na função ou gatilho armazenado)
Eu uso uma solução muito suja:
If NEW.test=1 then
CALL TEST_CANNOT_BE_SET_TO_1;
end if;
Quando teste = 1 MySQL lança a seguinte exceção:
Procedimento Administratie.test_cannot_be_set_to_1 não existe
Não sofisticado, mas rápido e útil.
No MS SQL, você pode fazê -lo funcionar usando sintaxe adequada:
IF UPDATE(column_name)
BEGIN
RAISEERROR
ROLLBACK TRAN
RETURN
END