No SQL Server, gatilhos são objetos de banco de dados que são executados sempre que um evento de gatilho acontece no banco de dados ou no servidor.
Os gatilhos desempenham um papel fundamental no cumprimento dos requisitos de negócios, como alertar pessoas-alvo, iniciar um trabalho ou outras operações. Como os Triggers podem lidar com muitas dessas operações, devemos defini-las com cuidado para evitar impactos no desempenho.
Neste artigo, examinaremos Triggers, tipos de Triggers e várias opções de Trigger disponíveis. Além disso, exploraremos as precauções necessárias ao usar gatilhos DML.
Acionadores em SQL
Um Trigger é um tipo especial de procedimento armazenado que é executado em eventos definidos, executando o script definido no corpo do Trigger. Existem vários tipos de gatilhos:
- Acionadores de DML – para executar operações DML como comandos INSERT, UPDATE e DELETE em tabelas.
- Acionadores de DDL – para executar operações DDL como comandos CREATE, ALTER e DROP em qualquer objeto no Banco de Dados ou Servidor.
- Acionadores de logon – para a tentativa de efetuar login em uma instância do SQL Server durante o evento LOGON.
Acionadores de DML no SQL Server
Acionadores DML são aqueles disparados por comandos DML (INSERT, UPDATE ou DELETE) em tabelas ou exibições. Podemos criar esses gatilhos nessas tabelas ou visualizações apenas onde os dados residem para que eles aceitem comandos DML neles.
Com base no tempo de disparo/invocação, os gatilhos DML podem ser dos seguintes tipos:
- PARA ou DEPOIS Tipo de gatilho – o gatilho é invocado após a conclusão bem-sucedida da instrução DML em uma tabela ou exibição. Observação:é possível criar o gatilho AFTER apenas em Tabelas, não em Visualizações.
- EM VEZ DE Tipo de acionador – O acionador será invocado antes (EM VEZ DE) do script DML executado na tabela ou visualização.
SQL Server cria duas tabelas especiais ou lógicas chamadas INSERTED e ATUALIZADO sempre que os gatilhos DML são criados em tabelas ou exibições. Essas tabelas lógicas ajudam a identificar as alterações de registro que ocorrem por meio de operações INSERT/UPDATE/DELETE. Dessa forma, garante que os gatilhos DML funcionem de forma eficaz.
- INSERIDO A tabela lógica armazena cópias de novos registros de registros modificados durante as operações INSERT e UPDATE. Quando um novo registro é adicionado à tabela real, ele também é adicionado à tabela INSERTED. Da mesma forma, quaisquer alterações nos registros existentes por meio da instrução UPDATE movem os valores mais recentes para a tabela INSERTED e os valores mais antigos – para a tabela lógica DELETED.
- EXCLUÍDO A tabela lógica armazena cópias de valores mais antigos durante as operações UPDATE e DELETE. Sempre que um registro é atualizado, os valores mais antigos são copiados para a tabela DELETED. Sempre que um registro é excluído da tabela real, os registros são inseridos na tabela DELETED.
SQL Server tem funções internas COLUMN_UPDATED() e ATUALIZAR() para identificar a presença de operações INSERT ou UPDATE na coluna específica.
- COLUMN_UPDATED() retorna valores varbinary de colunas que foram afetadas pelas operações INSERT ou UPDATE.
- ATUALIZAR() aceita o nome da coluna como um parâmetro de entrada e retorna as informações se essa coluna tem alguma alteração de dados como parte das operações INSERT ou UPDATE.
O NÃO PARA REPLICAÇÃO pode ser usada em gatilhos DML para evitar dispará-los para alterações provenientes do processo de replicação.
DML Triggers também podem ser criados com o .Net Framework Common Language Runtime (CLR).
O sistema DMV sys.triggers armazena a lista de todos os gatilhos com escopo de banco de dados. Podemos usar a consulta abaixo para buscar os detalhes de todos os gatilhos DML em um banco de dados:
SELECT *
FROM sys.triggers
WHERE type = 'TR';
As definições do gatilho DML podem ser visualizadas se o gatilho não estiver criptografado. Usamos qualquer uma das opções abaixo:
sys.sql_modules
SELECT OBJECT_SCHEMA_NAME(object_id, db_id()) Schema_name, OBJECT_NAME(object_id) Trigger_Name, definition
FROM sys.sql_modules
WHERE object_id = OBJECT_ID(<trigger_name>);
OBJECT_DEFINITION() função
SELECT OBJECT_DEFINITION (OBJECT_ID(<trigger_name>)) AS ObjectDefinition;
sp_helptext procedimento armazenado
EXEC sp_helptext '<trigger_name>';
Todos os eventos DML possíveis estão disponíveis em sys.events tabela. Podemos visualizá-los usando a consulta abaixo:
SELECT *
FROM sys.events;
Sintaxe do gatilho DML
CREATE TRIGGER <trigger_name>
ON <schema_name.table_name | schema_name.view_name >
[ WITH <DML_trigger_option> [ ,...n ] ]
{ FOR | AFTER | INSTEAD OF} <event_type>
AS { sql_statement | EXTERNAL NAME <method specifier> }
Para fins de demonstração, criei duas tabelas chamadas Vendas e Histórico de vendas com poucas colunas no banco de dados de teste:
CREATE TABLE Sales (SalesId int IDENTITY NOT NULL, SalesDate datetime, Itemcount int, price money);
CREATE TABLE SalesHistory (SalesId int NOT NULL, SalesDate datetime, Itemcount int, price money, ChangeType varchar(10), ChangeDate datetime DEFAULT GETDATE(), ChangedUser varchar(100) DEFAULT SUSER_NAME());
GO
Como você pode ver, o Histórico de vendas A tabela tem 3 colunas adicionais para rastrear a data de modificação e o nome de usuário que invocou a alteração. Se necessário, podemos ter mais uma coluna Identidade definido e torná-lo uma chave primária também.
INSERIR gatilho
Criamos um simples acionador INSERT nas Vendas tabelas para INSERT quaisquer novas alterações de registro em SalesHistory tabela. Use o script abaixo:
CREATE TRIGGER TR_INS_Sales ON Sales
FOR INSERT
AS
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'INSERT'
FROM inserted
END
GO
Para explicar a sintaxe do gatilho, criamos um gatilho DML chamado TR_INS_Sales nas Vendas tabela. Ele deve disparar o gatilho apenas para as operações INSERT – inserindo registros no SalesHistory tabela da tabela inserida.
Como sabemos, inserido é uma tabela lógica que captura as alterações que ocorrem na tabela Origem (o Vendas tabela no nosso caso).
Podemos ver outra tabela lógica especial excluída no gatilho UPDATE porque o excluído tabela não é aplicável para gatilhos INSERT.
Vamos adicionar um novo registro para verificar se os registros foram inseridos no SalesHistory mesa automaticamente.
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-01-01', 5, 100);
Mesmo que tenhamos inserido apenas um registro em Vendas tabela, obtemos 2 linhas de 1 linha afetada mensagem. O segundo registro aparece devido à operação INSERT como parte do acionador invocado pela atividade INSERT em Vendas table – inserindo um registro no SalesHistory tabela.
Vamos verificar os registros nas Vendas e Histórico de vendas tabelas:
SELECT *
FROM Sales
SELECT *
FROM SalesHistory
Podemos ver que ChangeDate e Usuário alterado são preenchidos automaticamente. É porque projetamos nossa História tabela com os valores padrão como GETDATE() e SUSER_NAME() .
Os usuários finais podem ver por meio do Trigger ou de algum outro meio que seu INSERT foi auditado por meio da 1 linha adicional afetada mensagem. Se você deseja monitorar as alterações sem informar os usuários, é necessário aplicar o SET ROWCOUNT ON comando. Ele suprime os resultados exibidos para operações DML que ocorrem dentro do gatilho.
Vamos ALTER nosso gatilho usando o script com o SET ROWCOUNT ON opção e vê-lo em ação:
ALTER TRIGGER TR_INS_Sales ON Sales
FOR INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'INSERT'
FROM inserted
END
GO
Agora, inserimos outro registro em Vendas tabela:
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-02-01', 1, 50);
Podemos ver apenas uma única 1 linha afetada mensagem. Assim, o público-alvo pode não ser notificado de que suas ações estão sendo monitoradas.
Vamos verificar se o gatilho INSERT foi invocado verificando as Vendas e Histórico de vendas mesas.
Sim, o evento INSERT nas Vendas tabela foi acionada com sucesso. Os registros foram inseridos no SalesHistory tabela sem notificar os usuários.
Portanto, se você criar acionadores para fins de auditoria, o SET NOCOUNT ON é necessário. Permite a auditoria sem alertar ninguém.
ATUALIZAR Acionador
Antes de criar um acionador UPDATE real nas Vendas tabela, vamos novamente nos referir às tabelas lógicas especiais inseridas e excluídas. Crie um acionador UPDATE de amostra em Vendas tabela:
CREATE TRIGGER TR_UPD_Sales ON Sales
FOR UPDATE
AS
BEGIN
SELECT * FROM inserted
SELECT * FROM deleted
END
GO
O gatilho UPDATE foi criado com sucesso. Agora, vamos INSERIR um novo registro incorretamente. Mais tarde, vamos atualizá-lo para verificar o gatilho UPDATE em ação:
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-02-01', 1, 50);
Temos os registros abaixo nas Vendas e Histórico de vendas tabela:
Vamos atualizar SalesId =3 nas Vendas tabela com novos valores. Veremos os dados nas tabelas inseridas e excluídas:
UPDATE Sales
SET SalesDate = '2021-03-01'
, Itemcount = 3
, price = 500
WHERE SalesId = 3
Quando a operação UPDATE ocorrer, todos os valores novos ou modificados estarão disponíveis na tabela inserida e os valores antigos estarão disponíveis na tabela excluída:
Agora, vamos modificar o gatilho UPDATE com o script abaixo e verificá-lo em ação:
ALTER TRIGGER TR_UPD_Sales ON Sales
FOR UPDATE
AS
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'UPDATE'
FROM inserted
END
GO
O trigger UPDATE foi alterado com sucesso, e podemos executar o mesmo script UPDATE novamente:
Agora, podemos ver a 1 linha afetada mensagem duas vezes. Indica a execução da operação UPDATE no Vendas tabela e a operação INSERT no SalesHistory tabela. Vamos verificar isso selecionando em ambas as tabelas:
A atividade UPDATE foi rastreada no SalesHistory tabela como um novo registro. Antes desse registro, temos outro mostrando quando o registro foi inserido primeiro.
EXCLUIR gatilho
Até agora, testamos o FOR ou DEPOIS tipo de gatilhos para operações INSERT ou UPDATE. Agora, podemos tentar usar o EM VEZ DE tipo de gatilho DML para a operação DELETE. Use o script abaixo:
CREATE TRIGGER TR_DEL_Sales ON Sales
INSTEAD OF DELETE
AS
BEGIN
RAISERROR ('Notify Sales Team', 16, 10);
END
GO
O gatilho DELETE foi criado com sucesso. Ele enviará uma mensagem de erro ao cliente em vez de executar o comando DELETE no Vendas tabela.
Vamos tentar excluir o registro SalesID =3 das Vendas tabela usando o script abaixo:
DELETE FROM Sales
WHERE SalesId = 3
Impedimos que os usuários excluíssem registros das Vendas tabela. O gatilho gerou uma mensagem de erro.
Vamos também verificar se o registro foi excluído das Vendas tabela e se houve alguma alteração no SalesHistory tabela:
Como executamos o script do gatilho antes da instrução DELETE real usando o gatilho INSTEAD OF, a operação DELETE em SalesId=3 não foi bem-sucedida. Portanto, nenhuma alteração foi refletida nas Vendas e Histórico de vendas tabela.
Vamos modificar o gatilho usando o script abaixo para identificar a tentativa DELETE na tabela:
ALTER TRIGGER TR_DEL_Sales ON Sales
INSTEAD OF DELETE
AS
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'DELETE ATP'
FROM deleted
END
GO
O gatilho foi modificado com sucesso. Vamos excluir o SalesId =3 registro das Vendas mesa novamente:
A execução da instrução DELETE mostra a 1 linha afetada mensagem duas vezes. Vamos verificar os registros nas Vendas e Histórico de vendas tabelas para ver o que exatamente acontece lá:
A lógica usada no gatilho DELETE era capturar qualquer tentativa de DELETE na tabela sem realmente excluir o registro das Vendas tabela usando o INSTEAD OF acionar. Podemos confirmar que o registro não foi excluído das Vendas tabela e um novo registro foi inserido no SalesHistory tabela.
Um único gatilho para lidar com a operação INSERT, UPDATE e DELETE
Até agora, criamos 3 gatilhos para lidar com as operações INSERT, UPDATE e DELETE em uma única tabela. Se tivermos vários gatilhos, será difícil gerenciá-los, principalmente se não estiverem devidamente documentados. Pode haver problemas de desempenho se os desenvolvedores usarem lógica contraditória em vários gatilhos.
Eu pessoalmente recomendo usar um único gatilho com toda a lógica combinada para evitar possíveis perdas de dados ou problemas de desempenho. Podemos tentar combinar 3 gatilhos em um único gatilho para um melhor desempenho. Mas antes de fazer isso, vamos examinar como DROP gatilhos existentes e como desabilitar ou habilitar gatilhos.
Solte o gatilho
Para mesclar 3 gatilhos em um único, primeiro precisamos DROP esses 3 gatilhos. É possível por meio de abordagens SSMS e T-SQL.
No SSMS, expanda o Teste banco de dados > Tabelas > Vendas tabela> Acionadores .
Podemos ver nossos 3 gatilhos criados até agora:
Para soltar um gatilho, basta clicar com o botão direito nele> Excluir > OK .
Se você preferir usar o T-SQL, veja a sintaxe abaixo para descartar o gatilho:
DROP TRIGGER <trigger_name>
Existe o TR_INS_Sales acionador que criamos nas Vendas tabela. O roteiro será:
DROP TRIGGER TR_INS_Sales
Importante :a eliminação de uma tabela elimina todos os gatilhos por padrão.
Desativar e ativar o gatilho
Em vez de descartar o gatilho, podemos desativá-lo temporariamente com o botão Desativar acionar opção via SSMS ou T-SQL.
No SSMS, clique com o botão direito do mouse no Nome do acionador> Desativar . Uma vez desativado, o gatilho não será acionado até que você o habilite novamente.
Enquanto o Acionador está funcionando, o Ativar opção está acinzentada. Ao desativá-lo, o Ativar opção ficará visível e ativa.
Se preferir usar T-SQL, você pode desabilitar e habilitar triggers usando os scripts abaixo:
-- To Disable all triggers on a specific table
DISABLE TRIGGER ALL ON <table_name>;
-- To Disable a specific trigger on a table
DISABLE TRIGGER <trigger_name> ON <table_name>;
-- To Enable all triggers on a specific table
ENABLE TRIGGER ALL ON <table_name>;
-- To Enable a specific trigger on a table
ENABLE TRIGGER <trigger_name> ON <table_name>;
Para desativar e ativar nossas TR_INS_Sales específicas acionar as Vendas tabela, usamos os scripts abaixo:
-- To Disable TR_INS_Sales trigger on Sales table
DISABLE TRIGGER TR_INS_Sales ON Sales;
-- To Enable TR_INS_Sales trigger on Sales table
ENABLE TRIGGER TR_INS_Sales ON Sales;
Assim, aprendemos como DROP , DESATIVAR , e ATIVAR gatilhos. Vou descartar 3 gatilhos existentes e criar um único gatilho cobrindo todas as 3 operações ou inserindo, atualizando e excluindo usando o script abaixo:
DROP TRIGGER TR_INS_Sales
DROP TRIGGER TR_UPD_Sales
DROP TRIGGER TR_DEL_Sales
GO
CREATE TRIGGER TR_INS_UPD_DEL_Sales ON Sales
FOR INSERT, UPDATE, DELETE
AS
BEGIN
IF (SELECT COUNT (*) FROM deleted) = 0
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'INSERT'
FROM inserted
END
ELSE IF (SELECT COUNT (*) FROM inserted) = 0
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'DELETE'
FROM deleted
END
ELSE IF (UPDATE (SalesDate) OR UPDATE (ItemCount) OR UPDATE (Price))
BEGIN
INSERT INTO SalesHistory(SalesId,SalesDate,Itemcount,price,ChangeType)
SELECT SalesId
,SalesDate
,Itemcount
,price
,'UPDATE'
FROM inserted
END
END
GO
A criação do gatilho único foi bem-sucedida. Usamos a lógica para identificar a operação usando as tabelas inseridas e excluídas.
Para a operação INSERT, a tabela excluída não será preenchida. Para a operação DELETE, a tabela inserida não será preenchida. Podemos identificar essas operações facilmente. Se essas 2 condições não corresponderem, é uma operação UPDATE e podemos usar uma instrução ELSE simples.
Eu usei o UPDATE() função para mostrar como funciona. Se houvesse alguma atualização nessas colunas, a ação do gatilho UPDATE seria acionada. Também podemos usar o COLUMNS_UPDATED() função que discutimos anteriormente para identificar uma operação UPDATE também.
Vamos testar nosso novo gatilho inserindo um novo registro:
INSERT INTO Sales(SalesDate,Itemcount,price)
VALUES ('2021-04-01', 4, 400);
Verificando registros nas Vendas e Histórico de vendas tabelas mostra os dados como abaixo:
Vamos tentar atualizar SalesId =2 registro:
UPDATE Sales
SET price = 250
WHERE SalesId = 2;
Vamos tentar um script DELETE por meio deste procedimento em SalesId =4 registro:
DELETE FROM Sales
WHERE SalesId = 4;
Como podemos notar, SalesId =4 foi excluído das Vendas tabela, pois este é um FOR ou DEPOIS acionador, fazendo com que a operação DELETE tenha sucesso nas Vendas tabela e insira um registro no SalesHistory tabela.
Objetivo dos acionadores de DML
Os gatilhos DML servem de forma eficaz para os seguintes cenários:
- Acompanhe as alterações históricas das operações INSERT, UPDATE e DELETE em uma tabela específica.
- Audite os eventos DML que ocorrem em uma tabela sem expor a atividade de auditoria aos usuários.
- Impedir que alterações DML aconteçam em uma tabela por meio de INSTEAD OF aciona e alerta os usuários com uma mensagem de erro específica.
- Envie notificações para pessoas segmentadas ao atingir qualquer condição predefinida.
- Inicie o trabalho do SQL Server Agent ou qualquer outro processo sempre que atingir as condições predefinidas.
E você pode usá-los para quaisquer outros requisitos de lógica de negócios que possam ser implementados com instruções T-SQL.
Conclusão
O corpo do gatilho DML é semelhante ao procedimento armazenado. Podemos implementar qualquer lógica de negócios necessária, mas temos que ter cuidado ao escrever essa lógica envolvida para evitar possíveis problemas.
Embora o SQL Server dê suporte à criação de vários gatilhos em uma única tabela, é melhor consolidar em um único gatilho. Dessa forma, você pode manter os gatilhos facilmente e solucioná-los mais rapidamente. Sempre que os acionadores DML forem implementados para fins de auditoria, certifique-se de que SET NOCOUNT ON opção é usada de forma eficaz.
No próximo artigo, examinaremos os gatilhos DDL e os gatilhos de logon.