2011-01-07 27 views
50

En mi procedimiento almacenado, declare dos variables de tabla en la parte superior de mi procedimiento. Ahora estoy tratando de usar esa variable de tabla dentro de una declaración SQL dinámica, pero obtengo este error al momento de la ejecución de ese procedimiento. Estoy utilizando SQL Server 2008.¿Cómo usar la variable de tabla en una declaración dinámica SQL?

Así es como mi consulta se parece,

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); 

Y consigo los siguientes errores,

Debe declarar la variable de tabla "@RelPro". Debe declarar la variable de tabla "@TSku".

He intentado sacar la tabla fuera del bloque de cadena de la consulta dinámica pero fue en vano.

Respuesta

50

Su EXEC se ejecuta en un contexto diferente, por lo tanto, no tiene conocimiento de ninguna variable que haya sido declarada en su contexto original. Debería poder usar una tabla temporal en lugar de una tabla como se muestra en la demostración simple a continuación.

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 
+1

Quizás cometí un error, pero ¿no causa el contexto diferente que la tabla temporal salga del alcance a menos que se use una temperatura global? –

+1

@John No, el alcance interno puede ver la #table creada en el ámbito principal. Fácil de probar –

+0

¿Quizás algún buen enlace en el contexto del servidor sql? Realmente no entiendo esto. Si las variables dinámicas sql y table están dentro del mismo lote, ¿qué contexto de rol desempeña aquí? – FrenkyB

6

No puede hacer esto porque las variables de la tabla están fuera del alcance.

Debería declarar la variable de la tabla dentro de la declaración de SQL dinámico o crear tablas temporales.

Le sugiero que lea este excelente artículo sobre SQL dinámico.

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

1

no creo que esto es posible (aunque se refieren a la actualización más adelante); por lo que sé, una variable de tabla solo existe dentro del alcance que la declaró. Sin embargo, puede utilizar una tabla temporal (use la sintaxis create table y prefija el nombre de su tabla con el símbolo #), y podrá acceder a ella tanto desde el ámbito que la crea como desde el alcance de su declaración dinámica.

ACTUALIZACIÓN: Consulte la respuesta de Martin Smith sobre cómo usar un parámetro con valores de tabla para pasar una variable de tabla a una instrucción SQL dinámica. También tenga en cuenta la limitación mencionada: los parámetros con valores de tabla son de solo lectura.

13

Usted no tienen utilizar SQL dinámico

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; 
+1

+1 para una solución sql no dinámica – JNK

1

Bueno, me di cuenta de la forma y pensado para compartir con la gente por ahí que podría encontrarse con el mismo problema.

Permítanme comenzar con el problema que había tenido que hacer frente,

que había estado tratando de ejecutar una sentencia de SQL dinámico que utiliza dos tablas temporales que declaró en la parte superior de mi procedimiento almacenado, pero debido a que DECLARACIÓN SQL dinámico creó un nuevo ámbito, no pude usar las tablas temporales.

Solución:

simplemente les cambió a variables temporales globales y trabajaron.

Encuentra mi procedimiento almacenado debajo.

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

COMO EMPEZAR - SET NOCOUNT EN añadido para evitar que los conjuntos de resultados adicionales de - interferir con las instrucciones SELECT. SET NOCOUNT ON;

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; 

FIN GO

+4

No he probado su código, pero no creo que sus tablas temporales tengan que ser _global_. Probablemente pueda usar una tabla temporal no global (¿local?) (Solo use un símbolo # único). El beneficio es que desaparecerá automáticamente una vez que se cierre su conexión, y solo será visible dentro de su conexión. Es decir. Creo que con una tabla temporal global, si tiene múltiples conexiones ejecutando el mismo procedimiento al mismo tiempo, podrían pegarse entre sí debido a que comparten la misma tabla global de temperatura. –

+4

Disculpa, ¿también tienes un CURSOR ...? – gbn

+2

@Dr. Wily - Sí. Creo que las tablas temporales globales son necesarias si se crean dentro del SQL dinámico para que no desaparezcan cuando termina el lote dinámico SQL, pero si se crean fuera del lote SQL dinámico, las tablas temporales locales deberían estar visibles. –

0

Utilizando la tabla de temperatura resuelve el problema, pero me encontré con problemas usando Exec así que fui con la siguiente solución de utilizar 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 '[email protected]_name+' SELECT '[email protected]+' FROM #tempJoin CJ 
         Inner Join '[email protected]_name+' sourceTbl On CJ.Old_ID = sourceTbl.Object_ID' 

**exec sp_executesql @strSQL;** 
0

Aquí está un ejemplo de utilizando una consulta dinámica de T-SQL y luego extrayendo los resultados si tiene más de una columna de valores devueltos (observe el nombre de la tabla dinámica):

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] 
57

En SQL Server 2008+, es posible usar los parámetros de valores de tabla para pasar una variable de tabla a una declaración de SQL dinámico, siempre que no necesite actualizar los valores en la tabla.

Así que desde el código que envió usted podría utilizar este enfoque para @TSku pero no para @RelPro

sintaxis siguiente ejemplo.

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', 
    @[email protected] 

La columna physloc se incluye sólo para demostrar que la variable de tabla referenciada en el ámbito secundario es sin duda el mismo que el alcance exterior en lugar de una copia.

+0

¡No entiendo por qué esta solución no se votó lo suficiente! Excelente y elegante solución. –

+0

He entendido que pasa la tabla por referencia, lo que reduce el IO. Havent tuvo la oportunidad de verificar esto, aunque en el caso de una llamada de proc almacenada anidada o una sentencia de ejecución – SheldonH

Cuestiones relacionadas