Why SCOPE_IDENTITY returns NULL? - sql

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.

Related

##ROWCOUNT shows as 0 when deleting using dynamic query SQL

I am facing a trouble when using dynamic query and when trying to get the number of deleted records using ##ROWCOUNT
Here is my QUery
declare #query nvarchar(max)='delete from '+ #table_name + ' where kfh_id=' + cast(#kfh_id as varchar)
--print #query
exec (#query)
print #query
insert into tbl_cleanup_log (tablename,kfh_id,rows_affected,remark,deletiontime)
values(#table_name,#kfh_id,##ROWCOUNT,#query,getdate())
Here after the dyanimic delete query (inside my cursor) I am trying to store the number of deleted records into another table using ##ROWCOUNT. But it shows as 0.
I didnt understand what I did wrong.
My SQL version is 2012
##ROWCOUNT is working correctly. From the documentation:
Returns the number of rows affected by the last statement. If the number of rows is more than 2 billion, use ROWCOUNT_BIG.
The prior statement to the statement you use ##ROWCOUNT in is print #query and that returns no rows, and hence ##ROWCOUNT returns 0.
To fix this I would suggest PRINTing your dynamic statement first. Also you need to fix your dynamic statement so it isn't open to injection. Don't use the syntax EXEC (#SQL), use a parametrised call to sys.sp_executesql and ensure you properly delimit identify your dynamic object with QUOTENAME:
DECLARE #table_name sysname,
#kfh_id int; --Guessed data type
DECLARE #query nvarchar(MAX) = N'delete from dbo.' + QUOTENAME(#table_name) + N' where kfh_id= #kfh_id;'; --Schema is guessed.
PRINT #query;
EXEC sys.sp_executesql #query, N'#kfh_id int', #kfh_id; --Reminder, guessed #kfh_id data type
INSERT INTO tbl_cleanup_log (tablename,
kfh_id,
rows_affected,
remark,
deletiontime)
VALUES (#table_name, #kfh_id, ##ROWCOUNT, #query, GETDATE());
##ROWCOUNT should be the used immediately after statement, here the PRINT is between and it's changing the result:
DECLARE #row_cnt INT;
EXEC (#query);
SET #row_cnt = ##ROWCOUNT;
print #query;
insert into tbl_cleanup_log (tablename,kfh_id,rows_affected,remark,deletiontime)
values(#table_name,#kfh_id,#row_cnt ,#query,getdate());

Setting SQL Variable via Dynamic SQL

I know I am overthinking this, but I've been banging against this for too long so I'm reaching out for help.
This is the statement I'm trying to run: SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]
BUT, the table name is a variable #reportTable
This doesn't work:
SET #sql = 'SELECT #cntMax = MAX(id) FROM #reportTable'
EXEC sp_executesql #sql
I even tried having the actual table name in the SET #sql and that doesn't work either.
I didn't think it would be this difficult, please tell me I'm missing something easy/obvious.
Here's the full bit of code for those who want it:
DECLARE
#inTable nvarchar(255) = 'Raw_Item',
#reportTable nvarchar(255),
#fieldName nvarchar(255),
#cnt int,
#cntMax int,
#sql nvarchar(max)
SET #reportTable = #inTable + '-FieldReport'
SET #cnt = 1
SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]
PRINT #cntMax
SET #cntMax = 0
SET #sql = 'SELECT #cntMax = MAX(id) FROM [Raw_Item-FieldReport]'
EXEC sp_executesql #sql
PRINT #cntMax
SQL Server 12.0.2008.8 (on Azure)
You need to use an output parameter, otherwise SQL Server has no idea how to connect #cntMax in the dynamic SQL to #cntMax not in the dynamic SQL, since they are different scopes. And to protect yourself from SQL injection (some tips here and here), always check that your object exists, and use QUOTENAME() as opposed to manually adding square brackets (and you should always use QUOTENAME() when building object names from user input or variables, even when they don't have bad characters like dashes):
DECLARE #sql nvarchar(max),
#inTable nvarchar(255) = N'Raw_Item',
#reportTable nvarchar(255);
SET #reportTable = N'dbo.' + QUOTENAME(#inTable + '-FieldReport');
IF OBJECT_ID(#reportTable) IS NOT NULL
BEGIN
SET #sql = N'SELECT #cntMax = MAX(id) FROM ' + #reportTable + N';';
EXEC sys.sp_executesql #sql,
N'#cntMax int output',
#cntMax = #cntMax OUTPUT;
PRINT #cntMax;
END
ELSE
BEGIN
PRINT 'Nice try, h#xx0rs!';
END
Always use schema reference (dbo), always use statement terminators, and please try to avoid naming things with invalid identifier characters like dash (-). And one additional tip: always use N prefix on N'nvarchar string literals'.

Removing NULL with COALESCE in Dynamic 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

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

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;