Movendo linhas de uma tabela para outra

9

Estou movendo registros de um banco de dados para outro, como parte do processo de arquivamento. Quero copiar as linhas para a tabela de destino e excluir as mesmas linhas da tabela de origem.

Minha pergunta é: qual é a maneira mais eficiente de verificar se a primeira inserção foi bem-sucedida antes de excluir as linhas.

Minha idéia é essa, mas sinto que há uma maneira melhor:

@num_records=select count(ID) from Source_Table where (criteria for eligible rows)

insert * into Destination_Table where (criteria for eligible rows)

if ((select count(ID) from Destination_Table where (criteria) )=@numrecords)

delete * from Source_Table where (criteria)

É melhor / possível combiná-lo com a função RAISERROR? Obrigado!

Dina
fonte

Respostas:

13

Eu recomendaria a sintaxe TRY / CATCH junto com transações explícitas. Minha suposição para esta solução é que o motivo da falha de inserção é algum tipo de erro SQL interceptável (como violação de chave, incompatibilidade de tipo de dados / erro de conversão, etc.). A estrutura ficaria assim:

BEGIN TRAN

BEGIN TRY
  INSERT INTO foo(col_a,col_b,col_c,recdate)
  SELECT col_a,col_b,col_c,recdate
  FROM bar
  WHERE recdate BETWEEN @startdate AND @enddate

  DELETE FROM bar
  WHERE recdate BETWEEN @startdate AND @enddate

  COMMIT TRAN
END TRY
BEGIN CATCH
  ROLLBACK TRAN
END CATCH

Da maneira como essa estrutura funciona, se ocorrer algum erro no INSERT ou no DELETE, a ação inteira será revertida. Isso garante que toda a ação seja bem-sucedida para ser concluída. Se você acha que isso é necessário, você pode combiná-lo com o THROW for 2012 ou o RAISERROR em 2008 e anterior para adicionar lógica adicional e forçar uma reversão se essa lógica não for atendida.

Outra opção é examinar SET XACT_ABORT ON , embora eu sinta que a sintaxe TRY / CATCH oferece mais granularidade.

Mike Fal
fonte
19

Se a sua tabela de arquivamento não .

  • Ativaram gatilhos definidos nele.
  • Participe dos dois lados de uma restrição FOREIGN KEY.
  • Possui restrições CHECK ou regras ativadas.

Você também pode fazer isso de uma só vez.

DELETE FROM source_table
OUTPUT deleted.Foo,
       deleted.Bar,
       SYSUTCDATETIME()
INTO archive_table(Foo, Bar, archived)
WHERE  Foo = 1; 

Isso terá êxito ou falhará como uma unidade e também evitará possíveis condições de corrida, com a adição de linhas entre INSERTo arquivo e o arquivo DELETE(embora sua WHEREcláusula possa tornar isso extremamente improvável).

Martin Smith
fonte
Nenhuma das acima. Suponho que poderia seguir esse caminho, gosto de minimalismo de código. Eu só não quero perder registros se a inserção falhar por qualquer motivo. (ou seja: bloqueio de tabela, tempos limites, etc.) Obrigado!
Dina
@ Dina - Você estava indicando que é possível com a OUTPUTcláusula? Não é porque é tudo uma afirmação. Também evita problema de ter que ler as linhas duas vezes (e, possivelmente, perder linhas que foram adicionadas entre a leitura para a inserção e a leitura para a exclusão)
Martin Smith
Sim, foi isso que eu quis dizer. Obrigado, entendo o seu ponto.
Dina
FWIW - esse método causará um crescimento do arquivo de log próximo ao tamanho da tabela original. Certifique-se de que você pode viver com isso. Se não puder, divida-o em lotes com DELETE TOP (N) e um loop While que verifica a variável @@ rowcount.
Wjdavis5
1

A maneira como pensei em arquivar (que, com certeza, também não é perfeito), é adicionar uma coluna de bits à nova tabela de arquivamento, como 'Arquivado', que teria o valor 1 após a transferência bem-sucedida de um registro. E depois de transferir todos os registros, você pode executar uma operação de exclusão enquanto procura pelo valor do campo 'Arquivado' de '1', ou seja, Verdadeiro da tabela arquivada.

E eu concordo com Mike em usar o Try / Catch.

avakharia
fonte
1

Tente o seguinte:

INSERT dbo.newtable(
      name,
      department,
      Salary
) SELECT 
            name,
            FirstName,
            Lastname
      FROM    (
           DELETE dbo.oldtable
           OUTPUT
                   DELETED.name,
                   DELETED.department,
                   DELETED.Salary
           WHERE ID  IN ( 1001, 1003, 1005 )
      ) AS RowsToMove;

SELECT * FROM dbo.newtable;
SELECT * FROM dbo.oldtable;
Nadeem
fonte