【问题标题】:How to use table variable in a dynamic sql statement?如何在动态 sql 语句中使用表变量?
【发布时间】:2011-06-05 07:21:37
【问题描述】:

在我的存储过程中,我在我的过程之上声明了两个表变量。现在我试图在动态 sql 语句中使用该表变量,但在执行该过程时出现此错误。我正在使用 Sql Server 2008。

这就是我的查询的样子,

set @col_name =  'Assoc_Item_' 
              + Convert(nvarchar(2), @curr_row1);

set @sqlstat = 'update @RelPro set ' 
             + @col_name 
             + ' = (Select relsku From @TSku Where tid = ' 
             + Convert(nvarchar(2), @curr_row1) + ') Where RowID = ' 
             + Convert(nvarchar(2), @curr_row);

Exec(@sqlstat);

我收到以下错误,

必须声明表变量“@RelPro”。 必须声明表变量“@TSku”。

我曾尝试将表格移出动态查询的字符串块,但无济于事。

【问题讨论】:

    标签: sql sql-server sql-server-2008 dynamic-sql table-variable


    【解决方案1】:

    您不能这样做,因为表变量超出了范围。

    您必须在动态 SQL 语句中声明表变量或创建临时表。

    我建议您阅读这篇关于动态 SQL 的优秀文章。

    http://www.sommarskog.se/dynamic_sql.html

    【讨论】:

      【解决方案2】:

      我认为这是不可能的(尽管参考下面的更新);据我所知,表变量只存在于声明它的范围内。但是,您可以使用 temp 表(使用 create table 语法并在表名前加上 # 符号),并且可以在创建它的范围和你的动态声明。

      更新:请参阅 Martin Smith 的回答,了解如何使用表值参数将表变量传递给动态 SQL 语句。还要注意提到的限制:表值参数是只读的。

      【讨论】:

        【解决方案3】:

        您的 EXEC 在不同的上下文中执行,因此它不知道在您的原始上下文中声明的任何变量。您应该能够使用临时表而不是表变量,如下面的简单演示所示。

        create table #t (id int)
        
        declare @value nchar(1)
        set @value = N'1'
        
        declare @sql nvarchar(max)
        set @sql = N'insert into #t (id) values (' + @value + N')'
        
        exec (@sql)
        
        select * from #t
        
        drop table #t
        

        【讨论】:

        • 也许我搞砸了,但是除非使用全局临时,否则不同的上下文是否也会导致临时表超出范围?
        • @John 不,内部作用域可以看到在父作用域中创建的#table。易于测试。
        • 也许有一些关于 sql server 上下文的好链接?我真的不明白这个。如果动态 sql 和表变量在同一个批次中 - 上下文在这里扮演什么角色?
        【解决方案4】:

        你没有使用动态 SQL

        update
            R
        set
            Assoc_Item_1 = CASE WHEN @curr_row = 1 THEN foo.relsku ELSE Assoc_Item_1 END,
            Assoc_Item_2 = CASE WHEN @curr_row = 2 THEN foo.relsku ELSE Assoc_Item_2 END,
            Assoc_Item_3 = CASE WHEN @curr_row = 3 THEN foo.relsku ELSE Assoc_Item_3 END,
            Assoc_Item_4 = CASE WHEN @curr_row = 4 THEN foo.relsku ELSE Assoc_Item_4 END,
            Assoc_Item_5 = CASE WHEN @curr_row = 5 THEN foo.relsku ELSE Assoc_Item_5 END,
            ...
        from
            (Select relsku From @TSku Where tid = @curr_row1) foo
            CROSS JOIN
            @RelPro R
        Where
             R.RowID = @curr_row;
        

        【讨论】:

          【解决方案5】:

          好吧,我想出了办法并想与可能遇到同样问题的人分享。

          让我从我一直面临的问题开始,

          我一直在尝试执行一个动态 Sql 语句,该语句使用了我在存储过程顶部声明的两个临时表,但由于该动态 sql 语句创建了一个新范围,我无法使用这些临时表。

          解决方案:

          我只是将它们更改为全局临时变量,它们就起作用了。

          在下面找到我的存储过程。

          CREATE PROCEDURE RAFCustom_Room_GetRelatedProducts
          -- Add the parameters for the stored procedure here
          @PRODUCT_SKU nvarchar(15) = Null
          

          作为 开始 -- 添加了 SET NOCOUNT ON 以防止额外的结果集 -- 干扰 SELECT 语句。 设置无计数;

          IF OBJECT_ID('tempdb..##RelPro', 'U') IS NOT NULL
          BEGIN
              DROP TABLE ##RelPro
          END
          
          Create Table ##RelPro
          (
              RowID int identity(1,1),
              ID int,
              Item_Name nvarchar(max),
              SKU nvarchar(max),
              Vendor nvarchar(max),
              Product_Img_180 nvarchar(max),
              rpGroup int,
              Assoc_Item_1 nvarchar(max),
              Assoc_Item_2 nvarchar(max),
              Assoc_Item_3 nvarchar(max),
              Assoc_Item_4 nvarchar(max),
              Assoc_Item_5 nvarchar(max),
              Assoc_Item_6 nvarchar(max),
              Assoc_Item_7 nvarchar(max),
              Assoc_Item_8 nvarchar(max),
              Assoc_Item_9 nvarchar(max),
              Assoc_Item_10 nvarchar(max)
          );
          
          Begin
              Insert ##RelPro(ID, Item_Name, SKU, Vendor, Product_Img_180, rpGroup)
          
              Select distinct zp.ProductID, zp.Name, zp.SKU,
                  (Select m.Name From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID),
                  'http://s0001.server.com/is/sw11/DG/' + 
                  (Select m.Custom1 From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID) +
                  '_' + zp.SKU + '_3?$SC_3243$', ep.RoomID
              From Product zp(nolock) Inner Join RF_ExtendedProduct ep(nolock) On ep.ProductID = zp.ProductID
              Where zp.ActiveInd = 1 And SUBSTRING(zp.SKU, 1, 2) <> 'GC' AND zp.Name <> 'PLATINUM' AND zp.SKU = (Case When @PRODUCT_SKU Is Not Null Then @PRODUCT_SKU Else zp.SKU End)
          End
          
          declare @curr_row int = 0,
                  @tot_rows int= 0,
                  @sku nvarchar(15) = null;
          
          IF OBJECT_ID('tempdb..##TSku', 'U') IS NOT NULL
          BEGIN
              DROP TABLE ##TSku
          END
          Create Table ##TSku (tid int identity(1,1), relsku nvarchar(15));
          
          Select @curr_row = (Select MIN(RowId) From ##RelPro);
          Select @tot_rows = (Select MAX(RowId) From ##RelPro);
          
          while @curr_row <= @tot_rows
          Begin
              select @sku = SKU from ##RelPro where RowID = @curr_row;
          
              truncate table ##TSku;
          
              Insert ##TSku(relsku)
              Select distinct top(10) tzp.SKU From Product tzp(nolock) INNER JOIN 
              [INTRANET].raf_FocusAssociatedItem assoc(nolock) ON assoc.associatedItemID = tzp.SKU
              Where (assoc.isActive=1) And (tzp.ActiveInd = 1) AND (assoc.productID = @sku)
          
              declare @curr_row1 int = (Select Min(tid) From ##TSku),
                      @tot_rows1 int = (Select Max(tid) From ##TSku);
          
              If(@tot_rows1 <> 0)
              Begin
                  While @curr_row1 <= @tot_rows1
                  Begin
                      declare @col_name nvarchar(15) = null,
                              @sqlstat nvarchar(500) = null;
                      set @col_name =  'Assoc_Item_' + Convert(nvarchar(2), @curr_row1);
                      set @sqlstat = 'update ##RelPro set ' + @col_name + ' = (Select relsku From ##TSku Where tid = ' + Convert(nvarchar(2), @curr_row1) + ') Where RowID = ' + Convert(nvarchar(2), @curr_row);
                      Exec(@sqlstat);
                      set @curr_row1 = @curr_row1 + 1;
                  End
              End
              set @curr_row = @curr_row + 1;
          End
          
          Select * From ##RelPro;
          

          结束 去吧

          【讨论】:

          • 我没有测试过你的代码,但我不认为你的临时表必须是global。您可能可以使用非全局(本地?)临时表(只需使用单个 # 符号)。好处是它会在您的连接关闭后自动消失,并且仅在您的连接中可见。 IE。我相信对于全局临时表,如果您有多个连接同时执行相同的过程,由于共享同一个全局临时表,它们可能会相互破坏。
          • 抱歉,你也有CURSOR...?
          • @Dr.威利——是的。我认为如果在动态 SQL 内部创建全局临时表以阻止它们在动态 SQL 批处理完成时消失,但如果在动态 SQL 批处理外部创建本地临时表应该是可见的。
          • @Martin - 啊,我刚试过,看起来你是对的;在动态语句中创建的临时表消失。但是,这似乎不是这里的情况。正在创建临时表,然后通过动态语句对其进行修改。
          【解决方案6】:

          使用 Temp 表解决了这个问题,但我在使用 Exec 时遇到了问题,所以我采用了以下使用 sp_executesql 的解决方案:

          Create TABLE #tempJoin ( Old_ID int, New_ID int);
          
          declare @table_name varchar(128);
          
          declare @strSQL nvarchar(3072);
          
          set @table_name = 'Object';
          
          --build sql sting to execute
          set @strSQL='INSERT INTO '+@table_name+' SELECT '+@columns+' FROM #tempJoin CJ
                                  Inner Join '+@table_name+' sourceTbl On CJ.Old_ID = sourceTbl.Object_ID'
          
          **exec sp_executesql @strSQL;**
          

          【讨论】:

            【解决方案7】:

            这是一个使用动态 T-SQL 查询然后在返回值超过一列时提取结果的示例(注意动态表名):

            DECLARE 
            @strSQLMain nvarchar(1000),
            @recAPD_number_key char(10),    
            @Census_sub_code varchar(1),
            @recAPD_field_name char(100),
            @recAPD_table_name char(100),
            @NUMBER_KEY varchar(10),
            
            if object_id('[Permits].[dbo].[myTempAPD_Txt]') is not null 
            
                DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
            
            CREATE TABLE [Permits].[dbo].[myTempAPD_Txt]
            (
                [MyCol1] char(10) NULL,
                [MyCol2] char(1) NULL,
            
            )   
            -- an example of what @strSQLMain is : @strSQLMain = SELECT @recAPD_number_key = [NUMBER_KEY], @Census_sub_code=TEXT_029 FROM APD_TXT0 WHERE Number_Key = '01-7212' 
            SET @strSQLMain = ('INSERT INTO myTempAPD_Txt SELECT [NUMBER_KEY], '+ rtrim(@recAPD_field_name) +' FROM '+ rtrim(@recAPD_table_name) + ' WHERE Number_Key = '''+ rtrim(@Number_Key) +'''')      
            EXEC (@strSQLMain)  
            SELECT @recAPD_number_key = MyCol1, @Census_sub_code = MyCol2 from [Permits].[dbo].[myTempAPD_Txt]
            
            DROP TABLE [Permits].[dbo].[myTempAPD_Txt]  
            

            【讨论】:

              【解决方案8】:

              在 SQL Server 2008+ 上,只要您不需要更新表本身的值,就可以使用表值参数将表变量传递给动态 SQL 语句。

              因此,从您发布的代码中,您可以将这种方法用于@TSku,但不能用于@RelPro

              下面的示例语法。

              CREATE TYPE MyTable AS TABLE 
              ( 
              Foo int,
              Bar int
              );
              GO
              
              
              DECLARE @T AS MyTable;
              
              INSERT INTO @T VALUES (1,2), (2,3)
              
              SELECT *,
                      sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
              FROM @T
              
              EXEC sp_executesql
                N'SELECT *,
                      sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
                  FROM @T',
                N'@T MyTable READONLY',
                @T=@T 
              

              包含physloc 列只是为了证明在子作用域中引用的表变量肯定与外部作用域相同,而不是副本。

              【讨论】:

              • 我知道它通过引用传递表,因此减少了 IO。在嵌套存储过程调用或执行语句的情况下,Havent 有机会验证这一点
              猜你喜欢
              • 1970-01-01
              • 2011-01-12
              • 1970-01-01
              • 1970-01-01
              • 2022-12-21
              • 2015-12-16
              相关资源
              最近更新 更多