【问题标题】:StoredProc manipulating Temporary table throws 'Invalid column name' on executionStoredProc 操作临时表在执行时抛出“无效的列名”
【发布时间】:2014-08-23 02:18:44
【问题描述】:

我有许多 sp 创建一个带有各种字段的临时表 #TempData。在这些 sp 中,我称其为在#TempData 上运行的一些处理 sp。临时数据处理取决于 sp 输入参数。 SP代码是:

CREATE PROCEDURE [dbo].[tempdata_proc]
  @ID int,
  @NeedAvg tinyint = 0
AS
BEGIN
  SET NOCOUNT ON;                
  if @NeedAvg = 1
    Update #TempData set AvgValue = 1
  Update #TempData set Value = -1;
END

然后,这个sp在外层sp中被调用,代码如下:

USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO

 Create table #TempData
  (
   tele_time datetime
   , Value float
   --, AvgValue float
  )
  Create clustered index IXTemp on #TempData(tele_time);

  insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data

declare 
  @ID int,
  @UpdAvg int;
select
  @ID = 1000, 
  @UpdAvg = 1
  ;

Exec  dbo.tempdata_proc @ID, @UpdAvg    ; 
select * from #TempData;    
drop table #TempData

此代码引发错误:消息 207,级别 16,状态 1,过程 tempdata_proc,第 8 行:无效的列名“AvgValue”。

但如果我取消注释声明 AvgValue float - 一切正常。

问题:是否有任何解决方法让存储的 proc 代码保持不变并向优化器提供提示 - 跳过此步骤,因为由于传递的参数,sp 不会使用 AvgValue 列。

动态 SQL 不是一个受欢迎的解决方案顺便说一句。根据现有的 tsql 代码(为此需要进行大量修改),使用 #TempData 表名的替代方案是不可取的解决方案。 试过 SET FMTONLY、tempdb.tempdb.sys.columns、try-catch wrapping 都没有成功。

【问题讨论】:

  • 为不同的目的使用不同命名的表。
  • 使用不同的表名可能会导致正在进行的存储过程的不可预知的行为,这也会选择/更新#TempData#records
  • 那么,一张临时表被不同的procs使用,目的不同?如果是这样,您必须在临时表中有所有使用的列。有问题吗?
  • 确实如此。确切的临时表用作许多 sp 之间的缓冲区。如果您想再操作一个额外的列,所有 sp 都必须添加此列以实现兼容性。问题是,如何强制数据库引擎避免未使用的不存在的列,而不是在执行时抛出错误。
  • 您可以有一个用于创建/删除临时表的过程。所有列都可以为空,因此所有列都是可选的。

标签: sql tsql sql-server-2005 tempdata


【解决方案1】:

处理存储过程的方式分为两部分 - 一部分,检查语法正确性,在创建或更改存储过程时执行。编译的剩余部分被推迟到存储过程执行的时间点。这称为Deferred Name Resolution,允许存储过程包含对在创建过程时不存在的表(不仅限于临时表)的引用。

不幸的是,当涉及到执行过程的时间点时,它需要能够编译所有单独的语句,而此时它会发现表存在,但列不存在 - 因此此时,它将生成错误并拒绝运行该过程。

不幸的是,T-SQL 语言是一种非常简单的编译器,在尝试执行编译时没有考虑运行时控制流。它不会分析控制流或尝试延迟条件路径中的编译 - 它只是使编译失败,因为该列(此时)不存在。

不幸的是,SQL Server 中没有内置任何机制来控制这种行为——这是你得到的行为,任何解决它的方法都将被视为一种解决方法——正如(有效) cmets 中的建议 - 处理它的两种主要方法是使用动态 SQL 或确保临时表始终包含所需的所有列。

如果您遵循“临时表的所有用途都应具有所有列”,解决您对维护的担忧的一种方法是将列定义移动到单独的存储过程中,然后可以使用所有所需的列 - 类似于:

create procedure S_TT_Init
as
    alter table #TT add Column1 int not null
    alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
    insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
    create table #TT (tmp int null)
    exec S_TT_Init
    insert into #TT(Column1) values (8)
    exec S_TT_Consumer
    select Column1 from #TT
go
exec S_TT_User

产生输出89。您可以将临时表定义放在 S_TT_Init 中,S_TT_Consumer 是多个存储过程调用的内部查询,S_TT_User 是一个此类存储过程的示例。

【讨论】:

  • 感谢您的解释,非常完整的答案!
  • 这对我很有帮助。谢谢。我想知道为什么我得到无效的列名!当我可以清楚地看到它们正在被创建时。然后我尝试使用动态 sql(有效),但无法解释原因。
【解决方案2】:

最初使用该列创建表。如果您使用 SPROC 输出填充 TEMP 表,只需将其设置为 IDENTITY INT (1,1),以便列与您的输出对齐。

然后删除该列,稍后在 SPROC 中将其重新添加为适当的数据类型。

【讨论】:

    【解决方案3】:

    除了动态 SQL,我唯一(或者可能是最好的)方法是使用数据库结构检查。

    if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
    begin
        --do something AvgValue related
    end 
    

    也许创建一个简单的函数来获取表名和列,或者如果它总是 #TempTable 并且如果列存在则返回 1/0 的唯一列,从长远来看我认为会很有用

    if dbo.TempTableHasField('AvgValue')=1
    begin
        -- do something AvgValue related
    end
    

    EDIT1:该死,你是对的,对此感到抱歉,我确定我有......这个...... :(让我多说一点

    【讨论】:

    • 您仍然需要将实际引用该列的 SQL 放在字符串和 EXECed 中,而不是直接出现 - 即它仍然看起来很像动态 SQL,因为正如我在我的回答是,SQL Server 在编译这段代码时不会分析控制流。
    猜你喜欢
    • 2011-01-22
    • 1970-01-01
    • 2012-12-01
    • 2022-11-23
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多