Não é possível inserir na coluna recém-criada

8

Eu tenho uma tabela de teste simples como esta:

CREATE TABLE MyTable (x INT);

Dentro de uma transação, tento adicionar uma coluna e inserir na coluna recém-criada:

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;

O problema é uma mensagem de erro quando executo o código acima:

Invalid column name 'SupplementalDividends'.

Por que isso está causando um erro? Se eu adicionar a coluna em um lote diferente, fora da transação, ela funcionará. Meu problema é que eu quero adicionar a coluna dentro da transação. Por que o erro?

Tom Baxter
fonte
4
Uma sugestão pequena, mas importante - sempre useschema.ObjectName . Um bom começo para se adaptar boas práticas :-)
Kin Shah

Respostas:

6

Só quero esclarecer que esse é um problema no tempo de execução, não no tempo de compilação e não tem nada a ver com o fato de que eles estão na mesma transação . Por exemplo, vamos assumir que temos esta tabela:

CREATE TABLE dbo.floob(a int);

O lote a seguir analisa com êxito (tempo de compilação), mas em tempo de execução, ele recebe o erro mencionado na pergunta:

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

  SELECT b FROM dbo.floob;
COMMIT TRANSACTION;

Em um editor de consultas no Management Studio, você pode contornar isso facilmente, por:

  1. Destacando as duas primeiras linhas, pressionando executar, depois destacando as duas segundas linhas e pressionando executar; ou,
  2. Colocando um separador de lote entre eles, assim:

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

Se você estiver executando isso fora do SQL Server (por exemplo, enviando um lote SQL a partir do código do aplicativo), basta enviar os dois lotes separadamente de maneira semelhante ou, se for absolutamente necessário enviá-lo como um único lote, poderá use SQL dinâmico (como na resposta de Gianluca ) para adiar a resolução de nomes.

Aaron Bertrand
fonte
11
Se este for um erro de tempo de execução, eu devo pegá-lo, correto? Eu não posso , no entanto.
Andriy M
@AndriyM Não é possível pegar todos os erros, não. E alguns ocorrem em algum lugar entre analisar e executar, como mostra este.
Aaron Bertrand
@AndriyM Além disso, nosso amigo Kiwi tem uma resposta aqui que implica o mesmo: há cenários em que os erros são muito tarde para o tempo de compilação, mas muito cedo para o tempo de execução. Ambas as respostas? SQL dinâmico. (Alguns exemplos no artigo de Erland também.)
Aaron Bertrand
10

É uma questão vinculativa. O código é vinculado aos metadados da tabela no tempo de compilação e não é vinculado tardiamente. Tente usar EXEC e SQL dinâmico para superar essa limitação:

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;

Outra opção é usar um procedimento armazenado para inserir os dados: a ligação tardia se aplica ao procedimento armazenado, mas não a consultas ad-hoc. Novamente, você precisaria usar o SQL dinâmico para criar o procedimento, mas isso poderia facilitar a passagem de parâmetros:

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;

Um procedimento armazenado temporário também funcionaria:

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
fonte
11
Eu gosto do procedimento temporário - eu não pensaria nisso!
Max Vernon
1

Estou curioso para saber por que você está alterando uma tabela e inserindo um valor nessa coluna dentro da mesma transação.

Quase não há chance de que você precise alterar a tabela (da mesma maneira exata) novamente, a menos que seja revertida a cada hora / dia, por que fazê-lo em uma transação?

Seu alter ainda não foi confirmado e, portanto, não é encontrado quando você tenta inseri-lo.

Meu conselho é separar suas tarefas DDL e DML (pelo menos em transações separadas de qualquer maneira).

MguerraTorres
fonte
Estou alterando a tabela e inserindo porque isso faz parte de um projeto de migração de dados único. Obviamente, eu mostrei apenas um pequeno trecho de código relevante.
Tom Baxter
Você pode fazer um após o outro sem precisar fazê-lo nas mesmas transações. O DDL cria suas próprias transações implícitas (supondo que você tenha deixado a configuração padrão de Transações implícitas ativada), mas quando você INICIA uma transação, ignora a propriedade implícita de uma tarefa DDL até que você confirme essa transação.
MguerraTorres
11
Na verdade, é o fato de eles estarem no mesmo lote e nada a ver com transações.
Aaron Bertrand
Este é um bom ponto. Meu erro.
MguerraTorres