Removing NULL with COALESCE in Dynamic SQL - sql

I have some dynamic SQL as part of a stored procedure I want to execute:
SET #SQL_TXT = 'INSERT INTO ' +#ENTY_TABLE_NAME+
'([ITEM_NAME]
,[ADD_DTT]
,[ADD_USR]
,[UPD_DTT]
,[UPD_USR]
,[ACTIVE_IND]
,[ITEM_PK])
VALUES
('''+#UPD_VALUE+'''
, CURRENT_TIMESTAMP
, '''+#UPD_USR_DOM_NAME+''', CURRENT_TIMESTAMP,'''+#UPD_USR_DOM_NAME+''',''Y'','''+#ITEM_PK+''');
SET #Id = SCOPE_IDENTITY();
RETURN;'
This runs fine, but ITEM_NAME can't be NULL so I want to ad a COALESE():
SET #SQL_TXT = 'INSERT INTO ' +#ENTY_TABLE_NAME+
'(COALESCE([ITEM_NAME], '')
,[ADD_DTT]
,[ADD_USR]
,[UPD_DTT]
,[UPD_USR]
,[ACTIVE_IND]
,[ITEM_PK])
VALUES
('''+#UPD_VALUE+'''
, CURRENT_TIMESTAMP
, '''+#UPD_USR_DOM_NAME+''', CURRENT_TIMESTAMP,'''+#UPD_USR_DOM_NAME+''',''Y'','''+#ITEM_PK+''');
SET #Id = SCOPE_IDENTITY();
RETURN;'
But I am getting this error:
Incorrect syntax near the keyword 'COALESCE'.
Unclosed quotation mark after the character string ');
SET #Id = SCOPE_IDENTITY();
RETURN;'.
Incorrect syntax near '='.
For the life of me I don't see where this ')' is. What am I doing wrong?
Edit: here is the exec
EXECUTE SP_executesql #SQL_TXT, N'#Id INTEGER OUTPUT', #Id OUTPUT

You can not put coalesce() around the column name destination of your insert, you use it around the value being inserted.
SET #SQL_TXT = 'INSERT INTO ' +#ENTY_TABLE_NAME+
'([ITEM_NAME]
,[ADD_DTT]
,[ADD_USR]
,[UPD_DTT]
,[UPD_USR]
,[ACTIVE_IND]
,[ITEM_PK])
VALUES
('''+coalesce(#UPD_VALUE,'')+'''
, CURRENT_TIMESTAMP
, '''+#UPD_USR_DOM_NAME+''', CURRENT_TIMESTAMP,'''+#UPD_USR_DOM_NAME+''',''Y'','''+#ITEM_PK+''');
SET #Id = SCOPE_IDENTITY();
RETURN;'
Note: #UPD_USR_DOM_NAME is inserted into two different columns. Not sure if that is intentional, just thought I would point it out.
You can also fully parameterize the rest of your values for use with sp_executesql instead of concatenating them like that. (Guessing at the data types of your parameters in this example)
declare #sql nvarchar(max);
declare #params nvarchar(max);
declare #id int;
set #sql = N'INSERT INTO ' +#ENTY_TABLE_NAME+'([ITEM_NAME] ,[ADD_DTT] ,[ADD_USR] ,[UPD_DTT] ,[UPD_USR] ,[ACTIVE_IND] ,[ITEM_PK])
VALUES (coalesce(#UPD_VALUE,''), CURRENT_TIMESTAMP, #UPD_USR_DOM_NAME, CURRENT_TIMESTAMP,#UPD_USR_DOM_NAME,''Y'',#ITEM_PK);
SET #Id = SCOPE_IDENTITY();
RETURN;'
set #params = N'#UPD_VALUE varchar(32),#UPD_USR_DOM_NAME varchar(32), #ITEM_PK varchar(32), #Id INTEGER OUTPUT';
EXECUTE SP_executesql #sql, #params, #UPD_Value, #UPD_USER_DOM_NAME, #ITEM_PK, #Id = #Id OUTPUT;
dynamic sql reference:
The curse and blessings of dynamic SQL - Erland Sommarskog
sp_executesql

Related

Dynamic SQL with parameters

I have a SQL query stored in a table that contains parameter names. I need to know how to execute it properly in a stored procedure.
This is my SQL code in the procedure
PROCEDURE [spMassUpdateSKUs]
#SKU AS NVARCHAR(20)
,#FIELD AS NVARCHAR(50)
,#VALUE AS NVARCHAR(50)
,#RESULT as Int = Null Output
AS
BEGIN
IF EXISTS(SELECT CODENUMBER FROM INVENTORY_MASTER WHERE CODENUMBER=#SKU)
BEGIN
DECLARE #SQLQUERY AS NVARCHAR(50)
SET #SQLQUERY=(SELECT SQLUPDATE FROM MASS_UPDATE WHERE DROPDOWNLABEL=#FIELD)
EXEC SP_EXECUTESQL #SQLQUERY
END
and this is the sql query from the table
update inventory_master_flex set departmentid=#value where codenumber=#sku
I've tried replacing with the real parameters but that doen't work.
SELECT REPLACE(REPLACE(#SQLQUERY,'#VALUE',#VALUE),'#SKU',#SKU)
-- 50 is too short for sure; you may try 1000 or different number
DECLARE #SQLQUERY AS NVARCHAR(MAX)
-- for debug purpose
PRINT #SQLQUERY
-- params
EXEC SP_EXECUTESQL #SQLQUERY, N'#Value NVARCHAR(50), #sku NVARCHAR(50)`, #Value, #sku
REPLACE is not good in case of strings with quotes and so on which would brake the #sqlquery code.
Pass the parameters in using sp_executesql, not replace():
IF EXISTS(SELECT CODENUMBER FROM INVENTORY_MASTER WHERE CODENUMBER=#SKU)
BEGIN
DECLARE #SQLQUERY AS NVARCHAR(MAX);
SET #SQLQUERY = (SELECT SQLUPDATE FROM MASS_UPDATE WHERE DROPDOWNLABEL = #FIELD);
EXEC SP_EXECUTESQL #SQLQUERY, N'#SKU VARCHAR(255), #VALUE VARCHAR(255)', #SKU = #SKU, #VALUE = #VALUE
END;
I don't know what the types are. But if one or both are strings or dates, then you would need single quotes in your implementation. However, you are already using sp_executesql so go whole-hog and pass in parameters as well.

Is it possible to supply sp_ExecuteSql parameter names dynamically?

Is it possible to supply the list of parameters to sp_ExecuteSql dynamically?
In sp_ExecuteSql the query and the parameter definitions are strings. We can use string variables for these and pass in any query and parameter definitions we want to execute. However, when assigning values to the parameters, we cannot seem to use strings or string variables for the parameter names.
For example:
DECLARE #SelectedUserName NVARCHAR(255) ,
#SelectedJobTitle NVARCHAR(255);
SET #SelectedUserName = N'TEST%';
SET #SelectedJobTitle = N'%Developer%';
DECLARE #sql NVARCHAR(MAX) ,
#paramdefs NVARCHAR(1000);
SET #sql = N'select * from Users where Name LIKE #UserName '
+ N'and JobTitle LIKE #JobTitle;'
SET #paramdefs = N'#UserName nvarchar(255), #JobTitle nvarchar(255)';
EXEC sp_ExecuteSql #sql, #paramdefs, #UserName = #SelectedUserName,
#JobTitle = #SelectedJobTitle;
The query #sql, and the parameter definitions, #paramdefs, can be passed into sp_ExecuteSql dynamically, as string variables. However, it seems to me that when assigning values to the parameters we cannot assign dynamically and must always know the number of parameters and their names ahead of time. Note in my example how I could declare parameters #UserName and #JobTitle dynamically and pass in that declaration as a string variable, but I had to explicitly specify the parameter names when I wanted to set them. Is there any way around this limitation?
I would like to be able to both declare the parameters dynamically and assign to them dynamically as well. Something like:
EXEC sp_ExecuteSql #sql, #paramdefs,
N'#UserName = #SelectedUserName, #JobTitle = #SelectedJobTitle';
Note that this doesn't actually work but illustrates the sort of thing I'd like to happen. If this sort of thing worked then I could pass in different queries with different numbers of parameters which have different names. The whole thing would be dynamic and I wouldn't have to know the names or numbers of parameters beforehand.
You can do this by using a table valued parameter as the only parameter:
DECLARE #YourQuery NVARCHAR(MAX0 = '<your dynamic query>'
CREATE TYPE dbo.SqlVariantTable AS TABLE
(
[Name] VARCHAR(255),
Type VARCHAR(255),
Value SQL_VARIANT
)
DECLARE #Table SqlVariantTable;
-- Insert your dynamic parameters here:
INSERT INTO #Table
VALUES
('Parameter1', 'VARCHAR(255)', 'some value'),
('Parameter2', 'INT', 3),
DECLARE #ParameterAssignment NVARCHAR(MAX)
SELECT #ParameterAssignment = ISNULL(#ParameterAssignment + ';','') + 'DECLARE ' + Name + ' ' + Type + ' = (SELECT CAST(Value AS ' + Type + ') FROM #p1 WHERE Name = ''' + Name + ''')'
FROM #Table
SET #YourQuery = #ParameterAssignment + ';' + #YourQuery
EXEC SP_EXECUTESQL #YourQuery, N'#p1 SqlVariantTable READONLY', #Table
Now you can simpy insert the parameters into the #Table variable, and they will be present with they original name and type within the query exeuted in the SP_EXECUTESQL. Only make sure you do not use VARCHAR(MAX) or NVARCHAR(MAX) variable types, since they are not supported by SQL_VARIANT. Use (for instance) VARCHAR(4000) instead
I also thought about this and couldn't find anything better than this:
BEGIN
DECLARE
#p1 int, #p2 int, #p3 int, #p4 int...;
DECLARE
#DynamicSQL NVARCHAR(MAX);
SET
#p1 = {some logic},
#p2 = {some different logic},
#p3 = {another logic},
#p4 = {yet another logic},
...;
SET
#DynamicSQL =
N'
some statement
doing
somethin
WHERE
someColumn = #p1
AND someAnotherColumn = #p2
/*no more parameters used below this line*/
';
exec sp_executesql
#stmt = #DynamicSQL,
#params = '#p1 int, #p2 int, #p3 int, #p4 int...'
#p1 = #p1, #p2 = #p2, #p3 = #p3, #p4 = #p4, ...
END;
Notice, that #DynamicSQL uses only 2 out of the 4 possible parameters. Parameters #p1 int, #p2 int, #p3 int, #p4 int... represent the maximum number of parameters you can use in your #DynamicSQL.
You have to have a predefined maximum number of parameters that can be used, and you build the #DynamicSQL statement only with some subset of them. Parameters defined in #params that are not present in the #stmt statement are ignored.
It is not 100 % universal, but I guess that using more than 200 dynamic parameters smells of code smell.
You're trying to work one level too high in abstraction.
Arbitrary parameters requires dynamic SQL, a.k.a. building SQL via strings, which then makes the entire point of parameters moot.
Instead, this should be handled as parameters in the calling code, such as C#, which will allow you to take any SQL statement in a string, apply an arbitrary number of arguments, and execute it.
While this doesn't answer my question I thought it may be useful for others in similar situations. I've discovered the following:
If you have a fixed number of parameters but don't know their names you can pass the parameter values by position rather than name. The following will work:
exec sp_ExecuteSql
#sql,
#paramdefs,
#SelectedUserName, #SelectedJobTitle;
or
exec sp_ExecuteSql
#sql,
#paramdefs,
N'TEST%', N'%Developer%';
Please try this.
Declare #UName varchar(50)
Declare #Job varchar(50)
Set #UName = 'TEST%'
Set #Job = '%Developer%'
exec sp_ExecuteSql #sql, #paramdefs, #UserName = #UName, #JobTitle = #Job;
May this will help you.
Ref From technet.Microsoft.com
Ex.
DECLARE #IntVariable int;
DECLARE #SQLString nvarchar(500);
DECLARE #ParmDefinition nvarchar(500);
/* Build the SQL string one time.*/
SET #SQLString = N'SELECT BusinessEntityID, NationalIDNumber, JobTitle, LoginID
FROM AdventureWorks2012.HumanResources.Employee
WHERE BusinessEntityID = #BusinessEntityID';
SET #ParmDefinition = N'#BusinessEntityID tinyint';
/* Execute the string with the first parameter value. */
SET #IntVariable = 197;
EXECUTE sp_executesql #SQLString, #ParmDefinition,
#BusinessEntityID = #IntVariable;
/* Execute the same string with the second parameter value. */
SET #IntVariable = 109;
EXECUTE sp_executesql #SQLString, #ParmDefinition,
#BusinessEntityID = #IntVariable;
For dynamic you have to pass something like this
EXECUTE sp_executesql N'Select * from Admin WHERE ID = #ID and FirstName=#FName',
N'#ID tinyint, #FName varchar(250)',
#ID = 2, #FName = 'admin';

Procedure to insert Xml Into Sql Server -- Must Declare Scalar Variable

I am using the below procedure to try and insert xml via the filepath into a xml column. I am getting an error must declare scalar variable for ForeignId. Is there a better way of doing what I am trying to do, or am I on the right path?
Here is the procedure
ALTER PROC [dbo].[InsertXml] #path nvarchar(100)
,#ForeignId uniqueidentifier
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQL nvarchar(4000) =
'INSERT INTO XmlTable(XmlId
, ForeignId
, TestXml)
SELECT NEWID()
, #ForeignId
,* FROM OPENROWSET(
BULK ''' + #path + ''',
SINGLE_BLOB) AS x;'
EXECUTE(#SQL);
RETURN ##ERROR;
END
When you're executing the SQL statement using EXECUTE(SQL) it has no access to the #ForeignId value
One way to solve this is to use sp_excuteSQL and do this instead of EXECUTE(#SQL);
DECLARE #ParmDefinition nvarchar(500);
SET #ParmDefinition = N'#ForeignId uniqueidentifier';
EXECUTE sp_executesql #SQL, #ParmDefinition, #ForeignId ;
You could also just concatenate the #ForeignId to your sql string but I can't recall if there are issues with that when using a uniqueidentifier

Why SCOPE_IDENTITY returns NULL?

I have stored procedure that take #dbName and insert record in that DB.
I want to get the id of the last inserted record. SCOPE_IDENTITY() returns NULL and ##IDENTITY returns correct id, why it happens? As I read, so it's better to use SCOPE_IDENTITY() in case there are some triggers on the table.
Can I use IDENT_CURRENT? Does it return the id in the scope of the table, regardless of trigger?
So what is the problem and what to use?
EDITED
DECALRE #dbName nvarchar(50) = 'Site'
DECLARE #newId int
DECLARE #sql nvarchar(max)
SET #sql = N'INSERT INTO ' + quotename(#dbName) + N'..myTbl(IsDefault) ' +
N'VALUES(0)'
EXEC sp_executesql #sql
SET #newId = SCOPE_IDENTITY()
Like Oded says, the problem is that you're asking for the identity before you execute the insert.
As a solution, it's best to run scope_identity as close to the insert as you can. By using sp_executesql with an output parameter, you can run scope_identity as part of the dynamic SQL statement:
SET #sql = N'INSERT INTO ' + quotename(#dbName) + N'..myTbl(IsDefault) ' +
N'VALUES(0) ' +
N'SET #newId = SCOPE_IDENTITY()'
EXEC sp_executesql #sql, N'#newId int output', #newId output
Here's an example at SE Data showing that scope_identity should be inside the sp_executesql.

Getting IDENTITY from dynamic INSERT

In a stored procedure I am dynamically creating a query with a INSERT. This is done in order to force default values (like with #name if it is NULL).
SET #sql = 'INSERT INTO table (username, password'
+ CASE #name IS NULL THEN '' ELSE ',name' END
+ ') VALUES (''root'',''gelehallon''' +
+ CASE #name IS NULL THEN '' ELSE ',''#name''' END
+ ')'
EXEC sp_executesql #sql
SET #id = SCOPE_IDENTITY()
#id will be 0 no matter.
How can I retrieve the IDENTITY in a safe manner even if another thread is running the same stored procedure simultaneously?
SET #sql = 'INSERT INTO table (username, password) VALUES (#username,#pwd)
SELECT #id = SCOPE_IDENTITY()'
EXEC sp_executesql #sql,
N'#username VARCHAR(50), #pwd VARCHAR(50), #id INTEGER OUTPUT',
'root', 'gelehallon', #id OUTPUT
-- #id now has SCOPE_IDENTITY() value in
Though a few points:
- assuming this is a simplified example as there doesn't seem to be a need to use dynamic SQL in this example
- assuming you're not going to store real passwords in plain text in the db!
Alternatively, you can use the OUTPUT clause with the INSERT statement. That will cause the dynamic statement, and, consequently, the system stored procedure used to invoke it, to return a rowset (one row in your case). You can grab at the chance and insert the rowset into a table variable, and then read the value.
Basically, it might look like this:
SET #sql = 'INSERT INTO table (...) OUTPUT inserted.ID VALUES (...)';
DECLARE #ScopeIdentity (ID int);
INSERT INTO #ScopeIdentity
EXEC sp_executesql #sql;
SELECT #id = ID FROM #ScopeIdentity;