Невозможно вставить новую колонку

8

У меня есть простая таблица тестов, как это:

CREATE TABLE MyTable (x INT);

В рамках транзакции я пытаюсь добавить столбец, а затем вставить его во вновь созданный столбец:

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;

Проблема - сообщение об ошибке, когда я запускаю приведенный выше код:

Invalid column name 'SupplementalDividends'.

Почему это вызывает ошибку? Если я добавлю столбец в другой пакет, вне транзакции, он будет работать. Моя проблема в том, что я хочу добавить столбец в транзакции. Почему ошибка?

Том Бакстер
источник
4
Небольшое, но важное предложение - всегда используйтеschema.ObjectName . Хорошее начало для адаптации хорошей практики :-)
Кин Шах

Ответы:

6

Просто хочу уточнить, что это проблема во время выполнения, а не во время компиляции, и не имеет ничего общего с тем, что они находятся в одной транзакции . Например, предположим, у нас есть эта таблица:

CREATE TABLE dbo.floob(a int);

Следующий пакет успешно анализирует (время компиляции), но во время выполнения получает ошибку, о которой вы упомянули в вопросе:

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

  SELECT b FROM dbo.floob;
COMMIT TRANSACTION;

В редакторе запросов в Management Studio вы можете легко обойти это, либо:

  1. Выделение первых двух строк, нажатие клавиши execute, затем выделение вторых двух строк и нажатие клавиши execute; или,
  2. Ввод пакетное разделитель между ними, например , так:

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

Если вы запускаете это из-за пределов SQL Server (например, отправляете пакет SQL из кода вашего приложения), вы можете просто отправить две партии отдельно, аналогичным образом, или, если вам абсолютно необходимо отправить его как один пакет, вы можете используйте динамический SQL (как в ответе Джанлуки ), чтобы отложить разрешение имен.

Аарон Бертран
источник
1
Если это ошибка во время выполнения, я смогу ее уловить, правильно? Я не могу , однако.
Андрей М
@AndriyM Не могу отловить все ошибки, нет. А некоторые происходят где-то между синтаксическим анализом и выполнением, как показывает этот.
Аарон Бертран
@AndriyM В дополнение к этому, у нашего друга-киви есть ответ, который подразумевает то же самое, есть сценарии, в которых ошибки слишком запаздывают во время компиляции, но слишком рано во время выполнения. Оба ответа? Динамический SQL. (Некоторые примеры в статье Эрланда тоже.)
Аарон Бертран
10

Это обязательный вопрос. Код привязан к метаданным таблицы во время компиляции, и он не привязан поздно. Попробуйте использовать EXEC и динамический SQL, чтобы преодолеть это ограничение:

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;

Другой вариант - использование хранимой процедуры для вставки данных: поздняя привязка применяется к хранимой процедуре, но не к специальным запросам. Опять же, вам придется использовать динамический SQL для создания процедуры, но это может упростить передачу параметров:

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;

Временная хранимая процедура также будет работать:

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
источник
1
Мне нравится временная процедура - я бы об этом не подумала!
Макс Вернон
1

Мне любопытно, почему вы изменяете таблицу и вставляете значение в этот столбец в той же транзакции.

Почти нет шансов, что вам когда-нибудь понадобится снова изменить таблицу (точно таким же образом), если только она не переворачивается каждый час / день, так зачем делать это в транзакции?

Ваше изменение еще не было совершено, и поэтому оно не найдено, когда вы пытаетесь вставить в него.

Мой совет - разделять задачи DDL и DML (по крайней мере, в отдельных транзакциях).

MguerraTorres
источник
Я изменяю таблицу и вставляю, потому что это часть проекта разовой миграции данных. Очевидно, я показал только небольшой фрагмент соответствующего кода.
Том Бакстер
Вы можете делать одно за другим, не делая их в одних и тех же транзакциях. DDL создает свои собственные неявные транзакции (при условии, что вы оставили настройку по умолчанию для неявных транзакций), но когда вы НАЧИНАЕТЕ транзакцию, вы пропускаете свойство Implicit задачи DDL до тех пор, пока не совершите эту транзакцию.
MguerraTorres
1
Это на самом деле тот факт, что они в одной партии, и не имеют ничего общего с транзакциями.
Аарон Бертран
Неплохо подмечено. Моя ошибка.
MguerraTorres