No se puede INSERTAR en una columna recién creada

8

Tengo una tabla de prueba simple como esta:

CREATE TABLE MyTable (x INT);

Dentro de una transacción, trato de agregar una columna y luego insertarla en la columna recién creada:

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;

El problema es un mensaje de error cuando ejecuto el código anterior:

Invalid column name 'SupplementalDividends'.

¿Por qué esto está causando un error? Si agrego la columna en un lote diferente, fuera de la transacción, funcionará. Mi problema es que quiero agregar la columna dentro de la transacción. ¿Por qué el error?

Tom Baxter
fuente
44
Una sugerencia pequeña pero importante: usar siempreschema.ObjectName . Un buen comienzo para adaptar las buenas prácticas :-)
Kin Shah

Respuestas:

6

Solo quiero aclarar que este es un problema en tiempo de ejecución, no en tiempo de compilación, y que no tiene nada que ver con el hecho de que están en la misma transacción . Por ejemplo, supongamos que tenemos esta tabla:

CREATE TABLE dbo.floob(a int);

El siguiente lote se analiza con éxito (tiempo de compilación), pero en el tiempo de ejecución obtiene el error que menciona en la pregunta:

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

  SELECT b FROM dbo.floob;
COMMIT TRANSACTION;

En un editor de consultas en Management Studio, puede solucionarlo fácilmente, ya sea:

  1. Resaltando las dos primeras líneas, presionando ejecutar, luego resaltando las segundas dos líneas y presionando ejecutar; o,
  2. Poner un separador de lotes entre ellos, así:

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

Si está ejecutando esto desde fuera de SQL Server (por ejemplo, enviando un lote SQL desde el código de su aplicación), simplemente puede enviar los dos lotes por separado de una manera similar, o si absolutamente necesita enviarlo como un único lote, puede use SQL dinámico (como en la respuesta de Gianluca ) para diferir la resolución de nombres.

Aaron Bertrand
fuente
1
Si se trata de un error de tiempo de ejecución, debería poder detectarlo, ¿correcto? Sin embargo, no puedo .
Andriy M
@AndriyM No se pueden detectar todos los errores, no. Y algunos ocurren en algún lugar entre analizar y ejecutar, como muestra este.
Aaron Bertrand
@AndriyM Además de esto, nuestro amigo Kiwi tiene una respuesta aquí que implica lo mismo, hay escenarios en los que los errores son demasiado tarde para el tiempo de compilación pero demasiado temprano para el tiempo de ejecución. Ambas respuestas? SQL dinámico (Algunos ejemplos también en el artículo de Erland .)
Aaron Bertrand
10

Es un problema vinculante. El código está enlazado a los metadatos de la tabla en tiempo de compilación y no está enlazado tarde. Intente usar EXEC y SQL dinámico para superar esta limitación:

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;

Otra opción es utilizar un procedimiento almacenado para insertar los datos: el enlace tardío se aplica al procedimiento almacenado, pero no a las consultas ad-hoc. Nuevamente, tendría que usar SQL dinámico para crear el procedimiento, pero podría facilitarle el paso 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;

Un procedimiento almacenado temporal también funcionaría:

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
fuente
1
Me gusta el procedimiento temporal, ¡no habría pensado en eso!
Max Vernon
1

Tengo curiosidad por saber por qué está alterando una tabla e insertando un valor en esa columna dentro de la misma transacción.

Casi no hay posibilidad de que alguna vez deba modificar la tabla (de la misma manera), a menos que se revierta cada hora / día, entonces, ¿por qué hacerlo dentro de una transacción?

Su modificación aún no se ha confirmado y, por lo tanto, no se encuentra cuando intenta insertarla.

Mi consejo es separar sus tareas DDL y DML (al menos en transacciones separadas de todos modos).

MguerraTorres
fuente
Estoy alterando la tabla e insertando porque esto es parte de un proyecto de migración de datos de una sola vez. Obviamente, solo mostré un pequeño fragmento de código relevante.
Tom Baxter
Puede hacer uno tras otro sin tener que hacerlos en las mismas transacciones. DDL crea sus propias transacciones implícitas (suponiendo que dejó activada la configuración predeterminada de Transacciones implícitas), pero cuando COMIENZA una transacción, omite la propiedad implícita de una tarea DDL hasta que COMPROMETE esa transacción.
MguerraTorres
1
En realidad, es el hecho de que están en el mismo lote y nada que ver con las transacciones.
Aaron Bertrand
Ese es un buen punto. Mi error.
MguerraTorres