无法插入到新创建的列中


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。适应良好做法的一个好的开始:-)
Kin Shah

Answers:


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. 突出显示前两行,单击执行,然后突出显示后两行,然后单击执行;要么,
  2. 在它们之间放置一个批处理分隔符,如下所示:

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

如果您是从SQL Server外部运行此程序(例如从应用程序代码发送SQL批处理),则可以简单地以类似的方式分别发送两个批处理,或者如果您绝对需要将其作为一个批处理发送,则可以使用动态SQL(如Gianluca的答案)来推迟名称解析。


1
如果这是运行时错误,我应该能够捕获它,对吗?不过我不能
舍甫琴科中号

@AndriyM无法捕获所有错误,不。如其中之一所示,其中一些发生在解析和执行之间。
亚伦·伯特兰

@AndriyM除此之外,我们的Kiwi朋友在这里的答案也暗示着同样的情况,在某些情况下,错误对于编译时间而言为时已晚,而对于运行时而言则为时过早。两个答案?动态SQL。(Erland的文章中也有一些例子。)
Aaron Bertrand

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;

1
我喜欢临时程序-我不会想到的!
Max Vernon

1

我很好奇为什么您要更改表并将值插入同一事务的该列中。

几乎没有机会再次需要更改表(以完全相同的方式进行),除非每小时/每天对其进行还原,那么为什么要在事务中进行更改呢?

您的更改尚未提交,因此在您尝试将其插入时找不到。

我的建议是将您的DDL和DML任务分开(至少要在单独的事务中)。


我正在更改表并插入,因为这是一次性数据迁移项目的一部分。显然,我只显示了一小段相关代码。
汤姆·巴克斯特

您可以一个接一个地执行,而不必在同一笔交易中进行。DDL创建自己的隐式事务(假设您保留默认的“隐式事务”设置为开),但是当您开始事务时,您将绕过DDL任务的“隐式”属性,直到您提交该事务。
MguerraTorres '02

1
实际上,它们在同一批中,与事务无关。
亚伦·伯特兰

那是个很好的观点。我的错。
MguerraTorres '02
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.