StoredProc操作临时表在执行时抛出“无效列名”

时间:2014-07-02 10:20:16

标签: sql tsql sql-server-2005 tempdata

我有一些sps创建了一个包含各种字段的临时表#TempData。在这些sp中,我将一些处理sp称为#TempData。临时数据处理取决于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代码保持不变并向优化器提供提示 - 跳过此步骤,因为sps传递后sp不会使用AvgValue列。

动态SQL不是一个受欢迎的解决方案BTW。根据现有的tsql代码(对此必需的大量修改),使用#TempData tablename的替代方法是不可取的解决方案。 尝试了SET FMTONLY,tempdb.tempdb.sys.columns,尝试抓包装没有任何成功。

3 个答案:

答案 0 :(得分:5)

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

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

遗憾的是,T-SQL语言是一种非常简单的编译器,在尝试执行编译时并没有考虑运行时控制流程。它没有分析控制流或试图推迟条件路径中的编译 - 它只是因为列没有(此时)存在而无法编译。

不幸的是,SQL Server内置的任何机制都没有控制这种行为 - 这是你得到的行为,任何解决它的行为都将被视为一种解决方法 - 正如已经证明的那样(注释中的有效建议 - 处理它的两种主要方法是使用动态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是一个此类存储过程的示例。

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

除了动态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: Dang,你是对的,对不起,我确定我有......这.... :(让我的东西多一点