Using variables within a dynamic sql script - sql-server

I'm currently editing a procedure, let's call it A, that does some manipulations of a temporary table created in procedure B (so procedure B calls A). Procedure B contains one argument, which is the name of the said temporary table.
That being said, we need to retrieve all the columns (in a CSV) of the temporary table into a variable. Since the name is not the same everytime, I need to use dynamic SQL.
Here is what I have:
-- Input parameters: #sTempTableName VARCHAR(MAX) --> Name of the temporary table to alter
DECLARE
#sColumns AS NVARCHAR(MAX) = '',
#sAlterTableDynamicSQL AS VARCHAR(MAX)
SET #sAlterTableDynamicSQL =
'
SELECT #sColumns = #sColumns + [name] + N'','' FROM Tempdb.Sys.Columns WHERE [object_id] = object_id(''tempdb..' + #sTempTableName + ''')
'
PRINT (#sAlterTableDynamicSQL)
EXEC (#sAlterTableDynamicSQL)
This fails saying that I need to declare #sColumns since it does not exists (I guess variables aren't shared between connections). How can I manage to do this ? I figured I could create another temporary table to hold that CSV, but I'm thinking there should be another way.

You should use sp_executesql statement. It would look like this:
DECLARE
#sColumns NVARCHAR(MAX),
#sAlterTableDynamicSQL NVARCHAR(MAX),
#temptableid INT = 3;
SELECT #sAlterTableDynamicSQL =
N'SELECT #sColumns = STRING_AGG([name], '','') FROM Tempdb.Sys.Columns WHERE [object_id] = + CAST(#temptableid AS VARCHAR(10))';
EXEC sp_executesql #sAlterTableDynamicSQL, N'#sColumns NVARCHAR(MAX) OUTPUT, #temptableid INT', #sColumns = #sColumns OUTPUT, #temptableid = #temptableid
SELECT #sColumns;

You can pass through variables to dynamic SQL via sp_executesql
Note that you should always use QUOTENAME to escape object names
Also, dynamic SQL variables should always be nvarchar
You also should not use variable coalescing to aggregate, instead use STRING_AGG or FOR XML
DECLARE
#sColumns AS NVARCHAR(MAX) = '',
#sAlterTableDynamicSQL AS NVARCHAR(MAX),
#sGUID AS VARCHAR(MAX) = CAST(NEWID() AS VARCHAR(MAX))
SET #sAlterTableDynamicSQL =
'
SELECT #sColumns = STRING_AGG(CAST([name] AS nvarchar(max)), N'','')
FROM Tempdb.sys.columns
WHERE [object_id] = object_id(N''tempdb..' + QUOTENAME(#sNomTableTemporaire, '''') + ''');
';
PRINT (#sAlterTableDynamicSQL);
EXEC sp_executesql
#sAlterTableDynamicSQL,
N'#sColumns nvarchar(max) OUTPUT'
#sColumns = #sColumns OUTPUT;
But you don't actually need dynamic SQL here at all. You can pass the table name straight to object_id()
DECLARE
#sColumns AS NVARCHAR(MAX) = '',
#sAlterTableDynamicSQL AS NVARCHAR(MAX),
#sGUID AS VARCHAR(MAX) = CAST(NEWID() AS VARCHAR(MAX))
SELECT #sColumns = STRING_AGG(CAST([name] AS nvarchar(max)), N',')
FROM Tempdb.sys.columns
WHERE [object_id] = object_id(N'tempdb..' + QUOTENAME(#sNomTableTemporaire));
For SQL Server 2016 and earlier, you can use the FOR XML PATH('') method

Related

scope of the variable in dynamic SQL in SQL server

I got this dynamic code:
declare #TableName varchar(100)='Customer'
declare #DestinationcolumnList NVARCHAR(MAX)
DECLARE #ServerName NVARCHAR(100) = '----'
DECLARE #SourceDatabase NVARCHAR(100) = 'Staging'
DECLARE #DestinationDatabase NVARCHAR(100) = 'History'
declare #SQL NVARCHAR(MAX)
set #SQL='
select
'+#DestinationcolumnList+' = coalesce('+#DestinationcolumnList+', '''') +'',''+ char(13) + char(10) + quotename(cast(d.COLUMN_NAME as varchar(128)))
from ['+#ServerName+'].['+#DestinationDatabase+'].INFORMATION_SCHEMA.COLUMNS d
inner join ['+#ServerName+'].['+#SourceDatabase+'].INFORMATION_SCHEMA.COLUMNS s
on d.TABLE_NAME = s.TABLE_NAME
and s.COLUMN_NAME = d.COLUMN_NAME
where d.TABLE_NAME = '''+#TableName+'''
order by d.ORDINAL_POSITION
'
exec sp_executesql #SQL
select #DestinationcolumnList
its giving NULL value. when I execute same code without dynamic SQL its working fine. How scope of the variable works in dynamic SQL.
Thanks in advance.
In Dynamic SQL, variables declared outside the dynamic string are used to build a string, they are not used as a part of the string, so they cannot be used the way you are trying to do it: doing the undocumented trick of concatenating a variable with itself to produce a single string in a SELECT statement.
If you made the variable declaration part of the string, it should work:
set #SQL='
declare #DestinationcolumnList NVARCHAR(MAX);
select
#DestinationcolumnList = coalesce(#DestinationcolumnList, '''') +'',''+ char(13) + char(10) + quotename(cast(d.COLUMN_NAME as varchar(128)))
from ['+#ServerName+'].['+#DestinationDatabase+'].INFORMATION_SCHEMA.COLUMNS d
inner join ['+#ServerName+'].['+#SourceDatabase+'].INFORMATION_SCHEMA.COLUMNS s
on d.TABLE_NAME = s.TABLE_NAME
and s.COLUMN_NAME = d.COLUMN_NAME
where d.TABLE_NAME = '''+#TableName+'''
order by d.ORDINAL_POSITION;
select #DestinationcolumnList;
'
exec sp_executesql #SQL

Variable table name in select statement

I have some tables for storing different file information, like thumbs, images, datasheets, ...
I'm writing a stored procedure to retrieve filename of a specific ID. something like:
CREATE PROCEDURE get_file_name(
#id int,
#table nvarchar(50)
)as
if #table='images'
select [filename] from images
where id = #id
if #table='icons'
select [filename] from icons
where id = #id
....
How can I rewrite this procedure using case when statement or should I just use table name as variable?
You can't use case .. when to switch between a table in the FROM clause (like you can in a conditional ORDER BY). i.e. so the following:
select * from
case when 1=1
then t1
else t2
end;
won't work.
So you'll need to use dynamic SQL. It's best to parameterize the query as far as possible, for example the #id value can be parameterized:
-- Validate #table is E ['images', 'icons', ... other valid names here]
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'select [filename] from **TABLE** where id = #id';
SET #sql = REPLACE(#sql, '**TABLE**', #table);
sp_executesql #sql, N'#id INT', #id = #id;
As with all dynamic Sql, note that unparameterized values which are substituted into the query (like #table), make the query vulnerable to Sql Injection attacks. As a result, I would suggest that you ensure that #table comes from a trusted source, or better still, the value of #table is compared to a white list of permissable tables prior to execution of the query.
Just build SQL string in another variable and EXECUTE it
DECLARE #sql AS NCHAR(500)
SET #sql=
'SELECT [filename] '+
' FROM '+#table+
' WHERE id = #id'
EXECUTE(#sql)
CREATE PROCEDURE get_file_name(
#id int,
#table nvarchar(50)
)as
DECLARE #SQL nvarchar(max);
SET #SQL = 'select [filename] from ' + #table + ' where id = ' + #id
EXECUTE (#SQL)

sql server 2008 create columns using a while

I need to create about 300 columns for a table and I don't want to to it manually.
How can I do this?
I want to have columns like
Bigint1..to..Bigint60
Text1 ..to..Text60
and so on.
IF (NOT EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = 'mytbl'))
begin
create table OBJ_AttributeValues(
ObjectID numeric(18,0) not null
);
end
else
begin
DECLARE #A INT
set #A = 1;
WHILE(#A <=60)
BEGIN
alter table OBJ_AttributeValues
add ...............................
set #A = #A+1;
END
end
What should I write instead of "..."?
You will need to use dynamic SQL for that, something like
DECLARE #SSQL VARCHAR(1000)
DECLARE #A INT
set #A = 1;
WHILE(#A <=60)
BEGIN
SET #SSQL = 'alter table OBJ_AttributeValues add Bigint' + CAST(#A as varchar) + ' bigint'
EXEC (#SSQL)
set #A = #A+1;
END
This isn't really a good idea, you should take the time to write the sql or just copy-paste the columns from Excel or something like that. You also shouldn't be using the TEXT data type, is deprecated and filled with restriction (use VARCHAR(MAX) instead if you need). That said, here is a way using dynamic SQL:
DECLARE #BigintCols NVARCHAR(MAX), #TextCols NVARCHAR(MAX)
DECLARE #Query NVARCHAR(MAX)
SET #BigintCols = ''
SET #TextCols = ''
SELECT #BigintCols = #BigintCols + N'Bigint' + CAST(number AS NVARCHAR(2)) + N' BIGINT,',
#TextCols = #TextCols + N'Text' + CAST(number AS NVARCHAR(2)) + N' TEXT,'
FROM master..spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 60
ORDER BY number
SET #Query = '
CREATE TABLE OBJ_AttributeValues(ObjectID numeric(18,0) not null,'+#BigintCols+
LEFT(#TextCols,LEN(#TextCols)-1)+')'
EXECUTE sp_executesql #Query
Oh, you should probably read about dynamic sql first.

Stored Procedure and populating a Temp table from a linked Stored Procedure with parameters

I have a Stored Procedure (SP) in which I pass in one value. In this SP, I am trying to create/populate a Temp Table from the result of another SP that is on a Linked/remote server. That is I am trying to executute an SP in my SP and populate a temp table which my query will use.
I have tried using the following syntax, but it does not work as it seems openquery does not like the "+" or the #param1 parameter.
select * into #tempTable
from openquery([the Linked server],'exec thelinkedSPname ' + #param1)
If I have the parameter value hard coded in this it works fine.
select * into #tempTable
from openquery([the Linked server],'exec thelinkedSPname 2011')
I have also gone as far as manually building the temp table and trying to execute the linked SP but that does not work as well.
create table #tempTable(
.
.
.
)
insert into #tempTable
(
.
.
.
)
Exec [the Linked server],'exec thelinkedSPname ' + #param1
Any suggestions as to how to populate a temp table from within a SP that executes a SP via a linked server. Note the above SQL is only pseudo code
I think you are gonna need dynamic SQL, since you can't pass the parameter to an OPENQUERY like that (but first visit this link) So you would have something like this:
create table #tempTable(
.
)
DECLARE #param1 VARCHAR(10), #Query VARCHAR(8000)
SET #param1 = '2011'
SET #Query = '
SELECT *
FROM OPENQUERY([Linked Server],''exec thelinkedSPname '' + #param1+''')'
INSERT INTO #tempTable
EXEC(#Query)
With the usual disclaimers about guarding dynamic SQL, you can do this without OPENQUERY etc. Just call sp_executesql remotely:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'EXEC thelinkedSPname ' + #param1 + ';';
INSERT #temptable EXEC [LinkedServerName].database.dbo.sp_executesql #sql;
I use this method quite frequently:
DECLARE #YEAR AS VARCHAR(4) SET #YEAR = 2015
DECLARE #SQL AS VARCHAR(MAX)
DECLARE #OPENQUERY AS VARCHAR(MAX)
DECLARE #LINKEDSERVER AS VARCHAR(MAX) SET #LINKEDSERVER = 'Name of Linked Server here with out brackets'
SET #SQL='
Select
tbl1.*
FROM
dbo.Table_ON_LINKED_SERVER AS tbl1
WHERE
tbl1.number_id = ''''1''''
AND YEAR(tbl1.DATETIME) = ' + #YEAR + '
AND tbl1.NAME <> ''''%JONES%''''
'''
SET #OPENQUERY = 'SELECT * INTO ##GLOBAL_TEMP_NAME FROM OPENQUERY(['+ #LINKEDSERVER +'],''' + #SQL + ')'
--SELECT #OPENQUERY
EXEC(#OPENQUERY)
Two words: Dynamic Query.
Try this:
DECLARE #TSQL varchar(8000)
SELECT #TSQL = 'SELECT * INTO #tempTable FROM OPENQUERY([the Linked server],''exec [the Linked server].DBName.dbo.thelinkedSPname ' + #param1 + ''')'
EXEC (#TSQL)
This is well documented here:
How to pass a variable to a linked server query
With some care you could use a shared temp table:
DECLARE #Qry AS VARCHAR(MAX)
SET #Qry = 'select * into ##tempTable from openquery([the Linked server],''exec thelinkedSPname ' + #param1 + ''')'
EXEC (#Qry)
-- Now just use the shared Temp table, or I suppose you could copy it to a temp table just as you wanted it:
SELECT * INTO #tempTable FROM( SELECT * FROM ##tempTable)tbl
DROP TABLE ##tempTable

Use variables expansions in schema in sql server 2005 / 2008

I am trying to create stored procedure which will decide which language to use based on a parameter passed?
How can I do something like this ?
declare #en varchar(50) = 'en'
declare #fi varchar(50) = 'fi'
select * from [#en].[TestingLanguagesInNameSpacesDelMe]
select * from [#fi].[TestingLanguagesInNameSpacesDelMe]
I would also advocate a single-table/language-segmented design where all the languages are stored in a single table with a language column.
Dynamic SQL may be potentially vulnerable to SQL injection. If the #LANG variable cannot be trusted (or if the schema and table need to be validated to be a valid combination), you can check it against sys.schemas using a technique like this:
DECLARE #template AS varchar(max)
SET #template = 'SELECT * FROM {object_name}'
DECLARE #object_name AS sysname
SELECT #object_name = QUOTENAME(s.name) + '.' + QUOTENAME(o.name)
FROM sys.objects o
INNER JOIN sys.schemas s
ON s.schema_id = o.schema_id
WHERE o.object_id = OBJECT_ID(QUOTENAME(#LANG) + '.[TestingLanguagesInNameSpacesDelMe]')
IF #object_name IS NOT NULL
BEGIN
DECLARE #sql AS varchar(max)
SET #sql = REPLACE(#template, '{object_name}', #object_name)
EXEC (#sql)
END
Now you have the power and flexibility of dynamic sql, but it is not vulnerable to injection.
You could use Dynamic SQL:
DECLARE #SQL varchar(MAX)
SELECT #SQL = 'select * from ['
+ #LANG
+ '].[TestingLanguagesInNameSpacesDelMe]'
sp_executesql #SQL
But I would consider using a SINGLE table with an indexed COLUMN with the language:
select * from [dbo].[TestingLanguagesInNameSpacesDelMe] where [lang] = #LANG
How about this?
declare #sqlCall varchar(200)
set #sqlCall = 'select * from [' + #language + '].[TestingLanguagesInNameSpacesDelMe]'
exec #sqlCall
I would have to agree with the other posted answer, make a column that indicates the language and just filter on the language..
-- Yep I also concluded that it is the dynamic sql ..
declare #tableName varchar(50)
set #tableName ='TestingLanguagesInNameSpacesDelMe'
declare #sql nvarchar(200)= 'select * from '
declare #fi varchar(40) = 'fi'
declare #en varchar(50) = 'en'
set #sql = #sql + '[' + #fi + '].[' + #tableName + ']'
print #sql
exec sp_executesql #sql

Resources