Impossible d'insérer dans la colonne nouvellement créée

8

J'ai une table de test simple comme celle-ci:

CREATE TABLE MyTable (x INT);

Dans une transaction, j'essaie d'ajouter une colonne, puis d'insérer dans la colonne nouvellement créée:

BEGIN TRANSACTION;
    PRINT 'Adding column, ''SupplementalDividends'', to MyTable table.';

    ALTER TABLE MyTable
        ADD SupplementalDividends DECIMAL(18,6);

    PRINT 'Column added successfully....';

    PRINT 'Ready to INSERT into MyTable ...';

    INSERT INTO MyTable (x, SupplementalDividends)
        VALUES (1, 3.2);

    PRINT '**** CHANGES COMPLETE -- COMMITTING.';
COMMIT TRANSACTION;

Le problème est un message d'erreur lorsque j'exécute le code ci-dessus:

Invalid column name 'SupplementalDividends'.

Pourquoi cela provoque-t-il une erreur? Si j'ajoute la colonne dans un lot différent, en dehors de la transaction, cela fonctionnera. Mon problème est que je veux ajouter la colonne dans la transaction. Pourquoi l'erreur?

Tom Baxter
la source
4
Une petite mais importante suggestion - utilisez toujoursschema.ObjectName . Un bon début pour adapter les bonnes pratiques :-)
Kin Shah

Réponses:

6

Je veux juste préciser qu'il s'agit d'un problème au moment de l'exécution, pas de la compilation, et n'a rien à voir avec le fait qu'ils sont dans la même transaction . Par exemple, supposons que nous avons ce tableau:

CREATE TABLE dbo.floob(a int);

Le lot suivant analyse correctement (au moment de la compilation), mais lors de l'exécution, il obtient l'erreur que vous mentionnez dans la question:

BEGIN TRANSACTION;
  ALTER TABLE dbo.floob ADD b int;

  SELECT b FROM dbo.floob;
COMMIT TRANSACTION;

Dans un éditeur de requêtes dans Management Studio, vous pouvez contourner cela facilement, soit en:

  1. Mettre en surbrillance les deux premières lignes, appuyer sur Exécuter, puis mettre en surbrillance les deux secondes lignes et appuyer sur Exécuter; ou,
  2. Mettre un séparateur de lots entre eux, comme ceci:

    BEGIN TRANSACTION;
      ALTER TABLE dbo.floob ADD c int;
    
    GO
    
      SELECT c FROM dbo.floob;
    COMMIT TRANSACTION;

Si vous l'exécutez depuis l'extérieur de SQL Server (par exemple, en envoyant un lot SQL à partir de votre code d'application), vous pouvez simplement envoyer les deux lots séparément de manière similaire, ou si vous devez absolument l'envoyer en tant que lot unique, vous pouvez utiliser le SQL dynamique (comme dans la réponse de Gianluca ) pour différer la résolution des noms.

Aaron Bertrand
la source
1
S'il s'agit d'une erreur d'exécution, je devrais pouvoir l'attraper, n'est-ce pas? Mais je ne peux pas .
Andriy M
@AndriyM Impossible de détecter toutes les erreurs, non. Et certains se produisent quelque part entre l'analyse et l'exécution, comme celui-ci le montre.
Aaron Bertrand
@AndriyM De plus, notre ami Kiwi a ici une réponse qui implique la même chose, il y a des scénarios où les erreurs sont trop tard pour la compilation mais trop tôt pour l'exécution. Les deux réponses? SQL dynamique. (Quelques exemples dans l'article d'Erland également.)
Aaron Bertrand
10

C'est un problème contraignant. Le code est lié aux métadonnées de la table au moment de la compilation et il n'est pas lié tardivement. Essayez d'utiliser EXEC et SQL dynamique pour surmonter cette limitation:

BEGIN TRANSACTION;
    PRINT 'Adding column, ''SupplementalDividends'', to MyTable table.';

    ALTER TABLE MyTable
        ADD SupplementalDividends DECIMAL(18,6);

    PRINT 'Column added successfully....';

    PRINT 'Ready to INSERT into MyTable ...';
    EXEC('
    INSERT INTO MyTable (x, SupplementalDividends)
        VALUES (1, 3.2);
    ')

    PRINT '**** CHANGES COMPLETE -- COMMITTING.';
COMMIT TRANSACTION;

Une autre option consiste à utiliser une procédure stockée pour insérer les données: la liaison tardive s'applique à la procédure stockée, mais pas aux requêtes ad hoc. Encore une fois, vous devrez utiliser SQL dynamique pour créer la procédure, mais cela pourrait vous faciliter la transmission des paramètres:

BEGIN TRANSACTION;
    PRINT 'Adding column, ''SupplementalDividends'', to MyTable table.';

    ALTER TABLE MyTable
        ADD SupplementalDividends DECIMAL(18,6);

    PRINT 'Column added successfully....';

    PRINT 'Ready to INSERT into MyTable ...';

    EXEC('
    CREATE PROCEDURE insData @p1 int, @p2 DECIMAL(18,6)
    AS
    BEGIN 
        INSERT INTO MyTable (x, SupplementalDividends)
        VALUES (@p1, @p2);
    END')

    EXEC InsData 1, 3.2;

    PRINT '**** CHANGES COMPLETE -- COMMITTING.';
COMMIT TRANSACTION;

Une procédure stockée temporaire fonctionnerait également:

BEGIN TRANSACTION;
    PRINT 'Adding column, ''SupplementalDividends'', to MyTable table.';

    ALTER TABLE MyTable
        ADD SupplementalDividends DECIMAL(18,6);

    PRINT 'Column added successfully....';

    PRINT 'Ready to INSERT into MyTable ...';

    EXEC('
    CREATE PROCEDURE #insData @p1 int, @p2 DECIMAL(18,6)
    AS
    BEGIN 
        INSERT INTO MyTable (x, SupplementalDividends)
        VALUES (@p1, @p2);
    END')

    EXEC #InsData 1, 3.2;

    PRINT '**** CHANGES COMPLETE -- COMMITTING.';
COMMIT TRANSACTION;
spaghettidba
la source
1
J'aime la procédure temporaire - je n'y aurais pas pensé!
Max Vernon
1

Je suis curieux de savoir pourquoi vous modifiez une table et insérez une valeur dans cette colonne dans la même transaction.

Il n'y a presque aucune chance que vous ayez besoin de modifier à nouveau la table (de la même manière exacte), à ​​moins qu'elle ne soit annulée toutes les heures / jour, alors pourquoi le faire dans une transaction?

Votre modification n'a pas encore été validée et, par conséquent, il n'est pas trouvé lorsque vous essayez de l'insérer.

Mon conseil est de séparer vos tâches DDL et DML (au moins dans des transactions distinctes de toute façon).

MguerraTorres
la source
Je modifie la table et j'insère car cela fait partie d'un projet de migration de données ponctuel. Évidemment, je n'ai montré qu'un petit extrait de code pertinent.
Tom Baxter
Vous pouvez faire les uns après les autres sans avoir à les faire dans les mêmes transactions. DDL crée ses propres transactions implicites (en supposant que vous ayez laissé le paramètre Transactions implicites par défaut activé), mais lorsque vous COMMENCEZ une transaction, vous contournez la propriété implicite d'une tâche DDL jusqu'à ce que vous ENGAGEZ cette transaction.
MguerraTorres
1
C'est en fait le fait qu'ils sont dans le même lot, et rien à voir avec les transactions.
Aaron Bertrand
C'est un bon point. Mon erreur.
MguerraTorres