SQL: Select dynamic column name based on variable - sql

I have a Microsoft SQL stored procedure whose column name I want to set via a variable that is passed into it:
CREATE PROCEDURE [My_Procedure]
#myDynamicColumn varchar(50)
AS BEGIN
SELECT 'value' AS #myDynamicColumn
END
This does not work ("Incorrect syntax"). If I wrap the column name with [ ]:
SELECT 'value' AS [#myDynamicColumn]
The column name literally outputs as '#myDynamicColumn' instead of the actual value. Is there any way to do this? I've looked into dynamic SQL articles but nothing is quite what I'm asking for.

EXEC ('SELECT ''value'' AS ' + #myDynamicColumn)

You could build your query into a string and use exec
CREATE PROCEDURE [My_Procedure]
#myDynamicColumn varchar(50)
AS BEGIN
EXEC('SELECT ''value'' AS ' + #myDynamicColumn)
END

Both the upvoted answers are very dangerous here, both are wide open to injection attacks and should not be used.
When injecting dynamic object names you must ensure you properly quote your object names. SQL Server has a built in function for that, QUOTENAME. Thus what you should actually be doing is the following:
CREATE PROCEDURE [My_Procedure] #myDynamicColumn sysname
AS BEGIN
DECLARE #SQL nvarchar(MAX) = N'SELECT ''value'' AS ' + QUOTENAME(#myDynamicColumn) + N';';
EXEC sys.sp_executesql #SQL;
END
You'll note I also change the data type of the parameter to sysname, a synonym for nvarchar(128) NOT NULL, which is the data type SQL Server uses internally for object names.

Related

how to pass table name as parameter to sql table valued function?

I want to pass the table name as a parameter to table_valued function in MS SQL Server
CREATE FUNCTION maxid
(
-- Add the parameters for the function here
#tblname sysname,
#feild nvarchar(max),
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
select ISNULL(max(#feild),0)+1 from #tblname
)
You cannot use dynamic SQL in table valued or any other TSQL function. However, the code you provide seems to be used to obtain the next value of some identifier or counter field. The way you want to do it is highly deprecated and leads to concurrency problems.
Indeed, SQL Server can do it using at least two standard methods:
using a sequences
creating an identity column
I have done it but with store procedure
CREATE procedure [dbo].[get_maxid]
#tblname nvarchar(max),
#col nvarchar(max)
as
Begin
declare #sql nvarchar(max);
set #sql='select ISNULL(MAX('+#col+'),0)+1 as id from '+ QUOTENAME( #tblname)
execute sp_executesql #sql
End
Now Execute Store Procedure
exec get_maxid #col='col_name',#tblname='tbl_name'

Using a variable to name query results column

Is there a way to use a variable to name a column in SQL query results? My example below gives an "Incorrect syntax" error?
declare #ColumnName varchar(100) = 'Column 1'
Select CustomerNumber as #ColumnName
from Customers
Generally, SQL isn't going to handle defining variables to use as column aliases. This means you'll likely have to resort to using dynamic SQL, which involves building your query and then executing it manually via the sp_executesql procedure.
The following is an example of your existing query executed dynamically using SQL Server :
-- Define your variable
DECLARE #ColumnName VARCHAR(100) = 'Column 1'
-- Define your SQL query
DECLARE #SQL NVARCHAR(200) = 'SELECT CustomerNumber AS ' + #ColumnName + ' FROM Customers'
-- Execute your query dynamically
EXEC sp_executesql #SQL

T-SQL: Variable Scope

I am trying to store the results of an SQL query into a variable.The query simply detects the datatype of a column, hence the returned result is a single varchar.
SET #SQL =
'declare ##x varchar(max) SET ##x = (select DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE Table_name = ' +char(39)+#TabName+char(39) +
' AND column_name = ' +char(39)+#colName+char(39) + ')'
EXECUTE (#SQL)
Anything within the 'SET declaration' cannot access any variables outside of it and vice versa, so I am stuck on how to store the results of this query in a varchar variable to be accessed by other parts of the stored procedure.
You dont need a dynamic query to achieve what you want, below query will give the same result as yours.
declare #x varchar(max)
declare #tableName varchar(100), #ColumnName varchar(50)
set #tableName = 'Employee'
set #ColumnName = 'ID'
select #x = DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
where
Table_Name = #tableName
and column_name = #ColumnName
select #x
All user-defined variables in T-SQL have private local-scope only. They cannot be seen by any other execution context, not even nested ones (unlike #temp tables, which can be seen by nested scopes). Using "##" to try to trick it into making a global-variable doesn't work.
If you want to execute dynamic SQL and return information there are several ways to do it:
Use sp_ExecuteSQL and make one of the parameters an OUTPUT parameter (recommended for single values).
Make a #Temp table before calling the dynamic SQL and then have the Dynamic SQL write to the same #Temp table (recommended for multiple values/rows).
Use the INSERT..EXEC statement to execute your dynamic SQL which returns its information as the output of a SELECT statement. If the INSERT table has the same format as the dynamic SQL's SELECT output, then the data output will be inserted into your table.
If you want to return only an integer value, you can do this through the RETURN statement in dynamic SQL, and receive it via #val = EXEC('...').
Use the Session context-info buffer (not recommended).
However, as others have pointed out, you shouldn't actually need dynamic SQL for what you are showing us here. You can do just this with:
SET #x = ( SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
WHERE Table_name = #TabName
AND column_name = #colName )
You may want to consider using the sp_executesql stored procedure for dynamic sql.
The following link provides a good usage example of sp_executesql procedure with output parameters:
http://support.microsoft.com/kb/262499

Using a Database Name as a Variable

I'm using this method (How to use a variable for the database name in T-SQL?) to pass a DBname into a variable. This variable is then used with something like this (simplified):
Select column From #DBname
However, when executed...You get, Command(s) completed successfully
Is there a way to adapt this method so that you can see the results of a query? (Not just create or alter a DB)
Something like this should work for you.
[[put code here that sets up #DBname]]
DECLARE #stmt NVARCHAR(MAX) = 'SELECT column FROM ' + #DBname;
EXEC sp_ExecuteSQL #stmt;
Edit: Note that I'm assuming you're using SQL server (you didn't specify in the question).

SQL Server: Sanitizing #param against injection attacks

For the sake of argument, let's just say I have to create a local variable containing a SQL query that has an INSERT:
DECLARE #insert NVARCHAR(MAX)
SELECT #insert = 'INSERT INTO [dbo].[' + #table + '] VALUES...
EXEC (#insert)
This INSERT is also going to contain a column value:
DECLARE #insert NVARCHAR(MAX)
SELECT #insert =
'INSERT INTO [dbo].[' + #table + '] VALUES (N''' + #message + ''')'
EXEC (#insert)
Now, I'm obviously concerned about an injection attack, and would like to ensure that #message's value can't make #insert's value malicious or malformed as a query to EXEC.
This brings us to my question: is escaping the ' characters in #message sufficient? Are there any other characters that could appear in #message that could escape out?
Example:
DECLARE #insert NVARCHAR(MAX)
SELECT #message = REPLACE(#message,'''','''''')
SELECT #insert =
'INSERT INTO [dbo].[' + #table + '] VALUES (N''' + #message + ''')'
EXEC (#insert)
(When I say "have to", this is because my query is in a stored procedure, and this stored procedure accepts #table, which is the destination table to INSERT into. I'm not interested in discussing my architecture or why the table to INSERT into is "dynamically" specified via a procedure parameter. Please refrain from commenting on this unless there's another way besides EXEC()ing a query to specify a table to INSERT into when then table name is received as a procedure parameter.)
Use sp_executesql and the built-in quotename(). This article, The Curse and Blessings of Dynamic SQL, is pretty much the definitive reference.
You could first query the schema information with regular T-SQL and make sure the table name exists first. This way, if it's malformed SQL, it won't execute as code. It will just be a VARCHAR table name.
DECLARE #Table AS VARCHAR(MAX)
DECLARE #Exists AS BIT
SET #Table = 'Vicious malformed dynamic SQL'
SELECT #Exists = COUNT(TABLE_NAME)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #Table
IF (#Exists = 1)
BEGIN
PRINT 'Table exists'
-- Execute dynamic SQL.
END
ELSE
PRINT 'Invalid table'
(Or simply use IF EXISTS (SELECT ....) )
Rather than calling EXEC(#somesql), I suggest using the sp_executesql stored procedure. Specifically, this allows you to pass parameters, and the system will check that the parameters are valid.
Apparently there's a 128-length limit to quotename(), even in 2008 according to my test, since it expects a SQL identifier. The reference suggests creating a quotestring() function, which does the same thing as:
REPLACE(#variable,'''','''''')
Therefore I am proposing that the answer is to create a function out of the REPLACE() above, like so:
CREATE FUNCTION quotestring(#string nvarchar(MAX))
RETURNS nvarchar(MAX) AS
BEGIN
RETURN(REPLACE(#string,'''',''''''))
END
...Unless I've misunderstood something.
When writing dynamic SQL you'll want to parameterise as much as possible, and only resort to character escaping when you absolutely have to. You can't parameterise #table, but you can parameterise #message.
DECLARE #insert NVARCHAR(MAX)
set #insert = 'INSERT INTO [dbo].' + quotename(#table) + ' values(#message)'
exec sys.sp_executesql #insert, N'#message nvarchar(max)', #message = #inMessage;
There are a lot of ways attackers can exploit dynamic SQL, including buffer length attacks and using unicode equivalent characters. I encountered an example once where escaping the single quote char still had a vulnerability where one of the unicode equivalents of the quote char could be passed in. Part of the software stack was doing a unicode to ascii conversion, so it was possible to inject a quote back in after they were escaped. Ouch.