web-dev-qa-db-fra.com

Faire reculer la table tronquée

J'ai une procédure stockée qui fait ce qui suit:

BEGIN TRANSACTION

    -- Code to delete updated records from production (dbo) table
    DELETE FROM [dbo].[factMyTable]
    WHERE exists (SELECT *
        FROM [RAW].[MyTable]
        WHERE [RAW].[MyTable].[refno] = [dbo].[factMyTable].[refno]
        AND [RAW].[MyTable].[modification_dttm] >= [dbo].[factMyTable].[modification_dttm]
        )

    -- Code to perform the append of incremental records 
    INSERT INTO [dbo].[factMyTable]
    SELECT
         [refno]
        ,[field1]
        ,[field2]
        ,[field3]
        ,[FieldN]
        ,[modification_dttm]
    FROM [RAW].[MyTable]

-- Truncate stage table and get ready for next load
TRUNCATE TABLE [RAW].[MyTable]

COMMIT TRANSACTION

Comme vous pouvez le voir ci-dessus, j'ai une commande tronquée qui est contenue dans un bloc de transaction BEGIN/COMMIT. Cependant, j'ai eu une erreur lors de l'exécution de cette procédure stockée à la commande d'insertion, où un champ défini comme NOT NULL recevait une valeur NULL. Par conséquent:

  1. L'insertion des enregistrements de la table RAW dans la table dbo a été annulée; MAIS
  2. La troncature de la table RAW ne s'est pas annulée.

L'idée est que s'il y a une erreur avec l'insertion de données, la troncature ne devrait pas se produire.

Selon cet article , nous pouvons annuler la commande tronquer, mais peut-être que ma procédure stockée n'est pas correctement scriptée. Peut-être existe-t-il un moyen plus direct de s'assurer que la troncature ne se produit que si l'insertion ne renvoie aucune erreur? Comment pourrais-je m'y prendre?

3
pmdci
  1. L'insertion des enregistrements de la table RAW dans la table dbo a été annulée; MAIS
  2. La troncature de la table RAW ne s'est pas annulée.

Non, il n'y avait pas de rollback du tout, et voici la repro.

Avec xact_abort off c'est votre default set option J'ai créé 2 tables, puis j'ouvre la transaction et fais 2 inserts dont une (la seconde) échoue, j'ai ajouté select @@trancount et select des deux tableaux pour mieux voir ce qui se passe:

--set xact_abort on

if object_id('dbo.t1') is not null drop table dbo.t1;
if object_id('dbo.t2') is not null drop table dbo.t2;
go


create table dbo.t1 (col1 int);
insert into dbo.t1 values(1), (null);

create table dbo.t2 (col1 int not null);
go

begin transaction

    insert into dbo.t2
    values(-1);

    insert into dbo.t2
    select col1
    from dbo.t1;

    select @@trancount as [@@trancount before truncate];

    truncate table dbo.t1;

commit transaction;

select @@trancount as [@@trancount after commit];

select *
from dbo.t1;

select *
from dbo.t2;

enter image description here

Comme vous le voyez, aucun rollback n'a été créé, seulement votre commit. Vous insérez (-1) dans dbo.t2 et cette ligne est là en permanence. C'est parce que l'erreur

Msg 515, niveau 16, état 2, ligne 18 Impossible d'insérer la valeur NULL dans la colonne "col1", table "dbo.t2"; la colonne n'autorise pas les valeurs nulles.

est l'instruction se terminant uniquement . La deuxième instruction échoue donc aucune ligne n'a été insérée, mais l'insertion de (-1) n'a pas été annulée, et comme vous le voyez après l'erreur, votre transaction est toujours ouvert . C'est votre commit qui valide l'insertion de -1 et la table truncation.


Maintenant, le deuxième test: décommenter set xact_abort on, cela fera statment terminating seule erreur soit batch aborting, tous les statements dans la transaction seront rolled back et l'exécution sera interrompue dès que l'erreur se produira.

Donc t1 la table ne sera jamais tronquée et l'insertion de (-1) dans t2 sera rolled back.

Et maintenant comment votre code doit être écrit:

set xact_abort on;

if object_id('dbo.t1') is not null drop table dbo.t1;
if object_id('dbo.t2') is not null drop table dbo.t2;
go

create table dbo.t1 (col1 int);
insert into dbo.t1 values(1), (null);

create table dbo.t2 (col1 int not null);
go

begin try
begin transaction

    insert into dbo.t2
    values(-1);

    insert into dbo.t2
    select col1
    from dbo.t1;

    select @@trancount as [@@trancount before truncate];

    truncate table dbo.t1;

commit transaction;
end try

begin catch
    select @@trancount as [@@trancount in catch before rollback];
    if @@rowcount > 0 rollback;
    throw;
end catch;

Votre code doit toujours activer xact_abort et il doit avoir try..catch bloquer.

Vous devez faire rollback depuis catch ad throw le error

4
sepupic

La référence de David est merveilleuse, mais si vous cherchez un exemple pratique de ce que vous devriez faire, voici un bloc pour vous. Je n'ai pas dupliqué votre exemple avec précision, car vous pouvez voir les problèmes sans lui.

Je crée un exemple de table et y insère quelques données. J'essaie intentionnellement d'insérer un NULL dans la table dbo.Fact, ce qui n'est pas autorisé.

J'encapsule ensuite mes relevés dans un bloc TRY/CATCH. Dans le bloc Catch, j'ai ajouté une commande de restauration ainsi qu'un lancer afin que l'erreur d'origine revienne au client. Si vous omettez le lancer, aucune erreur ne reviendra.

Si vous souhaitez voir votre comportement d'origine, vous devez supprimer le TRY/CATCH et simplement y avoir la transaction BEGIN et COMMIT. Puisque même avec le try/catch, SQL annule correctement la transaction.

/** Build up our sample tables and some data.
    **/ 

DROP TABLE IF EXISTS dbo.RawTable;
DROP TABLE IF EXISTS dbo.Fact;

CREATE TABLE dbo.RawTable
    (
    RefNo CHAR(10) NOT NULL
    , field1 CHAR(10) NULL
    , field2 CHAR(10) NULL
    );

CREATE TABLE dbo.Fact
    (
    RefNo CHAR(10) NOT NULL
    , field1 CHAR(10) NOT NULL
    , field2 CHAR(10) NOT NULL
    );

INSERT INTO dbo.RawTable 
    (RefNo, field1, field2)
VALUES ('T1', NULL, 'T1F2')
    , ('T2', 'T2F1', 'T1F2');

SELECT RefNo, field1, field2 FROM dbo.RawTable; 

GO

BEGIN TRY

    BEGIN TRANSACTION;

    INSERT INTO dbo.Fact 
    (RefNo, field1, field2)
    SELECT RefNo, field1, field2
    FROM dbo.RawTable AS T;

    TRUNCATE TABLE dbo.RawTable;

    COMMIT;

END TRY
BEGIN CATCH

    --If we are here and there are open transactions, then rollback.
    IF @@TRANCOUNT >= 1
    BEGIN
        ROLLBACK;
    END

    --We want the error to bubble back to the client so they know something went wrong.
    ;THROW;

END CATCH

GO

--Show some data.
SELECT RefNo, field1, field2 FROM dbo.Fact; 
SELECT RefNo, field1, field2 FROM dbo.RawTable;
2
Jonathan Fite