This question already has answers here:
Can I pass column name as input parameter in SQL stored Procedure
(9 answers)
Closed 4 years ago.
I need some help with my SQL logic, and I've been working (and researching) this for 2 days now with zero success.
My goal is to try an pass a variable from an ASP page to a stored procedure, which is utilizing the variable as criteria for a column name in the where clause.
So for example (a simplified version of my query):
#strDept nvarchar(10), #strUser nvarchar(30)
-- The asp page will pass f18 to #strDept & Ted Lee to strUser
-- f18 is the column name in my database that I need in the where.
select x, y, z from table1 where #strDept in (#strUser)
-- and this is the select statement, notice the where clause.
The stored procedure does execute, but it returns no values and I know its treating the #strDept as a literal nvarchar and not a column name.
So I guess my question is, how do I get SQL Server 2005 to treat my #sqlDept variable as a column name?
The reason you can't find guidance on how to do this is that it's a really bad idea.
Sooner or later, someone is going to pass a "column name" of 1 ;drop database badidea. Which will be a blessing for all concerned.
Read up on SQL Injection, and rethink your design.
If this is an internal company application why is everyone re-iterating and beating SQL Injection to death... Its very simple to just use Dynamic SQL.
If you are comfortable that these are only internal users using this then its very simple. Here is the concept. You essentially write a SQL Statement that writes a string that is really a SQL statement and then execute it.
CREATE Procedure myDynamicProcedure
#strDept nvarchar(10),
#strUser nvarchar(30)
as
BEGIN
1. Declare a variable to store the SQL Statement.
DECLARE #SQL varchar(max)
2. SET your #SQL Variable to be the SELECT Statement. Basically you are building it so it returns what you are wanting to write. Like this:
SET #SQL = 'select x, y, z from table1 where' + #strDept +
' in ' + #strUser
3. Execute the #SQL Statement and it will be exactly like you ran:
SELECT x,y,z from table1 where f18 = 'Ted Lee'
EXEC (#SQL)
END
Why do you want to make column name dynamic? What do you plan to achieve? You can use dynamic query like answer above but injection attacks may start.
If you explain what you want to do with that maybe we can recommend another solution.
You can use some dynamic sql e.g.
DECLARE #sqlDept VARCHAR(100)='CURRENT_TIMESTAMP';
EXEC('SELECT '+#sqlDept)
In your case this will be
DECLARE #strDept nvarchar(10)='dept1'
,#strUser nvarchar(30)='user1';
DECLARE #DynamicSql nvarchar(1000);
SET #DynamicSql='select x, y, z from table where '+#strDept+' in ('''+#strUser+''')';
Then
SELECT #DynamicSql;
Will give you:
select x, y, z from table where dept1 in ('user1')
To execute this statement you do this as
EXEC(#DynamicSql);
Another alternative is to use a small bit of substitution in the proc. This still uses dynamic SQL, but you are never executing user supplied values.
DECLARE #userSuppliedValue VARCHAR(50) = 'JOHNNY DROP TABLES'
DECLARE #substValue VARCHAR(50)
IF #userSuppliedValue = 'Table1'
SET #substValue = 'Table1'
IF #userSuppliedValue = 'Table2'
SET #substValue = 'Table2'
/*Repeat for N permutations*/
/* Throw an error if you think its necessary to do so when no match is found*/
IF #substValue IS NULL
RAISERROR(1,1,'errah')
EXEC ('SELECT * FROM ' + #substValue)
I think the best way is to build a dynamic SQL and add a lookup to see if the column exist and prevent SQL injection in the column name.
declare #strDept nvarchar(10), #strUser nvarchar(30),
#sql nvarchar(300), #found smallint
set #strDept = 'f18'
set #strUser = 'Ted Lee'
set #found = (SELECT count(*)
FROM syscolumns
WHERE id=OBJECT_ID('table1') AND name=''+#strDept+'')
set #sql = 'select x, y, z from table1 where ' + #strDept + ' in ('''+#strUser+''')'
if #found = 1 exec (#sql)
SQL injection testing : See SQL FIDDLE : http://www.sqlfiddle.com/#!6/df3f6/18/0
DECLARE #value varchar(10)
SET #value = 'intStep'
DECLARE #sqlText nvarchar(1000);
SET #sqlText = N'SELECT ' + #value + ' FROM dbo.tblBatchDetail'
Exec (#sqlText)
Related
My table has column names m1,m2,m3...,m12.
I'm using iterator to select them and insert them one by one in another table.
In this iterator I'm trying to generate filed names with:
'['+concat('m',cast(#P_MONTH as nvarchar))+']'
where #P_MONTH is incrementing in each loop.
so for #P_MONTH = 1 this suppose to give [m1] which works fine.
But when I run query I get:
Conversion failed when converting the nvarchar value '[m1]' to data
type int.
And if I put simply [m1] in that select it works ok.
How to concat filed name so it can be actually interpreted as filed name from certain table?
EDIT
Here is full query:
DECLARE #SQLString nvarchar(500),
#P_YEAR int,
#P_MONTH int = 1
set #P_YEAR = 2018
WHILE #P_MONTH < 13
BEGIN
SET #SQLString =
'INSERT INTO [dbo].[MASTER_TABLE]
(sector,serial,
date, number, source)'+
'SELECT ' + '[SECTOR],[DEPARTMENT]' +
QUOTENAME(cast(CONVERT(datetime,CONVERT(VARCHAR(4),#P_YEAR)+RIGHT('0'+CONVERT(VARCHAR(2),#P_MONTH),2)+'01',5) as nvarchar))+
QUOTENAME ('M',cast(#P_MONTH as nvarchar)) +
'EMPLOYED' +
'FROM [dbo].[STATS]'+
'where YEAR= #P_YEAR'
EXECUTE sp_executesql #SQLString
SET #P_MONTH = #P_MONTH + 1
END
It's still not working. It executes successfully but it does nothing.
Good day,
Let's create a simple table for the sake of the explanation
DROP TABLE IF EXISTS T
GO
CREATE TABLE T(a1 INT)
GO
INSERT T(a1) VALUES (1),(2)
GO
SELECT a1 FROM T
GO
When we are using a query like bellow, the server parse the text as a value and not as a column name
DECLARE #String NVARCHAR(10)
SELECT #String = '1'
--
SELECT '['+concat('a',cast(#String as nvarchar))+']'
FROM T
GO
This mean that the result will be 2 rows with no name for the column and the value will be "[a1]"
Moreover, the above query uses the brackets as part of the string.
One simple solution is to use the function QUOTENAME in order to add brackets around a name.
Another issue in this approach is the optional risk of SQL Injection. QUOTENAME might not be perfect solution but can help in this as well.
If we need to use entities name dynamically like in this case the column name then for most cases using dynamic query is the best solution. This mean to use the Stored Procedure sp_executesql as bellow
DECLARE #String INT
SELECT #String = 1
DECLARE #SQLString nvarchar(500);
SET #SQLString =
'SELECT ' + QUOTENAME(concat('a',cast(#String as nvarchar))) + ' FROM T'
EXECUTE sp_executesql #SQLString
GO
I'm creating a report using sql scripts through management studio and I'm getting the error " Must Declare the scalar variable "#Account". I've been reading other similar questions on this portal but they are related to c#
I'm currently trying to reduce the code on the script so I decided to put a sql script into a variable because depending on a condition the where condition will change. Below is an example of the code
Declare #Account int = 1 , #SQL varchar(max)=''
Select #SQL = N'Select ColumnA,ColumnB, ColumnC from Table1 where ColumnA =1'
if #Account IS NULL
Begin
exec(#SQL)
end
--Here is where the error is hapening
else
begin
--This is the line causing the error
Select #SQL = #SQL + 'AND ColumnB=#Account"
exec(#SQL)
end
If I type manually the value of the variable next to "ColumnB=" it works but the account number will be selected by the user executing the script. I'm thinking on maybe building a temp table to capture the variable value and then do a sub query on the where condition but maybe the solution to this error may be more easier
You want sp_executesql:
select #SQL = #SQL + 'AND ColumnB=#Account';
exec sp_executesql #SQL, N'#Account int', #Account=#Account;
This is how you pass parameters into a dynamic SQL statement in SQL Server. I strongly recommend that you only use sp_executesql to execute SQL statements -- even when you don't have parameters. Using it makes it easy to implement parameters when you need them.
You are passing in '#Account' into the #SQL variable -- the underlying EXEC cannot see that variable.
One way of fixing this would instead be to do this:
Select #SQL = #SQL + 'AND ColumnB=' + CAST(#Account as varchar)
I'm currently having trouble writing a stored procedure and setting the value of a variable of type int to the results of a select statement with a variable as the tablename. I've looked at old threads and tried multiple methods, but no luck. If I'm not getting an error regarding the tablename, I end up getting an error with a variable conversion issue. I've been working on this for too long and any help would be appreciated. Below is a portion of my code. Thanks
DECLARE #BATCHNUMBER VARCHAR --value set in earlier code
DECLARE #ETABLE VARCHAR(50); --the table name
DECLARE #FIRSTDOCID INT;
SET #ETABLE = 'tablename_' + #BATCHNUMBER; --CREATE FIRST TABLE NAME
SELECT #FIRSTDOCID = MIN(D0CID) FROM #ETABLE
The error I get is: Must declare the table variable "#ETABLE"
You are trying to select from a VARCHAR, not a table. The only way to make this work is by using Dynamic SQL.
DECLARE #SQL NVARCHAR(250);
SET #SQL = 'SELECT #OUTPUT = MIN(D0CID) FROM ' + QuoteName(#ETABLE);
EXEC sp_executeSql #SQL, N'#output INT OUTPUT', #FIRSTDOCID OUTPUT;
SELECT #FIRSTDOCID;
However, I would not suggest using Dynamic SQL as this often leads to SQL injection.
You'll probably have to do something like use exec if you're dynamically building the query:
SET #QUERY = "SELECT" + ...etc.
exec(#QUERY)
Since ETABLE is a varchar, and not, as expected, a 'table variable'.
This may be an easy answer but I've been staring at it for too long...
I have the following query that takes a stored procedure input parameter as a variable name and counts the records in that table. I'd like to retrieve the results of the dynamic statement (#toStartStr) into a variable (#toStart).
-- #tempTableName = SProc input parameter
DECLARE #toStartStr nvarchar(150);
DECLARE #toStart int;
SET #toStartStr = 'SELECT #toStart = COUNT(ID) FROM ' + #tempTableName;
EXEC(#toStartStr);
Right now, an error suggests that #toStart cannot be concatenated with the string SELECT, but this is the gist of what I want. Can anyone see what I'm doing wrong? Or suggest an alternative? FYI SQL 2008 R2. Thanks.
DECLARE #sql NVARCHAR(255);
DECLARE #toStart INT;
SET #sql = N'SELECT #toStart = COUNT(ID) FROM ' + QUOTENAME(#tempTableName);
EXEC sp_executesql #sql, N'#toStart INT OUTPUT', #toStart OUTPUT;
PRINT #toStart;
However there is a much easier and more efficient way to do this, if you're okay with ignoring current in-flight transactions (and you're using SQL Server 2005 or better - please specify the version when asking questions!).
DECLARE #toStart INT;
SELECT #toStart = SUM(rows)
FROM sys.partitions
WHERE [object_id] = OBJECT_ID(#tempTableName)
AND index_id IN (0,1);
PRINT #toStart;
Just for completeness, here is a solution for SQL Server 2000, which also doesn't require any special privileges (just connect and member of public):
DECLARE #toStart INT;
SELECT #toStart = [rows]
FROM sysindexes
WHERE id = OBJECT_ID(#tempTableName)
AND indid IN (0,1);
PRINT #toStart;
That said, if you're using a count to determine what the next ID might be, or something like that, I think you're approaching this the wrong way, since rows can be deleted and if it's an identity column values can be skipped due to rollbacks.
it is very easy to use the following SQL to get value for a specific primary key: ID from a specific table: myTale:
DECLARE #v_maxID bigint;
SELECT #v_maxID = MAX(ID) FROM myTable;
What I need is a generic SQL codes to get the max value for a key from a table, where both key and table are specified as varchar(max) types as parameters:
DECLARE #v_maxID bigint;
-- SELECT #v_maxID = MAX(#p_ID) FROM #p_Table;
I comment out the SELECT since it is not working. I tried to build a SQL string and I can EXEC it, but I cannot get the max value back to my local variable(#v_maxID). Any suggestions?
DECLARE #max bigint, #sql nvarchar(max)
SET #sql = N'SELECT #max = MAX(' + #p_ID + ') FROM ' + #p_Table
EXEC sp_executesql
#query = #sql,
#params = N'#max bigint OUTPUT',
#max = #max OUTPUT
PRINT #max
Users are choosers, but I consider this an ugly idea (for being overgeneralized). And unoptimizable. Just write the SQL.
Correct me if I'm wrong, but don't you just want:
SELECT MAX(ID) FROM mytable
Just build the query at the app level, thus the query running would be just like the one above. Doing in on sql will certainly open you for sql injection, since you have to use exec(). Also in either case, be careful with user input.
As BC states, you have to use sp_executesql with an OUTPUT parameter.
How to specify output parameters when you use the sp_executesql stored procedure in SQL Server