Pergunta

Eu fiz isso antes em algum lugar eu tenho certeza disso!

Eu tenho uma tabela SQL Server 2000 que eu preciso para registrar mudanças para campos em atualizações e inserções em uma segunda tabela de registo. Uma versão simplificada da estrutura que estou usando é abaixo:

MainTable
ID varchar(10) PRIMARY KEY
DESCRIPTION varchar(50)

LogTable
OLDID varchar(10)
NEWID varchar(10)

Para qualquer outro campo algo como isso iria funcionar muito bem:

Select i.DESCRIPTION As New, d.DESCRIPTION As Old 
From Inserted i
LEFT JOIN Deleted d On i.ID=d.ID

... Mas, obviamente, a junção iria falhar se ID foi alterado.

Eu não posso modificar as tabelas em forma, o único poder que eu tenho neste banco de dados é criar um gatilho.

Como alternativa há alguém que pode me ensinar o tempo de viagem e eu vou voltar para o passado e me pergunto de volta, em seguida, como eu fiz isso? Cheers:)


Editar:

Eu acho que eu preciso esclarecer algumas coisas aqui. Esta é não realmente o meu banco de dados , é um sistema pré-existente que eu tenho quase nenhum controle de, além de escrever esse gatilho.

A minha pergunta é como eu posso recuperar a chave primária de idade se disse chave primária foi alterada. Eu não preciso de ser dito que eu não deveria alterar a chave primária ou sobre perseguindo as chaves estrangeiras etc. Isso não é o meu problema:)

Foi útil?

Solução

É possível supor que as tabelas inseridas e excluídas que lhe é apresentado em um gatilho são garantidos para ser na mesma ordem?

Outras dicas

DECLARE @OldKey int, @NewKey int;

SELECT @Oldkey = [ID] FROM DELETED;
SELECT @NewKey = [ID] FROM INSERTED;

Isso só funciona se você tiver uma única linha. Caso contrário, você não tem "âncora" para ligar linhas antigas e novas. Portanto, verifique no seu gatilho para> 1 em inserido.

Eu não acho que é possível. Imagine se você tem 4 linhas na tabela:

1  Val1
2  Val2
3  Val3
4  Val4

Agora emitir a seguinte atualização:

UPDATE MainTable SET
ID = CASE ID WHEN 1 THEN 2 WHEN 2 THEN 1 ELSE ID END
Description = CASE ID WHEN 3 THEN 'Val4' WHEN 4 THEN 'Val3' ELSE Description END

Agora, como é que vai fazer a distinção entre o que aconteceu com as linhas 1 e 2 e o que aconteceu com as linhas 3 e 4. E mais importante, você pode descrever o que é diferente entre eles? Todas as coisas que lhe diz quais colunas foram atualizados não vai ajudá-lo.

Se é possível, neste caso, que não há uma chave adicional sobre a mesa (por exemplo, Descrição é UNIQUE), e suas regras de atualização permitirem, você pode escrever o gatilho para impedir atualizações simultâneas para ambas as teclas, e então você pode usar qualquer chave não foi atualizado para correlacionar as duas tabelas.

Se você tem de lidar com inserções / atualizações de várias linhas, e não há nenhuma chave alternativa que está garantido para não mudar, a única maneira que eu posso ver de fazer isso é usar um disparador INSTEAD OF. Por exemplo, no gatilho você poderia quebrar o original comando insert / update em um comando por linha, agarrando cada idade id antes de insert / update.

Dentro disparadores no SQL Server você tem acesso a duas tabelas: excluída e inserido. Ambos já foram mencionados. Veja como eles funcionam dependendo de qual ação o gatilho está disparando em:

Inserir operação

  • excluídos - não utilizado
  • inserido - contém as novas linhas que estão sendo adicionados à tabela

operação de exclusão

  • apagado - contém as linhas que estão sendo removidos da tabela
  • inserido - não utilizado

ATUALIZAÇÃO DE OPERAÇÃO

  • apagado - contém as linhas como eles existiriam antes da operação UPDATE
  • inserido - contém as linhas à medida que existiria após a operação UPDATE

Estes funcionam em todos os sentidos como mesas. Portanto, é perfeitamente possível usar uma operação baseada fileira como algo como o seguinte (existe operação só na mesa de auditoria, assim como DateChanged):

INSERT INTO MyAuditTable
(ID, FirstColumn, SecondColumn, ThirdColumn, Operation, DateChanged)
VALUES
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-Before', GETDATE()
FROM deleted
UNION ALL
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-After', GETDATE()
FROM inserted

---- ---- novo adicionar uma coluna de identidade para a mesa que a aplicação não pode mudar, então você pode usar essa nova coluna para se juntar ao inserido para as tabelas eliminadas dentro do gatilho:

ALTER TABLE YourTableName ADD
    PrivateID int NOT NULL IDENTITY (1, 1)
GO

---- ---- velho Nunca atualizar / valores-chave de mudança. Como você pode fazer isso e corrigir todas as suas chaves estrangeiras?

Eu não recomendo nunca usar um gatilho que não pode lidar com um conjunto de linhas.

Se você deve alterar a chave, inserir uma nova linha com a nova chave e valores, utilização SCOPE_IDENTITY () se é isso que você está fazendo. Excluir a linha antiga. Log para a linha antiga que foi alterado para chave da nova linha, que agora você deve ter. Espero que não há nenhuma chave estrangeira na chave alterada em seu log ...

Você pode criar uma nova coluna de identidade na tabela MainTable (nomeado por exemplo CorrelationId) e correlacionar inserido e tabelas apagados através desta coluna. Esta nova coluna deve ser transparente para o código existente.

INSERT INTO LOG(OLDID, NEWID)
SELECT deleted.id AS OLDID, inserted.id AS NEWID
FROM inserted 
INNER JOIN deleted 
    ON inserted.correlationid = deleted.correlationid

Preste atenção, você pode inserir registros duplicados na tabela de log.

É claro que ninguém deve ser alterar a chave primária na tabela - mas isso é exatamente o que desencadeia são suposto ser para (em parte), é manter as pessoas de fazer coisas que não deve fazer. É uma tarefa trivial em Oracle ou MySQL para escrever um gatilho que intercepta alterações em chaves primárias e pára-los, mas não nada fácil em SQL Server.

O que você, naturalmente, gostaria de ser capaz de fazer seria a de simplesmente fazer algo como isto:

if exists
  (
  select *
    from inserted changed
           join deleted old
   where changed.rowID = old.rowID
     and changed.id != old.id
  )
... [roll it all back]

É por isso que as pessoas saem googling para o equivalente SQL Server de ROWID. Bem, o SQL Server não tê-lo; então você tem que vir para cima com uma outra abordagem.

Um rápido, mas infelizmente não à prova de bombas, a versão é escrever um em vez de gatilho atualização que olha para ver se alguma das linhas inseridas ter uma chave primária não encontrado na tabela atualizada ou vice-versa. Este seria pegar a maioria, mas não todos, dos erros:

if exists
  (
  select *
    from inserted lost
           left join updated match
             on match.id = lost.id
   where match.id is null
  union
  select *
    from deleted new
           left join inserted match
             on match.id = new.id
    where match.id is null
  )
  -- roll it all back

Mas isso ainda não pegar uma atualização como ...

update myTable
   set id = case
              when id = 1 then 2 
              when id = 2 then 1
              else id
              end

Agora, eu tentei fazer a suposição de que as tabelas inseridas e excluídas são ordenados de tal forma que cursoring através das tabelas inseridas e excluídas simultaneamente lhe dará linhas corretamente correspondentes. E isso parece funcionar. Na verdade você transformar o gatilho para o equivalente dos para-cada-gatilhos de linha disponíveis no Oracle e obrigatórias no MySQL ... mas eu imagino o desempenho será ruim sobre atualizações em massa uma vez que este não é um comportamento nativo para SQL Server. Também depende de uma suposição de que eu não posso realmente encontrar documentado em qualquer lugar e por isso estou relutante em confiar. Mas o código estruturado dessa forma parece funcionar corretamente em minha instalação do SQL Server 2008 R2. O script no final deste post destaca tanto o comportamento da solução fast-mas-não-à prova de bombas e o comportamento do segundo, pseudo-solução Oracle.

Se alguém poderia me aponte para algum lugar onde minha suposição é documentado e garantida pela Microsoft eu seria um cara muito grato ...

begin try
  drop table kpTest;
end try
begin catch
end catch
go

create table kpTest( id int primary key, name nvarchar(10) )
go

begin try
  drop trigger kpTest_ioU;
end try
begin catch
end catch
go

create trigger kpTest_ioU on kpTest
instead of update
as
begin
  if exists
    (
    select *
      from inserted lost
             left join deleted match
               on match.id = lost.id
     where match.id is null
    union
    select *
      from deleted new
             left join inserted match
               on match.id = new.id
      where match.id is null
    )
      raisError( 'Changed primary key', 16, 1 )
  else
    update kpTest
       set name = i.name
      from kpTest
             join inserted i
               on i.id = kpTest.id
    ;
end
go

insert into kpTest( id, name ) values( 0, 'zero' );
insert into kpTest( id, name ) values( 1, 'one' );
insert into kpTest( id, name ) values( 2, 'two' );
insert into kpTest( id, name ) values( 3, 'three' );

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- This throws an error, appropriately
update kpTest set id = 5, name = 'FIVE' where id = 1
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- This allows the change, inappropriately
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
     , name = UPPER( name )
go

select * from kpTest

/*
0   ZERO
1   TWO   -- WRONG WRONG WRONG
2   ONE   -- WRONG WRONG WRONG
3   THREE
*/

-- Put it back
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
     , name = LOWER( name )
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

drop trigger kpTest_ioU
go

create trigger kpTest_ioU on kpTest
instead of update
as
begin
  declare newIDs cursor for select id, name from inserted;
  declare oldIDs cursor for select id from deleted;
  declare @thisOldID int;
  declare @thisNewID int;
  declare @thisNewName nvarchar(10);
  declare @errorFound int;
  set @errorFound = 0;
  open newIDs;
  open oldIDs;
  fetch newIDs into @thisNewID, @thisNewName;
  fetch oldIDs into @thisOldID;
  while @@FETCH_STATUS = 0 and @errorFound = 0
    begin
      if @thisNewID != @thisOldID
        begin
          set @errorFound = 1;
          close newIDs;
          deallocate newIDs;
          close oldIDs;
          deallocate oldIDs;
          raisError( 'Primary key changed', 16, 1 );
        end
      else
        begin
          update kpTest
             set name = @thisNewName
           where id = @thisNewID
          ;
          fetch newIDs into @thisNewID, @thisNewName;
          fetch oldIDs into @thisOldID;
        end
    end;
  if @errorFound = 0
    begin
      close newIDs;
      deallocate newIDs;
      close oldIDs;
      deallocate oldIDs;
    end
end
go

-- Succeeds, appropriately
update kpTest
   set name = UPPER( name )
go

select * from kpTest;

/*
0   ZERO
1   ONE
2   TWO
3   THREE
*/

-- Succeeds, appropriately
update kpTest
   set name = LOWER( name )
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/


-- Fails, appropriately
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- Fails, appropriately
update kpTest 
   set id = id + 1
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- Succeeds, appropriately
update kpTest 
   set id = id, name = UPPER( name )
go

select * from kpTest;

/*
0   ZERO
1   ONE
2   TWO
3   THREE
*/

drop table kpTest
go
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top