Need help updating a table with variable columns using dynamic SQL - sql

I need help with a piece of code in a stored procedure that returns a dataset to a .NET app containing a variable set of columns--standard columns, plus user-defined columns. My code creates a temp table to hold the dataset and appends the custom columns. The issue I'm having is with the population of these custom columns. (By the way, my code needs to be compatible with Microsoft SQL Server 2008 R2).
I want to use dynamic SQL to put together an Update statement that updates the values of the custom columns in a temp table using SQL snippets stored in a lookup table.
For example, a user might want a custom field that contains an employee's name concatenated a certain way (e.g. "Last Name, First Name"), so the SQL snippet for the custom field would be "LastName + ', ' + FirstName" to concatenate the FistName and LastName fields in the temp table. That's easy. The hard part is when the custom field needs to get populated using a variable passed into the sproc, say to create a unique ID (e.g. The SQL snippet for the custom field is 'Inc_' + CAST(MONTH(#PayrollDate) AS VARCHAR)' to get something like 'Inc_10' if the input parameter #PayrollDate is '10/15/17').
Are still with me? Good, let's do some SQL.
Let's assume we have a temp table with our standard fields and then append a couple of custom fields, as follows (please don't pay attention to my alter table method; in my real sproc, I use dynamic SQL to append the fields, based on saved values in a lookup table).
-- 1. Create the temp table with the standard fields
IF OBJECT_ID('tempdb..#MyTempTable') IS NOT NULL
DROP TABLE #MyTempTable
CREATE TABLE #MyTempTable (
EmpID VARCHAR(50),
FirstName VARCHAR(100),
LastName VARCHAR(100),
EarningsValue MONEY)
-- 2. Add sample earnings data to the temp table
INSERT INTO #MyTempTable (EmpID,FirstName,LastName,EarningsValue) VALUES('1234','Tom','Jones',525.50)
INSERT INTO #MyTempTable (EmpID,FirstName,LastName,EarningsValue) VALUES('4455','Mary','Smith',800.25)
INSERT INTO #MyTempTable (EmpID,FirstName,LastName,EarningsValue) VALUES('9876','Aaron','Lee',200.00)
-- 3. Add the custom fields to the temp table
ALTER TABLE #MyTempTable ADD EmployeeName VARCHAR(100)
ALTER TABLE #MyTempTable ADD BatchID VARCHAR(50)
I know of two methods to execute dynamic SQL to populate my custom fields. The method that works partway is the EXEC(#SQL) method, where you do something like this:
DECLARE #SQL VARCHAR(1000), #CustomField VARCHAR(50), #SQLSnippet VARCHAR(500)
SELECT #CustomField = 'EmployeeName', #SQLSnippet = 'LastName + '','' + FirstName'
SET #SQL = 'UPDATE #MyTempTable SET ' + #CustomField + ' = ' + #SQLSnippet
EXEC(#SQL)
If you were to do PRINT #SQL instead of EXEC(#SQL), you would get:
UPDATE #MyTempTable SET EmployeeName = LastName + ',' + FirstName
If I do SELECT * FROM #MyTempTable, I'll see my 'EmployeeName' custom field populated just fine.
The other dynamic SQL method to use is sp_executesql. However, if I try to do my UPDATE, I get NOTHING, because the it doesn't recognize #CustomField in my SET statement.
DECLARE #NSQL NVARCHAR(1000), #CustomField NVARCHAR(50), #SQLSnippet NVARCHAR(500)
SELECT #CustomField = 'EmployeeName', #SQLSnippet = 'LastName + '','' + FirstName'
SET #NSQL = 'UPDATE #MyTempTable SET #CustomField = #SQLSnippet'
EXECUTE sp_executesql #NSQL,N'#CustomField NVARCHAR(50), #SQLSnippet NVARCHAR(500)',#CustomField,#SQLSnippet
If I do PRINT #NSQL, I get UPDATE #MyTempTable SET #CustomField = #SQLSnippet. Obviously, that doesn't look good, but theoretically the values passed into the #CustomField and #SQLSnippet fields should work, shouldn't it?
At this point, you're thinking, "Steve, why don't you use the EXEC(#SQL) method and forget the sp_executesql nonsense?" Ah, but there's a catch.
In the case where I need to use dynamic SQL using a variable in the SQL snippet, the EXEC(#SQL) method fails. It complains with 'Must declare the scalar variable "#PayrollDate"'. Really? Yeah, really. Here, try it...
DECLARE #SQL VARCHAR(1000), #CustomField VARCHAR(50), #SQLSnippet VARCHAR(500), #PayrollDate SMALLDATETIME
SET #PayrollDate = '10/15/17'
SELECT #CustomField = 'BatchID', #SQLSnippet = '''INC_'' + CAST(MONTH(#PayrollDate) AS VARCHAR)'
SET #SQL = 'UPDATE #MyTempTable SET ' + #CustomField + ' = ' + #SQLSnippet
EXEC(#SQL)
From what I've researched online, only sp_executesql works with parameters in the dynamic SQL. However, I still don't get any results using it. Here's the dynamic SQL using sp_executesql:
DECLARE #NSQL NVARCHAR(1000), #CustomField NVARCHAR(50), #SQLSnippet NVARCHAR(500), #PayrollDate SMALLDATETIME
SET #PayrollDate = '10/15/17'
SELECT #CustomField = 'BatchID', #SQLSnippet = '''INC_'' + CAST(MONTH(#PayrollDate) AS VARCHAR)'
SET #NSQL = 'UPDATE #MyTempTable SET #CustomField = #SQLSnippet'
EXECUTE sp_executesql #NSQL, N'#CustomField NVARCHAR(50),#SQLSnippet NVARCHAR(500),#PayrollDate SMALLDATETIME',#CustomField,#SQLSnippet,#PayrollDate
If you do a SELECT * FROM #MyTempTable, the BatchID custom field is NULL. Grrrr!
So how do I get the blanking dynamic SQL to work properly? Do I use the EXEC(#SQL) method or the sp_executesql method, and how? Much appreciated!

In order to Insert the data into a column, first you need to ADD the column (with its datatype) to the table and because ALTER and UPDATE cannot be in the same batch you will have to use sp_executesql twice. So below is the query which will accomplish what you want.
DECLARE #NSQL NVARCHAR(1000),#ALTSQL NVARCHAR(1000), #CustomField NVARCHAR(50), #CustomFieldDataType NVARCHAR(50), #SQLSnippet NVARCHAR(500), #PayrollDate SMALLDATETIME
SET #PayrollDate = '10/15/17'
SELECT #CustomField = 'BatchID', #CustomFieldDataType = ' NVARCHAR(50)', #SQLSnippet = '''INC_'' + CAST(MONTH(#PayrollDate) AS VARCHAR)'
SET #ALTSQL = 'ALTER TABLE #MyTempTable ADD '+ #CustomField + #CustomFieldDataType
SET #NSQL = 'UPDATE #MyTempTable SET '+ #CustomField +' = '+ #SQLSnippet
EXECUTE sp_executesql #ALTSQL, N'#CustomField NVARCHAR(50), #CustomFieldDataType NVARCHAR(50)',#CustomField, #CustomFieldDataType
EXECUTE sp_executesql #NSQL, N'#CustomField NVARCHAR(50), #SQLSnippet NVARCHAR(500),#PayrollDate SMALLDATETIME',#CustomField, #SQLSnippet,#PayrollDate
SELECT * FROM #MyTempTable

RE:
sp_executeSQL - you cannot pass a column name as a variable. If you want to the column name to come from a variable, you need dynamically construct the query string:
SET #NSQL = 'UPDATE #MyTempTable SET ' + #CustomField + ' = #SQLSnippet'
EXECUTE sp_executesql #NSQL, N'#PayrollDate SMALLDATETIME, #SQLSnippet NVARCHAR(500)',#PayrollDate, #SQLSnippet
What your original query was doing is assigning the values in variable #SQLSnippet to variable #CustomField.

try this
DECLARE #SQL VARCHAR(1000), #CustomField VARCHAR(50), #SQLSnippet VARCHAR(500), #PayrollDate smalldatetime
SET #PayrollDate = '10/15/17'
SELECT #CustomField = 'BatchID', #SQLSnippet = '''INC_' + CAST(MONTH(#PayrollDate) AS VARCHAR)+''''
SET #SQL = 'UPDATE #MyTempTable SET ' + #CustomField + ' = ' + #SQLSnippet
exec(#SQL)

Related

How to send column name as param in stored proc and function in MSSQL

I'm trying to create a stored procedure in mssql (sql server) which takes the params of table name and column name. The stored proc should update given table and column name with base 64 value. I was able to create the function to convert string to base64 in sql server, which is a follows,
ALTER FUNCTION [dbo].[uFnStrToB64]
(
#InputString VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
RETURN (
SELECT
CAST(N'' AS XML).value(
'xs:base64Binary(xs:hexBinary(sql:column("bin")))'
, 'VARCHAR(MAX)'
)
FROM (
SELECT CAST(#InputString AS VARBINARY(MAX)) AS bin
) AS RetVal
)
END;
Now, I'm calling this function in following stored procedure, as follows
ALTER PROCEDURE [dbo].[UpdateTableColumnWithB64]
#tbl sysname,
#Name sysname
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N' UPDATE ' + QUOTENAME(#tbl)
+ N' SET ' + #Name + ' = ' + dbo.uFnStrToB64(#Name)
EXECUTE sp_executesql #SQL
END
But instead of updating the column value with base 64 of the column value, it is replacing it with base 64 of column name. But when I run following update command, it works flawlessly,
UPDATE mytable SET mycolumn=dbo.uFnStrToB64(mycolumn)
I'm calling stored proc as follows,
DECLARE #return_value int
EXEC #return_value = [dbo].[UpdateTableColumnWithB64]
#tbl = mytable,
#Name = mycolumn
SELECT 'Return Value' = #return_value
GO
Adding create and insert table commands, if someone wants to run it and try it,
CREATE TABLE mytable(
mycolumn varchar(max) NULL
);
INSERT INTO mytable(mycolumn) VALUES ('test'), ('test2'), ('test3');
Can somebody help me understand, when I move the update statement to stored procedure why the same update statement takes mycolumn as string instead of getting value from column? I'm open to change function and stored proc Or open for ways to do base 64 conversion in stored proc without usage of function.
Thanks to #dan, following is the solution
SET #SQL = N' UPDATE ' + QUOTENAME(#tbl)
+ N' SET ' + QUOTENAME(#Name)
+ N' = dbo.uFnStrToB64(' + QUOTENAME(#Name)
+ N');';

How to use Table -Valued Parameter with Dynamic qry

I am using Table - Valued Parameter for to build dyNamic Query Using the Following code
AlTER PROCEDURE [dbo].[ABC]
#tblName Varchar(1000),
#Details ABC_TYPE Readonly
AS
BEGIN
Declare #PK as nvarchar(1000)
Declare #SyncFlag as nvarchar(1) ='S'
Declare #SelectCommand as nvarchar(1200)
Declare #tblName2 as nvarchar(1000) ='#Details_N'
Set #PK = 'PK'
Declare #Details_N as table (Pk int)
Insert into #Details_N(Pk)
select PK from #Details
set #SelectCommand = 'Update A ' + ' set A.Sync_Flag ='''+ #SyncFlag + ''' From '+ #tblName + ' A, ' + #tblName2 + ' B ' +
' where A.' + #PK +'='+ 'B.PK'
EXEC sp_executesql #SelectCommand;
This giving me error
Must declare the table variable "#Details_N"
Not finding where my I am doing wrong
Inside dynamic query, you cannot use table variables declared outside. Use temp table instead. Also you have complicated it little too much, here is a cleaner version
DECLARE #SyncFlag AS NVARCHAR(1) ='S'
DECLARE #SelectCommand AS NVARCHAR(1200)
CREATE TABLE #Details_N(Pk INT)
INSERT INTO #Details_N(Pk)
SELECT PK
FROM #Details
SET #SelectCommand = 'Update A ' + ' set A.Sync_Flag = #SyncFlag
From '+ Quotename(#tblName) + ' A
inner join #Details_N B '+ 'on A.PK =' + 'B.PK'
EXEC Sp_executesql
#SelectCommand,
N'#SyncFlag NVARCHAR(1)',
#SyncFlag
Start using INNER JOIN syntax, old style comma separated join is deprecated
The scope of Table Variable is for specific batch (same context) while Temporary table is for SPID. The EXEC command runs in different context. Use temporary tables instead:
Declare #tblName2 as nvarchar(1000) ='#Details_N'
CREATE TABLE #Details_N (Pk int)
Insert into #Details_N(Pk)
select PK from #Details

Passing two values to a stored procedure?

I've written a stored procedure which is called on a link which provides a date value every time and #cg is NULL that time to filter the result on a particular date.
DECLARE #return_value int
EXEC #return_value = [dbo].[Get_Mydata]
#cg = NULL,
#tosearch = '15-05-2014'
SELECT 'Return Value' = #return_value
GO
And after first execution of the stored procedure, it gives some results and using same stored procedure.
I need to filter result by passing below parameter so this time #cg is NOT NULL.
DECLARE #return_value int
EXEC #return_value = [dbo].[Get_Mydata]
#cg = 'CUSTOMER NAME',
#tosearch = 'manish'
SELECT 'Return Value' = #return_value
GO
I'm not able to figure how should I create a dynamic where clause and add it to existing query as well as how to pass value to same parameter which already been passed as date.
More like first getting results for a particular date and then applying like filter on that result. I cannot pass different parameter that's Front end developers requirement.
This is my stored procedure and table data here. http://sqlfiddle.com/#!3/bb917
create proc Get_Mydata
(
#cg varchar(50),
#tosearch varchar(50)
)
as
begin
set nocount on
declare #sqlquery nvarchar(max)
set #sqlquery = N'select q_no, trandate, cust_name from testsp where CONVERT(Date, trandate, 103) = CONVERT(Date, ''' + #tosearch + ''' ,103)';
create table #temp1
(
q_no int,
trandate datetime,
cust_name varchar(50)
)
insert into #temp1(q_no, trandate, cust_name)
exec (#sqlquery)
select * from #temp1 as T;
set nocount off
end
What I have understood is that you want stored procedure to filter results on Date column when you pass null to #cg param and you want to filter results on Cust_name when you pass string 'Cust_Name' to your #Cg Param.
It should be fairly simple, But in any case you do not need a temp table to get the results back its just an over kill of a fairly simple query.
I would do something like this....
Pass the column name to #ColumnName Parameter, and your value to #tosearch parameter. It will build the query depending on what values you pass.
Make sure when you pass a value(Column Name) to #ColumnName.
create proc Get_Mydata
(
#ColumnName varchar(50),
#tosearch varchar(50)
)
as
begin
set nocount on;
declare #sqlquery nvarchar(max);
set #sqlquery = N' select q_no, trandate, cust_name '
+ N' from testsp '
+ N' where ' + QUOTENAME(#ColumnName) + N' = '
+ CASE
WHEN #ColumnName = 'trandate'
THEN N' CAST(#tosearch AS DATE)'
WHEN #ColumnName = 'cust_name'
THEN N' #tosearch'
ELSE N'' END
EXECUTE sp_executesql #sqlquery
,N'#tosearch varchar(50)'
,#tosearch
set nocount off;
end

How to delete the records in local table using sql server

I can able to insert the records into the temp table. From the same temp table i need to delete the records according to another condition
Here is my code :
DECLARE #tempDataTable Table
(
Id bigint,
Title varchar(max),
Forename varchar(max),
Surname varchar(max),
Addr1 varchar(max),
Addr2 varchar(max),
Addr3 varchar(max),
Village varchar(max),
Town varchar(max),
Postcode varchar(max),
LatestER varchar(50),
RowNum bigint
)
DECLARE #sqlQuery varchar(max)
SET #sqlQuery = 'SELECT [Id]
,[Title]
,[ForName]
,[Surname]
,[Addr1]
,[Addr2]
,[Addr3]
,[Village]
,[Town]
,[Postcode]
,[LatestER]
,ROW_NUMBER() OVER(PARTITION BY (DomicileId) ORDER BY Title) as RowNum
FROM [dbo].[DPS_DataRegion_Scotland]
WHERE (' + #searchFilteredDataCondition + ')'
INSERT INTO #tempDataTable
EXEC (#sqlQuery)
The Above part is working fine. Here i need to delete the records from #tempDataTable
SET #sqlQuery = 'DELETE FROM '+ #tempDataTable +'
WHERE (' + #searchFilteredTownCondition + ')'
EXEC (#sqlQuery)
While inserting it throws an error of - Must declare the scalar variable "#tempDataTable".
Your problem is that you need to use the name of the variable in your dynamic SQL, like
SET #sqlQuery = 'DELETE FROM #tempDataTable'
But you won't be able to use your table variable inside dynamic SQL this way - it won't be visible inside the scope of that dynamic SQL execution. You would have to declare everything inside one dynamic SQL batch and use one EXEC or use temporary tables.
The latter would look like:
CREATE TABLE #tempDataTable
(
Id bigint,
Title varchar(max),
Forename varchar(max),
Surname varchar(max),
Addr1 varchar(max),
Addr2 varchar(max),
Addr3 varchar(max),
Village varchar(max),
Town varchar(max),
Postcode varchar(max),
LatestER varchar(50),
RowNum bigint
)
DECLARE #sqlQuery varchar(max)
SET #sqlQuery = 'SELECT [Id]
,[Title]
,[ForName]
,[Surname]
,[Addr1]
,[Addr2]
,[Addr3]
,[Village]
,[Town]
,[Postcode]
,[LatestER]
,ROW_NUMBER() OVER(PARTITION BY (DomicileId) ORDER BY Title) as RowNum
FROM [dbo].[DPS_DataRegion_Scotland]
WHERE (' + #searchFilteredDataCondition + ')'
INSERT INTO #tempDataTable
EXEC (#sqlQuery)
SET #sqlQuery = 'DELETE FROM #tempDataTable WHERE (' + #searchFilteredTownCondition + ')'
EXEC (#sqlQuery)
While inserting it throws an error of - Must declare the scalar variable "#tempDataTable".
Did you ever look at the SQL you are sending to the server? Not the source. Basically print out the SQL.
SET #sqlQuery = 'DELETE FROM '+ #tempDataTable +'
translated into
DELETE FROM (content of #tempDataTable)
if you want to use #tempdataTable as name of a table, make it part of the string, not a parameter.
SET #sqlQuery = 'DELETE FROM #tempDataTable'
I would try to avoid using dynamic SQL unless it is absolutely essential. Your problem is that you are using #tempDataTable as if it is a string variable to build up a dynamic SQL statement.
It is a table variable, therefore you can use it in the same way as an actual table, ie:
DELETE FROM #tempDataTable
Can you give some examples of the values being stored in #searchFilteredTownCondition? It would be easier if you could replace this with column names and comparison values passed in individual variables - then you could dispense with the dynamic SQL.

How to specify a table dynamically in a Stored Procedure

Thanks for the feedback, but I was hoping for help with an UPDATE command, not SELECT.
Can anyone help with the syntax for an UPDATE command?
I am passing a table name into a Stored Procedure but the SQL does not seem to recognize it.
DECLARE #userTable AS VARCHAR(200);
SET #userTable = #currTable
UPDATE #userTable
SET [lang_String] = #lang_String, [date_Changed] = #submitDate1
WHERE (ID = #ID)
#currTable is passed into the Stored Procedure. All tables names are built by design in code.
You can't, you need to build the entire SQL string and then execute it, like this for example:
DECLARE #sql nvarchar(4000)
SELECT #sql = ' SELECT col1, col2, col3 ' +
' FROM dbo.' + quotename(#tblname) +
' WHERE keycol = #key'
EXEC sp_executesql #sql, N'#key varchar(10)', #key
Got this to work quite easily....
#myTable varchar(150)
/* Comments:
*/
AS
SET NOCOUNT ON;
DECLARE #sql varchar(max);
SET #sql = 'SELECT [ID], [StringID], [GUID] FROM ' + #myTable + ' ORDER BY [GUID]';
print (#sql)
EXECUTE(#sql);
SET #langTable = Null;
FYI, the values available for myTable are stored in another table and are not available to users for edit. Table names are built dynamically in code based on a unique combination of values.