web-dev-qa-db-fra.com

Comment utiliser une variable de table dans une instruction SQL dynamique?

Dans ma procédure stockée, j'ai déclaré deux variables de table en plus de ma procédure. Maintenant, j'essaie d'utiliser cette variable de table dans une instruction SQL dynamique, mais j'obtiens cette erreur au moment de l'exécution de cette procédure. J'utilise SQL Server 2008.

Voici à quoi ressemble ma requête, 

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

Et je reçois les erreurs suivantes, 

Doit déclarer la variable de table "@RelPro" . Doit déclarer la variable de table "@TSku".

J'ai essayé de prendre la table en dehors du bloc de chaîne de la requête dynamique mais en vain.

57
Ashar Syed

Votre fonction EXEC s'exécute dans un contexte différent. Par conséquent, il n'a pas connaissance des variables déclarées dans votre contexte d'origine. Vous devriez pouvoir utiliser une table temporaire au lieu d'une variable de table, comme indiqué dans la démonstration ci-dessous.

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
58
Joe Stefanelli

Sur SQL Server 2008+, il est possible d'utiliser des paramètres de valeur de table pour transmettre une variable de table à une instruction SQL dynamique tant que vous n'avez pas besoin de mettre à jour les valeurs de la table elle-même.

Donc, à partir du code que vous avez posté, vous pouvez utiliser cette approche pour @TSku mais pas pour @RelPro

Exemple de syntaxe ci-dessous.

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 

La colonne physloc est incluse uniquement pour démontrer que la variable de table référencée dans la portée de l'enfant est certainement la même que celle de la portée externe plutôt qu'une copie.

74
Martin Smith

Vous n'avez pas besoin d'utiliser du SQL dynamique

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;
13
gbn

Vous ne pouvez pas faire cela parce que les variables de table sont hors de portée. 

Vous devez déclarer la variable de table à l'intérieur de l'instruction SQL dynamique ou créer des tables temporaires.

Je vous suggère de lire cet excellent article sur le SQL dynamique.

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

6
codingbadger

Eh bien, j'ai trouvé le chemin et j'ai pensé partager avec les gens qui pourraient être confrontés au même problème.

Permettez-moi de commencer par le problème auquel j'ai été confronté,

J'avais essayé d'exécuter une instruction SQL dynamique qui utilisait deux tables temporaires déclarées en haut de ma procédure stockée, mais comme cette instruction SQL dynamique créait une nouvelle étendue, je ne pouvais pas utiliser les tables temporaires.

Solution:

Je les ai simplement modifiées en variables temporaires globales et elles ont fonctionné.

Trouvez ma procédure stockée dessous.

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

AS COMMENCE - SET NOCOUNT ON ajouté pour empêcher les jeux de résultats supplémentaires de - interférer avec les instructions 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

2
Ashar Syed

Je ne pense pas que ce soit possible (mais reportez-vous à la mise à jour ci-dessous _); pour autant que je sache, une variable de table n'existe que dans la portée qui l'a déclarée. Vous pouvez cependant utiliser un table temporaire (utilisez la syntaxe create table et préfixez votre nom de table avec le symbole #), qui sera accessible à la fois dans la portée qui la crée et dans la portée de votre déclaration dynamique. .

UPDATE: reportez-vous à la réponse de Martin Smith pour savoir comment utiliser un paramètre table pour transmettre une variable de table à une instruction SQL dynamique. Notez également la limitation mentionnée: les paramètres table sont en lecture seule.

1

Voici un exemple d'utilisation d'une requête T-SQL dynamique, puis d'extraction des résultats si vous avez plusieurs colonnes de valeurs renvoyées (notez le nom de la table dynamique):

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]  
0
Gerald

L'utilisation de la table Temp résout le problème, mais j'ai rencontré des problèmes avec Exec. Je suis donc allé à la solution suivante consistant à utiliser 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;**
0
sfomate