In my stored procedure I declared two table variables on top of my procedure. Now I am trying to use that table variable within a dynamic sql statement but I get this error at the time of execution of that procedure. I am using Sql Server 2008.
This is how my query looks like,
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);
And I get the following errors,
Must declare the table variable "#RelPro".
Must declare the table variable "#TSku".
I have tried to take the table outside of the string block of dynamic query but to no avail.
On SQL Server 2008+ it is possible to use Table Valued Parameters to pass in a table variable to a dynamic SQL statement as long as you don't need to update the values in the table itself.
So from the code you posted you could use this approach for #TSku but not for #RelPro
Example syntax below.
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
The physloc column is included just to demonstrate that the table variable referenced in the child scope is definitely the same one as the outer scope rather than a copy.
Your EXEC executes in a different context, therefore it is not aware of any variables that have been declared in your original context. You should be able to use a temp table instead of a table variable as shown in the simple demo below.
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
You don't have to use dynamic 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;
You can't do this because the table variables are out of scope.
You would have to declare the table variable inside the dynamic SQL statement or create temporary tables.
I would suggest you read this excellent article on dynamic SQL.
http://www.sommarskog.se/dynamic_sql.html
Well, I figured out the way and thought to share with the people out there who might run into the same problem.
Let me start with the problem I had been facing,
I had been trying to execute a Dynamic Sql Statement that used two temporary tables I declared at the top of my stored procedure, but because that dynamic sql statment created a new scope, I couldn't use the temporary tables.
Solution:
I simply changed them to Global Temporary Variables and they worked.
Find my stored procedure underneath.
CREATE PROCEDURE RAFCustom_Room_GetRelatedProducts
-- Add the parameters for the stored procedure here
#PRODUCT_SKU nvarchar(15) = Null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
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;
END
GO
I don't think that is possible (though refer to the update below); as far as I know a table variable only exists within the scope that declared it. You can, however, use a temp table (use the create table syntax and prefix your table name with the # symbol), and that will be accessible within both the scope that creates it and the scope of your dynamic statement.
UPDATE: Refer to Martin Smith's answer for how to use a table-valued parameter to pass a table variable in to a dynamic SQL statement. Also note the limitation mentioned: table-valued parameters are read-only.
Here is an example of using a dynamic T-SQL query and then extracting the results should you have more than one column of returned values (notice the dynamic table name):
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]
Using Temp table solves the problem but I ran into issues using Exec so I went with the following solution of using 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;**
In SQL Server in a stored procedure, I want to get the value in a column of a single-row table given the column name, and assign that value to a variable. The column name may be different every time (I use T-SQL to interrogate the schema at run time).
The example given below is as minimal as I can make it, the important thing is that you cannot assume that the column name will always be entity_client, it could be anything at all, though (due to interrogation of INFORMATION SCHEMA) we will have the value assigned to the variable #entity_column_name.
Example preparation SQL:
IF OBJECT_ID('tempdb..#foo') IS NOT NULL
BEGIN;
DROP TABLE #foo;
END;
CREATE TABLE #foo
(
id INT,
entity_client NVARCHAR(255)
);
INSERT INTO #foo VALUES (1, 'clientcode|client_number');
DECLARE #entity_column_name NVARCHAR(255) = 'entity_client';
DECLARE #entity_column_value NVARCHAR(255);
I have tried the following:
SELECT TOP 1 #entity_column_name = [#entity_column_value]
FROM #foo;
...which generates an error
Invalid column name '#entity_column_value'
I have also tried:
EXEC('SELECT TOP 1 #entity_column_value = [' + #entity_column_name + '] FROM #foo;');
which generates another error
Must declare the scalar variable "#entity_column_value"
The following works, but unfortunately the column name is hard-coded - I wanted to be able to vary the column name:
SELECT TOP 1 #entity_column_value = [entity_client]
FROM #foo;
Yes, I have looked on SO and found the following questions, but they do not provide an answer where the value is assigned to a variable, in both cases the SELECT output is simply dumped to screen:
Get column value from string column name sql
Get column value from dynamically achieved column name
This will actually work but you need to declare the output variable:
DECLARE #entity_column_name NVARCHAR(255) = 'entity_client';
DECLARE #entity_column_value NVARCHAR(255);
DECLARE #tsql NVARCHAR(1000) = 'SELECT TOP 1 #entity_column_value = [' + #entity_column_name + '] FROM #foo;'
EXEC sp_executesql #tsql, N'#entity_column_value NVARCHAR(255) OUTPUT',
#entity_column_value OUTPUT;
I have an application with a requirement that the user can "copy" a record. This will duplicate the record in the table, and the associated records in any child tables.
I will use a trigger to execute a stored procedure to do the copy. The issue I am facing is that I want to increment the ID field for the copied record, which is also the FK in the child tables. The ID field is not a standard format, so using an increment won't work. In order to make this future-proof, I was going to use dynamic SQL to pull the columns for each table so that I don't need to modify the code if I add a new field to one of the tables. The client's system admin can also add columns to the table via the GUI, but they have no access to the SQL backend so they would need to contact us to modify the code (not ideal).
Example:
Declare #ColumnNames varchar(2000)
Declare #BLDGCODE char(4)
set #BLDGCODE = '001'
select #ColumnNames = COALESCE(#ColumnNames + ', ', '') + COLUMN_NAME
from
INFORMATION_SCHEMA.COLUMNS
where
TABLE_NAME='FMB0'
Declare #DynSqlStatement varchar(max);
set #DynSqlStatement = 'Insert into dbo.FMB0('+ #ColumnNames + ')
select * from dbo.FMB0 where BLDGCODE= ' + cast(#BLDGCODE as char(4));
print(#DynSqlStatement);
This solves the issue for a new column being added to one of the tables. However, how can I increment the ID (BLDGCODE in this example). Is my only solution to script out the columns by name so I can increment the ID, or is there a function I am overlooking?
Hopefully this made sense. I am an intermediate SQL user at best, so forgive the naivete if there's an obvious solution.
UPDATE
So I've decided to use #temp tables to hold the record that was changed, modify the id there, and then insert back into the main table from the #temp table. This is working pretty well, with one exception. I get the following error:
The column "FLOORID_" cannot be modified because it is either a computed column or is the result of a UNION operator.
Below is my stored procedure. I've investigated a STUFF approach, but not sure where to insert that code. Using STUFF with calculated column. I am now back to thinking I need to call out the columns specifically for the one table with the computed column, and if we add a new field, I just need to modify this stored procedure. Anyone have any other ideas?
ALTER PROCEDURE [dbo].[LDAC_BLDGCOPY]
#BLDGCODE CHAR(4)
AS
BEGIN
SET NOCOUNT ON;
--COPY BUILDING RECORD
BEGIN
DECLARE #ColumnNamesB0 VARCHAR(2000);
SELECT *
INTO #TEMPB0
FROM FMB0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPB0
SET BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey(),
BLDGCOPY = 0;
SELECT #ColumnNamesB0 = COALESCE(#ColumnNamesB0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FMB0';
DECLARE #DynSqlStatementB0 VARCHAR(MAX);
SET #DynSqlStatementB0 = 'Insert into dbo.FMB0('+#ColumnNamesB0+')
select * from #TEMPB0';
EXEC (#DynSqlStatementB0);
END;
--COPY FLOOR RECORDS
BEGIN
DECLARE #ColumnNamesL0 VARCHAR(2000);
--DECLARE #Val INT =
RTRIM(CONVERT(CHAR(10),CAST(LEFT(#BL_KEY,LEN(RTRIM(#BL_KEY))-2)+1 as
INT)))+'01
SELECT *
INTO #TEMPL0
FROM FML0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPL0
SET BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey();
UPDATE #TEMPL0
SET FLOORID_ = auto_key;
SELECT #ColumnNamesL0 = COALESCE(#ColumnNamesL0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FML0';
DECLARE #DynSqlStatementL0 VARCHAR(MAX);
SET #DynSqlStatementL0 = 'Insert into dbo.FML0('+#ColumnNamesL0+')
select * from #TEMPL0';
EXEC (#DynSqlStatementL0);
END;
--COPY ROOM RECORDS
BEGIN
DECLARE #ColumnNamesA0 VARCHAR(2000);
--DECLARE #Val INT =
RTRIM(CONVERT(CHAR(10),CAST(LEFT(#BL_KEY,LEN(RTRIM(#BL_KEY))-2)+1 as
INT)))+'01
SELECT *
INTO #TEMPA0
FROM FMA0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPA0
SET
BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey(),
FLOORID = #TEMPL0.FLOORID_
FROM #TEMPA0
INNER JOIN #TEMPL0 ON CONVERT(CHAR(4), (CAST((#BLDGCODE) AS
INT) + 100)) = #TEMPL0.BLDGCODE;
SELECT #ColumnNamesA0 = COALESCE(#ColumnNamesA0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FMA0';
DECLARE #DynSqlStatementA0 VARCHAR(MAX);
SET #DynSqlStatementA0 = 'Insert into dbo.FMA0('+#ColumnNamesA0+')
select * from #TEMPA0';
EXEC (#DynSqlStatementA0);
DROP TABLE #TEMPB0;
DROP TABLE #TEMPL0;
DROP TABLE #TEMPA0;
END;
END;
I'm trying to copy data from one table to another using global parameters to define the local parameters. The string values for the local parameters is changed appropriately, however, when I try to use them as the referenced column names it populates the column I am trying to colpy to with just the value of the parameter and not the values from the columns of the other table. I'm new to sql so I am stuck.
DECLARE #ProductName VarChar(255), #SiteName VarChar(255)
SET #ProductName =
CASE ##[Level]
WHEN 1 THEN 'MaterialAggName'
WHEN 2 THEN 'TradeAggName'
WHEN 3 THEN 'ProfitCenterAggName'
WHEN 4 THEN 'PerformanceCenterAggName'
WHEN 5 THEN 'ValueCenterAggName'
WHEN 6 THEN 'BusinessAggName'
END;
SET #SiteName =
CASE ##[CustomerLevel]
WHEN 1 THEN 'Customer_Postal_Name'
WHEN 2 THEN 'Customer_3DigitPostal_Name'
WHEN 3 THEN 'Customer_Location_Name'
WHEN 4 THEN 'Customer_Region_Name'
WHEN 5 THEN 'Customer_County_Name'
WHEN 6 THEN 'Customer_GeoBusiness_Name'
END;
CREATE TABLE [dbo].[Temp_Demand]
(
SiteName VarChar(255),
ProductName VarChar(255),
Quantity Int
)
INSERT INTO [dbo].[Temp_Demand] (SiteName, ProductName, Quantity)
SELECT #SiteName, #ProductName, Final
FROM [dbo].[Customers_Master]
Any Suggestions?
To do this you need to use Dynamic SQL, your insert statement would need something like .
DECLARE #SQL NVARCHAR(MAX)
Set #SQL = 'INSERT INTO [dbo].[Temp_Demand] (SiteName, ProductName, Quantity)
SELECT ''' + #SiteName + ''', ''' + #ProductName + ''', Final
FROM [dbo].[Customers_Master]'
sp_executesql #SQL
If there is more than one insert with changing values, simply put this in a loop. *except the declare of course.
DECLARE
#countFlag INT,
#maxNum INT = 5,
#addingName NVARCHAR(6)
SET #countFlag = 1
WHILE (#countFlag <= #maxNum)
BEGIN
SET #addingName = 'name' + CAST(#countFlag AS NVARCHAR(2))
ALTER TABLE TableName
ADD
#addingName NVARCHAR(30)
SET #countFlag = #countFlag + 1
END
========================================================
This is called at the beginning of a set of procedures. #maxNum is actually passed in based on a question to the operator and changes the 'shape' of an existing db to include more columns. I would like to have the resulting column names be something like "name1" "name2" etc. but I am getting an "Incorrect syntax near '#addingName'" after the ADD statement when I execute it. What am I doing wrong here?
You cannot do it in that way, you should compose the query dynamically and execute it with Exec:
DECLARE #sqlCommand varchar(200)
SET #sqlCommand = 'ALTER TABLE TableName ADD ' + #addingName + ' NVARCHAR(30) '
EXEC (#sqlCommand)