Convert string from column to parameter in dynamic SQL? - sql

I created a dictionary table with 'Condition' column where I store conditions for particular customers. Table's name is CustomerConditions:
Then I created dynamic SQL where I want to use this Condition:
declare
#TableName as nvarchar(10),
#FieldName as nvarchar(20),
#CustName as Nvarchar(50),
#Condition as NVARCHAR(MAX)
set #Condition = (SELECT o.Condition FROM CustomerConditions o WHERE o.Group = #CustName)
declare #strSQL as NVARCHAR(MAX)
SET #strSQL =
'DECLARE #FieldName as nvarchar(20),
#CustName as nvarchar(50)
;WITH NewCTE AS (
SELECT Tab1.Group, '+#FieldName+'
FROM (
SELECT
'+#Condition+' AS Group,
'+#FieldName+'
FROM '+#TableName+' as c) as Tab1)
SELECT * FROM NewCTE'
EXEC(#strSQL)
The problem is that when I pass #Condition to dynamic SQL string from column 'Condition' does not become part of SQL syntax - it's passed as expression, in the result I got:
This is not what I want. I want this 'Case WHEN...' to become part of my SQL syntax.
On the other hand when I'm not using #Condition but pass 'CASE WHEN..' explicitly then everything works well (all declared parameters works well, all is good):
;WITH NewCTE AS (
SELECT Tab1.Group, '+#FieldName+'
FROM (
SELECT
CASE WHEN '+ #FieldName +' LIKE ''XY%'' OR '+ #FieldName +' LIKE ''XYZ%'' then '''+ #CustName +''' END AS Group,
'+#FieldName+'
FROM '+#TableName+' as c) as Tab1)
So how can I pass this 'Case when' condition into dynamic SQL using parameter?

While agreeing with the issue of dynamic prone to injection attacks, and assuming some random values for your non initialized variables, here is how I approach
The trick is
SELECT #Condition = REPLACE(#Condition, '#FieldName', #FieldName )
SELECT #Condition = REPLACE(#Condition, '#CustName', #CustName )
So the main Query will be
declare
#TableName as nvarchar(10) = 'MyTbl',
#FieldName as nvarchar(20) = 'FldName',
#CustName as Nvarchar(50) = 'C1' ,
#Condition as NVARCHAR(MAX)
SELECT #Condition = (SELECT o.Condition FROM CustomerConditions o WHERE o.[Group] = #CustName)
SELECT #Condition = REPLACE(#Condition, '#FieldName', #FieldName )
SELECT #Condition = REPLACE(#Condition, '#CustName', #CustName )
declare #strSQL as NVARCHAR(MAX)
SET #strSQL = 'DECLARE #FieldName as nvarchar(20),
#CustName as nvarchar(50)
;WITH NewCTE AS (
SELECT Tab1.Group, '+#FieldName+'
FROM (
SELECT
'+#Condition+'
'+#FieldName+'
FROM '+#TableName+' as c) as Tab1)
SELECT * FROM NewCTE'
Select(#strSQL)
First provide proper values to your variables and execute this.
Instead of executing the query, what I did was create the query to see whether it got created as you want.
To be certain, you can copy paste the outcome and execute that.
Then you can change the last line to Execute ...

Related

Stored Procedure That Return Different Table According To Parameter

I want to write a stored procedure that takes #FirmId as a parameter and I will use the related table according to this parameter.
What I want to obtain (but I don't want to use) is something like that:
CREATE PROCEDURE spFirmDetailGetByFirmId
#FirmId AS INT
AS
BEGIN
IF #FirmId = 1
SELECT * FROM Firm1
ELSE IF #FirmId = 2
SELECT * FROM Firm2
.
.
.
.
ELSE IF #FirmId = 1000
SELECT * FROM Firm1000
END
And also I don't want to create query string and then EXEC it, something like that in the fallowing code block. Because the real query is too complex and it will be very hard to manage if I use this option.
CREATE PROCEDURE spFirmDetailGetByFirmId
#FirmId AS INT
AS
BEGIN
DECLARE #Query AS NVARCHAR(MAX) = 'SELECT * FROM Firm'
SET #Query = #Query + CAST(#FirmId AS NVARCHAR(10))
EXEC(#Query)
END
Is there any other option?
Thanks.
I take your Yes the tables are identical and will be kept identical to suggest two approaches:
DECLARE #Firm VARCHAR(10)='Firm3';
SELECT * FROM Firm1 WHERE #Firm='Firm1'
UNION ALL
SELECT * FROM Firm2 WHERE #Firm='Firm2'
UNION ALL
SELECT * FROM Firm3 WHERE #Firm='Firm3'
[...]
UNION ALL
SELECT * FROM Firm1000 WHERE #Firm='Firm1000'
The second is:
DECLARE #query NVARCHAR(MAX)='SELECT * FROM ####';
SET #query=REPLACE(#query,'####',#Firm);
EXEC (#query)
The second could be used with a VIEW (in place of the #query), where you could read the VIEW's definition into the variable and create an ALTER VIEW-statement dynamically... Your procedure would call the same VIEW (but this would crash with parallel calls!)
This code can by use in a stored procedure to automatic create the view, every time you need to add columns
declare #tableId int
declare #columns varchar(max)
declare #tablesCount int
declare #tableName varchar(255)
declare #query varchar(255)
declare #id int
declare #result nvarchar(max)
set #columns = ''
set #tableName = 'Firm'
set #id = 1
set #result = ''
--Base table
select #tableId = object_id from sys.tables where name =#tableName
--Count how many table with the 'same name'
select #tablesCount= count(*) from sys.tables where name like #tableName+'%'
--Build Columns to add in the view
select #columns =#columns+name+', 'from Sys.columns where object_id = #tableId
--Drop View
set #result = 'Drop view vw_'+#tableName
exec sp_executesql #result
set #result=''
while(#id<=#tablesCount)
Begin
declare #idVarchar varchar(10)
set #idVarchar = cast(#id as varchar(10))
set #result =#result+'Select '+#columns+#idVarchar+' as FirmId from '+#tableName+#idVarchar
+'
Union all
'
set #id =#id+1
End
set #result = substring(#result, 1, len(#result)-12)
set #result='Create view vw_'+#tableName+' as
'+#result
exec sp_executesql #result
There is a another choice to this, you can also use sp_helpText to get the current definition of the view and append only add new table identifier

cannot transfer value from a variable to another variable

I have a problem when I would like to transfer the value of a variable to another variable.
declare #column varchar(255);
set #column = 'cheesecake';
declare #tmp varchar(255);
set #tmp = (select #column from TEST where id = 1);
But in this case #tmp won't have the value of the table, but the name of the #column variable. I tried it with dynamic sql, but I got a syntax error.
declare #column varchar(255);
set #column = 'cheesecake';
declare #tmp varchar(255);
set #tmp = exec('select ' + #column + ' from TEST where id = 1');
How can I solve that the #tmp variable would contain the value of the query?
EXEC executes in a different context, therefore the variables cannot be shared without hassle. You specify an output parameter to the statement using sp_executesql
DECLARE
#language nvarchar(255),
#translation nvarchar(255),
#statement nvarchar(255)
SET #language = N'norwegian'
SET #statement = 'select #translation='+#language+' from Translations where id = 1'
EXEC sp_executesql
#statement,
N'#translation nvarchar(255) OUTPUT',
#translation OUTPUT
SELECT #translation
SQLFiddle
AFAIK, it is not possible to directly assign to a variable using `exec. A workaround to your issue would be to create a table variable to store the results of the dynamic query, and then set the value of the second variable using the table variable, like so:
declare #column varchar(255);
set #column = 'cheesecake';
declare #tmp varchar(255);
declare #query nvarchar(255) = N'select ' + #column + N' from TEST where id = 1'
declare #tbl table(tmp varchar(255)) --Intermediate Table variable
insert into #tbl --Insert dynamic query results here
exec sp_executesql #query
select top 1 #tmp = tmp from #tbl --Assign value here
select #tmp
EDIT: I stand corrected. This answer shows how you can assign from dynamic SQL result to a variable by making use of OUTPUT parameters within the dynamic query.

How to store a dynamic SQL result in a variable in T-SQL?

I have a stored procedure which takes 'table name' as parameter. I want to store my 'exec' results to a variable and display using that variable.
Here is my T-SQL stored procedure..
create procedure DisplayTable( #tab varchar(30))
as
begin
Declare #Query VARCHAR(30)
set #Query='select * from ' +#tab
EXEC (#Query)
END
I want to do something like this..
SET #QueryResult = EXEC (#Query)
select #QueryResult
How do i achieve this.. Please help.. I am a beginner..
You can use XML for that. Just add e.g. "FOR XML AUTO" at the end of your SELECT. It's not tabular format, but at least it fulfills your requirement, and allows you to query and even update the result. XML support in SQL Server is very strong, just make yourself acquainted with the topic. You can start here: http://technet.microsoft.com/en-us/library/ms178107.aspx
alter procedure DisplayTable(
#tab varchar(30)
,#query varchar(max) output
)
as
BEGIN
Declare #execution varchar(max) = 'select * from ' +#tab
declare #tempStructure as table (
pk_id int identity
,ColumnName varchar(max)
,ColumnDataType varchar(max)
)
insert into
#tempStructure
select
COLUMN_NAME
,DATA_TYPE
from
INFORMATION_SCHEMA.columns
where TABLE_NAME= #tab
EXEC(#execution)
declare #ColumnCount int = (SELECT count(*) from #tempStructure)
declare #counter int = 1
while #counter <= #ColumnCount
BEGIN
IF #counter = 1
BEGIN
set #query = (SELECT ColumnName + ' ' + ColumnDataType FROM #tempStructure where pk_id= #counter)
END
IF #counter <> 1
BEGIN
set #query = #query + (SELECT ',' + ColumnName + ' ' + ColumnDataType FROM #tempStructure where #counter = pk_id)
END
set #counter = #counter + 1
END
END
When you execute the SP, you'll now get a return of the structure of the table you want.
This should hopefully get you moving.
If you want the table CONTENTS included, create yourself a loop for the entries, and append them to the #query parameter.
Remember to delimit the #query, else when you read it later on, you will not be able to restructure your table.
First of all you have to understand that you can't just store the value of a SELECTon a table in simple variable. It has to be TABLE variable which can store the value of a SELECTquery.
Try the below:
select 'name1' name, 12 age
into MyTable
union select 'name2', 15 union
select 'name3', 19
--declaring the table variable and selecting out of it..
declare #QueryResult table(name varchar(30), age int)
insert #QueryResult exec DisplayTable 'MyTable'
select * from #QueryResult
Hope this helps!

Getting output in a variable from dynamic SQL

I am using a dynamic sql i.e.
DECLARE #searchstring VARCHAR(500)
DECLARE #str VARCHAR(MAX)
SELECT #str = 'SELECT * FROM Table1 WHERE ' + #searchstring
EXECUTE #str
What I need is I want to select one column value from above dynamic sql to pass in a different SP
Let's say I need ID column value and pass it to another sp named GetAnotherData #Ids. How can I do that?
well you can go with Alexander Fedorenko example, but if you don't want to create any temp tables, you can use output xml parameter to pass your ids:
declare #stmt nvarchar(max), #Data xml, #searchstring nvarchar(max)
select #stmt = '
select #Data = (
select id
from Table1
where ' + #searchstring + '
for xml raw(''Data'')
)
'
exec sp_executesql
#stmt = #stmt,
#params = N'#Data xml output',
#Data = #Data output
select
T.C.value('#id', 'int') as id
from #Data.nodes('Data') as T(C)
sql fiddle demo
The following example creates a user-defined table type that has one Id column. Further the INSERT #RetIds EXEC(#STR) statement fills the parameter list, and then passes the values to a stored procedure
CREATE TYPE RetIds AS TABLE
(
Id int
)
DECLARE #searchstring varchar(500) = 'AND SearchCol = 1'
DECLARE #str varchar(max)
SELECT #str ='SELECT Id FROM dbo.test6 WHERE 1 = 1 ' + #searchstring
DECLARE #RetIds AS RetIds
INSERT #RetIds
EXEC(#str)
EXEC dbo.ExecIds #RetIds
See demo on SQLFiddle

SQL SP: Dynamic Query

I am trying get a col and value as a function parameter and planning to use them in a query.
Unfortunately, my value for #Col is being treated like a string and not a column identifier.
ie, if I specify name as a value for the #Col parameter, it will be treated like 'name' instead of name and hence, the call to the function always returns NULL as a result
Have you came across similar situations? Could you please recommand me a better way to deal with this situation?
Here is my Function for your reference:
CREATE FUNCTION [dbo].[FN_FindIdBy]
(
#Col NVARCHAR(255),
#Value NVARCHAR(255)
)
RETURNS INT
AS
BEGIN
DECLARE #Id INT = NULL
SET #Id = (SELECT ID FROM dbo.MYWORK WHERE (CONVERT(NVARCHAR(255), #Col) = #Value))
IF #Id IS NOT NULL RETURN #Id
RETURN NULL
END
Thanks a lot for looking into this.
The following works, but you have to use it as a procedure and create dynamic sql.
create table MYWORK (ID int identity, Name nvarchar(255))
insert into MYWORK(Name)
select 'a'
union select 'b'
union select 'c'
union select 'd'
union select 'e'
union select 'f'
CREATE procedure [dbo].[EPG_FN_FindIdBy]
#Col NVARCHAR(255),
#Value NVARCHAR(255)
AS
BEGIN
DECLARE #Id nvarchar(255)
, #ParmDefinition nvarchar(255)
, #sql nvarchar(max)
set #sql = N'SELECT #IdOUT = ID FROM dbo.MYWORK WHERE '+ #Col +' = ''' + #Value + ''''
set #ParmDefinition = N'#IdOUT nvarchar(255) OUTPUT'
PRINT #sql
EXECUTE sp_executesql #sql,#ParmDefinition, #IdOUT = #Id OUTPUT
SELECT #Id as ID
END
Run this and it'll return the matching row
Exec dbo.EPG_FN_FindIdBy #Col = 'Name', #Value = 'a'
And for a NULL
Exec dbo.EPG_FN_FindIdBy #Col = 'Name', #Value = 'g'
Yeah, there's almost always a better way to query than using dynamic SQL.
Check out this usage of the CASE operator.
SELECT id
FROM dbo.MYWORK
WHERE CASE #Col
WHEN 'a' THEN [a]
WHEN 'b' THEN [b]
WHEN 'c' THEN [c]
END = #Value
Where the table has columns [a], [b] and [c].