Pergunta

Considere o gatilho:

ALTER TRIGGER myTrigger 
   ON someTable 
   AFTER INSERT
AS BEGIN
  DELETE FROM someTable
         WHERE ISNUMERIC(someField) = 1
END

Eu tenho uma tabela, SomeTable, e eu estou tentando impedir as pessoas de inserção de registros incorretos. Para efeitos desta questão, um registro ruim tem um campo "SomeField" isso é tudo numérico.

É claro que o caminho certo para fazer isso não é com um gatilho, mas eu não controlar o código-fonte ... apenas o banco de dados SQL. Então, eu realmente não pode impedir a inserção da má fila, mas posso excluí-lo imediatamente, o que é bom o suficiente para minhas necessidades.

Os trabalhos de gatilho, com um problema ... quando ele é acionado, ele nunca parece excluir o registro ruim just-inserida ... ele apaga todos os registros maus velhos, mas não excluir o registro ruim just-inserida . Portanto, há muitas vezes um registro ruim por aí que não é excluído até que alguém vem e faz outra INSERT.

Este é um problema no meu entendimento de gatilhos? São linhas recém-inseridas ainda não comprometidos, enquanto o gatilho está a funcionar?

Foi útil?

Solução

Triggers não pode modificar os dados alterados (Inserted ou Deleted) senão você poderá obter recursão infinita como as mudanças invocado o gatilho novamente. Uma opção seria para o gatilho para reverter a transação.

Editar: A razão para isto é que o padrão para SQL é que linhas inseridas e excluídas não podem ser modificados pelo gatilho. A razão subjacente para é que as modificações podem causar recursão infinita. No caso geral, esta avaliação pode envolver múltiplos gatilhos em uma cascata mutuamente recursiva. Ter um sistema de decidir de forma inteligente se deve permitir que essas atualizações é computacionalmente intratável, essencialmente, uma variação sobre o parada problema.

A solução aceita para isso é não permitir o gatilho para alterar os dados de mudança, embora possa reverter a transação.

create table Foo (
       FooID int
      ,SomeField varchar (10)
)
go

create trigger FooInsert
    on Foo after insert as
    begin
        delete inserted
         where isnumeric (SomeField) = 1
    end
go


Msg 286, Level 16, State 1, Procedure FooInsert, Line 5
The logical tables INSERTED and DELETED cannot be updated.

Algo como isso irá reverter a transação.

create table Foo (
       FooID int
      ,SomeField varchar (10)
)
go

create trigger FooInsert
    on Foo for insert as
    if exists (
       select 1
         from inserted 
        where isnumeric (SomeField) = 1) begin
              rollback transaction
    end
go

insert Foo values (1, '1')

Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.

Outras dicas

Você pode inverter a lógica. Em vez de excluir uma linha inválida depois de ter sido inserido, escrever um gatilho INSTEAD OF para inserir única se você verificar a linha é válido.

CREATE TRIGGER mytrigger ON sometable
INSTEAD OF INSERT
AS BEGIN
  DECLARE @isnum TINYINT;

  SELECT @isnum = ISNUMERIC(somefield) FROM inserted;

  IF (@isnum = 1)
    INSERT INTO sometable SELECT * FROM inserted;
  ELSE
    RAISERROR('somefield must be numeric', 16, 1)
      WITH SETERROR;
END

Se o seu aplicativo não quer lidar com erros (como Joel diz é o caso em seu app), então não RAISERROR. Basta fazer o gatilho silenciosamente não fazer uma inserção que não é válido.

Eu corri isso em SQL Server Express 2005 e ele funciona. Note-se que desencadeia INSTEAD OF não causa recursão se inserir na mesma tabela para a qual o gatilho está definido.

Aqui está a minha versão modificada do código do Bill:

CREATE TRIGGER mytrigger ON sometable
INSTEAD OF INSERT
AS BEGIN
  INSERT INTO sometable SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 1 FROM inserted;
  INSERT INTO sometableRejects SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 0 FROM inserted;
END

Isso permite a inserção sempre bem sucedidos, e quaisquer registros falsos são jogados em seus sometableRejects onde você pode lidar com eles mais tarde. É importante fazer campos seu rejeições o uso da tabela nvarchar para tudo - não ints, tinyints, etc - porque se eles estão ficando rejeitada, é porque os dados não é o que você esperava que fosse

.

Isso também resolve o recorde múltipla inserção problema, o que fará com gatilho de Bill para falhar. Se você inserir registros dez simultaneamente (como se você fizer um select-insert-a) e apenas um deles é falso, gatilho de Bill teria sinalizado todos eles tão ruim. Este lida com qualquer número de bons e maus registros.

Eu usei esse truque em um projeto de armazenamento de dados, onde a aplicação de inserção não tinha idéia se a lógica do negócio era bom, e nós fizemos a lógica de negócios em triggers vez. Verdadeiramente desagradável para o desempenho, mas se você não pode deixar a inserção falhar, ela não funciona.

Eu acho que você pode usar restrição CHECK - é exatamente o que foi inventado para

.
ALTER TABLE someTable 
ADD CONSTRAINT someField_check CHECK (ISNUMERIC(someField) = 1) ;

A minha resposta anterior (também junto pode ser um exagero bit):

Eu acho que o caminho certo é usar em vez de gatilho para evitar que os dados errados de ser inserido (em vez de excluí-lo pós-factum)

UPDATE:. DELETE FROM um gatilho funciona tanto em MSSql 7 e MSSql 2008

Eu não sou nenhum guru relacional, nem uma padrões SQL sabichão. No entanto - ao contrário da resposta aceita - MSSQL lida muito bem com ambos r ecursive e nested avaliação gatilho . Eu não sei sobre outros RDBMSs.

As opções relevantes são 'recursivo gatilhos' e 'nested gatilhos' . disparadores aninhados estão limitados a 32 níveis e padrão para 1. gatilhos recursivos estão desativados por padrão, e não há nenhuma conversa de um limite - mas, francamente, eu nunca girou sobre eles, então eu não sei o que acontece com o inevitável estouro de pilha. Eu suspeito MSSQL só iria matar o seu SPID (ou há um limite recursivo).

Claro, isso só mostra que a resposta aceite tem o errado razão , não que isso é incorreto. No entanto, antes VEZ DE gatilhos, Lembro-me de escrever gatilhos ON INSERT que iria alegremente atualizar as linhas apenas inseridos. Esta tudo muito bem trabalhado, e como esperado.

Um teste rápido de delete a linha acabou de inserir também funciona:

 CREATE TABLE Test ( Id int IDENTITY(1,1), Column1 varchar(10) )
 GO

 CREATE TRIGGER trTest ON Test 
 FOR INSERT 
 AS
    SET NOCOUNT ON
    DELETE FROM Test WHERE Column1 = 'ABCDEF'
 GO

 INSERT INTO Test (Column1) VALUES ('ABCDEF')
 --SCOPE_IDENTITY() should be the same, but doesn't exist in SQL 7
 PRINT @@IDENTITY --Will print 1. Run it again, and it'll print 2, 3, etc.
 GO

 SELECT * FROM Test --No rows
 GO

Você tem algo mais acontecendo aqui.

A partir da criar documentação TRIGGER :

Suprimido e Inserida são lógicas (conceitual) tabelas. Eles são estruturalmente semelhante a tabela na que o gatilho está definido, isto é, a tabela na qual a ação do usuário é tentada, e mantenha os valores antigos ou novos valores das linhas que podem ser modificada pela ação do usuário. Para exemplo, para recuperar todos os valores na tabela excluída, use: SELECT * FROM deleted

De modo que, pelo menos, dá-lhe uma maneira de ver os novos dados.

Não consigo ver nada nos docs que especifica que você não vai ver os dados inseridos ao consultar a tabela normal, embora ...

Eu encontrei esta referência:

create trigger myTrigger
on SomeTable
for insert 
as 
if (select count(*) 
    from SomeTable, inserted 
    where IsNumeric(SomeField) = 1) <> 0
/* Cancel the insert and print a message.*/
  begin
    rollback transaction 
    print "You can't do that!"  
  end  
/* Otherwise, allow it. */
else
  print "Added successfully."

Eu não testei, mas logicamente parece que ele deve dp que você está depois ... em vez de apagar os dados inseridos, prevenir a inserção completamente, portanto, não exigindo que você tem que desfazer a inserção. Ele deve executar melhor e deve, portanto, em última análise, lidar com uma carga mais elevada com mais facilidade.

Edit: Claro, há é o potencial que se a inserção aconteceu dentro de uma transação de outra forma válida de que a operação Wole poderia ser revertida assim que você precisa para levar esse cenário em conta e determinar se a inserção de uma linha de dados inválido constituiria uma transação completamente inválido ...

É possível a inserção é válido, mas que uma atualização separada é feito depois que é inválido, mas não iria disparar o gatilho?

As técnicas descritas acima descrevem suas opções muito bem. Mas o que são os usuários vendo? Eu não posso imaginar como um conflito básico como este entre você e quem é responsável pelo software não pode acabar em confusão e antagonismo com os usuários.

Eu faria tudo o que podia encontrar alguma outra maneira de sair do impasse -. Porque outras pessoas podem ver facilmente qualquer mudança que você faz como uma escalada do problema

EDIT:

Eu vou marcar o meu primeiro "undelete" e admitir para postar o acima quando esta pergunta apareceu pela primeira vez. Eu, claro, se acovardou quando vi que era de Joel Spolsky. Mas parece que ele desembarcou em algum lugar perto. Fazer votos não precisa, mas vou colocá-lo no registro.

IME, gatilhos são tão raramente a resposta certa para qualquer coisa diferente de restrições de integridade de grão fino fora do reino de regras de negócio.

MS-SQL tem uma definição para evitar disparo gatilho recursiva. Esta é confirgured via sp_configure armazenados proceedure, onde você pode transformar gatilhos recursivos ou aninhadas ligado ou desligado.

Neste caso, seria possível, se você desligar disparadores recursiva para ligar o registro da tabela inserido via a chave primária, e fazer alterações no registro.

No caso específico em questão, não é realmente um problema, porque o resultado é para excluir o registro, que não vai refire o gatilho particular, mas, em geral, que poderia ser uma abordagem válida. Implementamos concorrência otimista desta forma.

O código para o seu gatilho que poderia ser usado desta forma seria:

ALTER TRIGGER myTrigger
    ON someTable
    AFTER INSERT
AS BEGIN
DELETE FROM someTable
    INNER JOIN inserted on inserted.primarykey = someTable.primarykey
    WHERE ISNUMERIC(inserted.someField) = 1
END

O seu "gatilho" está fazendo algo que um "gatilho" não é suposto estar fazendo. Você pode simples ter o seu executar o SQL Server Agent

DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1

cada 1 segundo ou assim. Enquanto você está nisso, que tal escrever um pouco agradável SP para parar o povo de programação a partir da inserção de erros em sua tabela. Uma coisa boa sobre SP de é que os parâmetros são do tipo seguro.

me deparei com esta pergunta olhando para obter detalhes sobre a seqüência de eventos durante uma instrução de inserção e gatilho. Acabei de codificação alguns testes breves para confirmar como SQL 2016 se comporta (expresso) - e pensei que seria apropriado para compartilhar como ele pode ajudar os outros em busca de informações similar.

Com base no meu teste, é possível selecionar dados da tabela "inserido" e usar isso para atualizar os dados em si inseridos. E, de interesse para mim, os dados inseridos não está visível para outras consultas até que os concluída gatilho altura em que o resultado final é visível (pelo menos melhor que pude testar). Eu não testei isso por gatilhos recursivos, etc. (eu esperaria o gatilho aninhada teria total visibilidade dos dados inseridos na tabela, mas isso é apenas um palpite).

Por exemplo - supondo que temos "mesa" na mesa com um campo inteiro "campo" e no campo "pk" chave primária e o seguinte código no nosso disparador de inserção:

select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
waitfor delay '00:00:15'

Nós inserir uma linha com o valor de 1 para "campo", em seguida, a linha vai acabar com o valor 2. Além disso - se eu abrir outra janela no SSMS e tentar: SELECT * FROM tabela onde pk = @pk

onde @pk é a chave primária I originalmente inserida, a consulta estará vazia até que os 15 segundos expirar e, em seguida, mostrar o valor atualizado (campo = 2).

Eu estava interessado no que os dados é visível para outras consultas, enquanto o gatilho está a executar (aparentemente sem novos dados). Eu testei com um adicional de exclusão, bem como:

select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
delete from table where pk=@pk
waitfor delay '00:00:15'

Mais uma vez, a inserção levou 15 segundos para executar. A consulta em execução em uma sessão diferente não apresentaram novos dados - durante ou após a execução da inserção + gatilho (embora eu esperaria que qualquer identidade iria incrementar mesmo que não aparece de dados a ser inserido)

.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top