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
Related
I have a SELECT statement that can produce a list of values:
DECLARE #ValueList varchar(Max);
SELECT #ValueList = COALESCE(#ValueList + ',', '') + CAST(Val AS varchar(max))
FROM
(SELECT TOP (100) PERCENT tblSampleTable.SomeIDNumber AS Val
FROM tblSampleTable) AS ValuesThisYear
PRINT #ValList
This returns a list with values something like
val1,val2,val4,val9,
etc., ehich I can then feed into a stored procedure, or manage some other way.
Now I want to have the query that gets assessed for the list of values to be dynamic, maybe passed in or from another stored procedure, similar to this:
DECLARE #ValueList varchar(Max);
DECLARE #TSQL varchar(Max);
SET #TSQL = {stored proc to get base query}
SELECT #ValueList = COALESCE(#ValueList + ',', '') + CAST(Val AS varchar(max))
FROM
(#TSQL) AS ValuesThisYear
PRINT #ValList
I know that's the wrong syntax for including #TSQL, and that's what I'm trying to find out. I've viewed a number of threads and tried a number of methods, but am still not able to incorporate this dynamic part.
The tricky part seems to be the making of the list (the COALESCE and CAST statements), where I incorporate #ValList as part of the returned string.
Any help would be appreciated!
Dynamic SQL is usually about
Creating a variable that contains the exact SQL you want to run
Then using EXEC (#SQLvariable) to run that code
For example (not for production yet!) I've added a new variable #CustomSQL
DECLARE #ValueList varchar(Max);
DECLARE #TSQL varchar(Max);
DECLARE #CustomSQL varchar(Max);
SET #TSQL = {stored proc to get base query}
SET #CustomSQL =
'SELECT COALESCE(#ValueList + '','', '''') + CAST(Val AS varchar(max))
FROM (
' + #TSQL + '
) As ValuesThisYear;'
PRINT #CustomSQL
EXEC (#CustomSQL)
Notice that adding text/strings (e.g., the #TSQL variable) have to be entered as exact strings rather than their variable names. Also note apostrophes - you need to use '' every time you wish to refer to a '.
I also removed the variable name from the SELECT #ValueList = ... because the dynamic SQL cannot actually reference the variables - it has its own scope (?cannot remember the correct word) and doesn't have access to the variables. Solutions to this include
Using a temporary table e.g., #temp which can be referenced
Using the OUTPUT clause
Personally, I would approach it a different way - use the T-Sql provided to put data into a temporary table. Then use the temporary table in the other statement e.g.,
DECLARE #ValueList varchar(Max);
DECLARE #TSQL varchar(Max);
SET #TSQL = {stored proc to get base query}
DECLARE #CustomSQL varchar(Max)
CREATE TABLE #temp (Val varchar(1000))
SET #CustomSQL = 'INSERT INTO #temp (Val) ' + #TSQL
EXEC (#CustomSQL)
SELECT #ValueList = COALESCE(#ValueList + ',', '') + CAST(Val AS varchar(max))
FROM #temp As ValuesThisYear;
PRINT #ValList
I almost never get my dynamic SQL correct first try. Suggestions
Keep it as simple as possible
Before having a version that runs (e.g., EXEC (#CustomSQL)), comment the EXEC out and PRINT it instead.
Here are some examples from previous posts I've done recently
Query for R Machine Learning Services - Filtering Categories in Where Clause
Bottom of Dynamic columns depend on previous dynamic columns - TSQL
I have the following sql query
Select * from Name
where surname in ('test1', 'test2')
which works
But I wanted to do the following
DECLARE #Surname as VARCHAR(100)
set #Surname = 'test1' + ',' + 'test2'
Select * from Name
where surname in #Surname
this is the actual query used
DECLARE #COESNo as VARCHAR(100)
set #COESNo = '121108883' + ',' + '121108890'
declare #sql varchar(max)
set #sql = 'select [LEI_ACCEPT] , [PREFERED_LEI] , [INSPECTION_COMPANY], [INSP_ACCEPT] from [CERTIFICATE_DETAILS] where [Certificate_no] in ('
set #sql = #sql + #COESNo + ')'
exec #sql
get the error
The name 'select [LEI_ACCEPT] , [PREFERED_LEI] , [INSPECTION_COMPANY], [INSP_ACCEPT] from [CERTIFICATE_DETAILS] where [Certificate_no] in (121108883,121108890)' is not a valid identifier.
doesn't seem to work
any ideas
There are two popular solutions.
First one is to build string with query and use sp_executesql to run it.
Second one is to write (or find) function (something like SplitText2Table()) which converts comma separated string to table and write query which use this function -- something like:
select *
from name
where surname in (select item from SplitText2Table(#surnames))
This is not a solution for the exact problem in the question. But maybe (?) you could instead use this workaround:
declare #SurName1 nvarchar(20)
declare #SurName2 nvarchar(20)
set #SurName1='test1'
set #SurName2='test2'
select * from [Name] where [surname] in (#SurName1, #SurName2)
you will need to create your statement and execute - like
declare #sql varchar(max)
set #sql = 'Select * from [Name] where surname in ('
set #sql = #sql + #surname + ')'
After creating the statement just say
exec #sql
You can also check the formed query is correct or not using print
print #sql
Please use print before execution of the command (this will help you in correcting the query is there is any error.).
EDIT:
As per the comment - Since Name is a keyword for SQL - we will need to use square bracket against it. I have modified the statement in my answer.
Edit 2: Based on further comments -
Firstly I would like to know the datatype of Certificate_no column in your database.
If it is a varchar field you will need to have single quotes around each value.
The name SELECT [LEI_ACCEPT] , [PREFERED_LEI] , [INSPECTION_COMPANY], [INSP_ACCEPT] FROM[CERTIFICATE_DETAILS] WHERE [Certificate_no] IN ('121108883','121108890')
You will need to create -
DECLARE #COESNo as VARCHAR(100)
set #COESNo = '''121108883''' + ',' + '''121108890'''
Since Certificate number is varchar the string build will need a single quote in formed query which will appear with three single quotes.
I have created Working DEMO for you on SQL FIDDLE - CLICK HERE
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)
I have a SQL stored procedure of the form
SELECT [fields] FROM [table] WHERE #whereSql
I want to pass the procedure an argument (#whereSql) which specifies the entire WHERE clause, but the following error is returned:
An expression of non-boolean type specified in a context where a condition is expected
Can this be done?
The short answer is that you can't do it like this -- SQL Server looks at the contents of a variable as a VALUE. It doesn't dynamically build up the string to execute (which is why this is the correct way to avoid SQL injection attacks).
You should make every effort to avoid a dynamic WHERE as you're trying to do, largely for this reason, but also for the sake of efficiency. Instead, try to build up the WHERE clause so that it short-circuits pieces with lots of ORs, depending on the situation.
If there's no way around it, you can still build a string of your own assembled from the pieces of the command, and then EXEC it.
So you could do this:
DECLARE #mywhere VARCHAR(500)
DECLARE #mystmt VARCHAR(1000)
SET #mywhere = ' WHERE MfgPartNumber LIKE ''a%'' '
SELECT #mystmt = 'SELECT TOP 100 * FROM Products.Product AS p ' + #mywhere + ';'
EXEC( #mystmt )
But I recommend instead that you do this:
SELECT TOP 100 *
FROM Products.Product AS p
WHERE
( MfgPartNumber LIKE 'a%' AND ModeMfrPartNumStartsWith=1)
OR ( CategoryID = 123 AND ModeCategory=1 )
I believe this can be done using Dynamic SQL. See below:
CREATE PROCEDURE [dbo].[myProc]
#whereSql nvarchar(256)
AS
EXEC('SELECT [fields] FROM [table] WHERE ' + #whereSql)
GO
That said, you should do some serious research on dynamic SQL before you actually use it.
Here are a few links that I came across after a quick search:
http://www.sommarskog.se/dynamic_sql.html
http://msdn.microsoft.com/en-us/library/aa224806%28SQL.80%29.aspx
http://www.itjungle.com/fhg/fhg100505-story02.html
Make sure you read this fully
www.sommarskog.se/dynamic_sql.html
Dynamic SQL listed in some of the Answers is definitely a solution. However, if Dynamic SQL needs to be avoided, one of the solutions that I prefer is to make use of table variables (or temp tables) to store the parameter value that is used for comparison in WHERE clause.
Here is an example Stored Procedure implementation.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
declare #myTempTableVar Table(param1 varchar(50))
insert into #myTempTableVar values(#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
In case you want to pass in multiple values, then the comma separated values can be stored as rows in the table variable and used in the same way for comparison.
CREATE PROCEDURE [dbo].[myStoredProc]
#parameter1 varchar(50)
AS
--Code Block to Convert Comma Seperated Parameter into Values of a Temporary Table Variable
declare #myTempTableVar Table(param1 varchar(50))
declare #index int =0, #tempString varchar(10)
if charindex(',',#parameter1) > 0
begin
set #index = charindex(',',#parameter1)
while #index > 0
begin
set #tempString = SubString(#parameter1,1,#index-1)
insert into #myTempTableVar values (#tempString)
set #parameter1 = SubString(#parameter1,#index+1,len(#parameter1)-#index)
set #index = charindex(',',#parameter1)
end
set #tempString = #parameter1
insert into #myTempTableVar values (#tempString)
end
else
insert into #myTempTableVar values (#parameter1)
select * from MyTable where MyColumn in (select param1 from #myTempTableVar)
GO
http://sqlmag.com/t-sql/passing-multivalued-variables-stored-procedure
try this it works!!
CHARINDEX (',' + ColumnName + ',', ',' +
REPLACE(#Parameter, ' ', '') + ',') > 0
execute syntax set #Parameter= 'nc1,nc2'
Is it possible to hide a dynamic query from the result sets provided from a Stored Procedure?
I am using the ##rowcount of the dynamic query to set a variable that is used to determine whether another query runs or not.
The other query is used by code that I cannot change - hence why I am changing the Stored Procedure. The dynamic query returns as the first result set from the Stored Procedure is now the result of the dynamic query which currently is "breaking" the calling code.
Thanks in advance
I have managed to solve this by inserting the result of the dynamic query into a temporary table and then retrieving the rowcount from the temporary table.
-- Create query
declare #query nvarchar(max)
set #query = 'select ' + #entityname + 'id from ' + #entityname + ' where ' + #entityname + 'id = ' + cast(#entityid as nvarchar(100))
-- Insert into to temp table - no new result set displayed!
declare #tbl table (EntityID int not null primary key)
insert into #tbl
exec (#query)
-- Retrieve variable from temporary table
declare #count int
select #count = count(*) from #tbl
Above is the code I ended up using.
Try wrapping the dynamic query like this:
Set #query = 'SELECT COUNT(*) FROM (' + #Query + ') t'