How to get identity value after calling exec(#Sql) - sql-server

I am trying to find the identity value of an inserted record inserted by exec(#Sql), but it seems that exec() excutes in a different scope.
/*
create table [dbo].[__Test](
[id] [int] IDENTITY(1,1) NOT NULL,
[description] [varchar](100) NULL
) ON [PRIMARY]
GO
*/
declare #Sql varchar(512)
set #Sql = 'insert into [dbo].[__Test] ([description]) values (''Some text'')'
exec (#Sql)
select
##IDENTITY as [##IDENTITY],
scope_identity() as [scope_identity],
ident_current('__Test') as [ident_current]
/*
if exists(select * from sys.objects where object_id = object_id('[dbo].[__Test]') and type='U')
drop table [dbo].[__Test]
GO
*/
returns:
##IDENTITY scope_identity ident_current
---------- -------------- -------------
14 NULL 14
and if there is a trigger on __Test, returns:
##IDENTITY scope_identity ident_current
---------- -------------- -------------
6 NULL 14
So ##IDENTITY could be a trigger insert, the execution is not in scope and ident_current() could be from another user.
Is there any way of reliably finding the identity value from an insert made by exec()?

yes, by using sp_executesql:
DECLARE #nSQL NVARCHAR(500)
DECLARE #NewID INTEGER
SET #nSQL = 'INSERT MyTable (MyField) VALUES (123) SELECT #NewID = SCOPE_IDENTITY()'
EXECUTE sp_executesql #nSQL, N'#NewID INTEGER OUTPUT', #NewId OUTPUT
--#NewId now contains the ID
The advantage of sp_executesql is you can parameterise the SQL statement being executed, so you don't have to concentenate values into a string to then be executed.

I had the same problem, but instead of using sp_executesql I've used a more native solution which also works for other data you want to bring outside.
You can pass variables from inside the EXEC to the calling scope like this:
DECLARE #Result AS Table (Value int)
INSERT INTO #Result EXEC('INSERT INTO MyTable(Fields) ''Value''; SELECT ##IDENTITY')
SELECT Value FROM #Result

Related

CURSOR with a stored procedure with OUTPUT parameters

I have to insert one record at a time into a table and then execute a stored procedure by passing output variables.
The insert part actually does do the this and I see two different records into the destination table. But the stored procedure seems to use the same out parameters that were passed for the very first record inserted into destination table. So basically when a stored procedures is being called in loops over the same output parameters over and over again for each distinct ID that is being inserted into the destination table.
In my pseudocode below it prints 3 times the following 5, 10, 15 . Which is correct since it takes each new ID in the dbo.Table_Test. But in my actual code actually it does take only only very first ID that repeats looping over the same ID three times.
-------- CREATING STORED PROCEDURE --------
USE MyDB;
GO
DROP PROCEDURE IF EXISTS dbo.sp_Testing
USE MyDB;
GO
CREATE PROCEDURE dbo.sp_Testing
#QueueId INT,
-- response
#MainId INT OUT, -- this allows null
#MessageTx VARCHAR(500) OUT,
#SuccessIn BIT OUT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT * FROM MyDB.sys.tables WHERE name = 'Table_Test') --print 1
CREATE TABLE dbo.Table_Test
(
ID INT NOT NULL PRIMARY KEY IDENTITY(5,5),
name VARCHAR(10) NULL,
Phone INT NULL,
category VARCHAR(10) NULL
)
INSERT INTO dbo.Table_Test (name)
VALUES ('Andrew')
SET #MainId = SCOPE_IDENTITY()
PRINT #MainId
END
-------- END OR STORED PROCEDURE --------
GO
-------- INSERT STATEMENTS ---------
USE MyDB;
IF OBJECT_ID('tempdb..#MainTable') IS NOT NULL
DROP TABLE #MainTable
IF OBJECT_ID('tempdb..#Queue') IS NOT NULL
DROP TABLE #Queue
DECLARE #MessageTx VARCHAR(30)
DECLARE #SuccessIn BIT
DECLARE #QueueId INT
DECLARE #MainId INT
DECLARE #ParentId INT
SET #MainId = NULL
SET #SuccessIn = 1
CREATE TABLE #MainTable
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
name VARCHAR(10) NULL,
Phone INT NOT NULL,
category VARCHAR(10) NULL
)
INSERT INTO #MainTable (name, Phone, category)
VALUES ('Adam', 123433, 'new'),
('John', 222222, 'new'),
('Samuel', 123123313, 'new')
-- SELECT * FROM #MainTable
-- SELECT * FROM #Queue
-- SELECT * FROM #Test
DECLARE Cursor_test CURSOR LOCAL FOR
SELECT id
FROM #MainTable
-- get relationships for next level
OPEN Cursor_test
FETCH NEXT FROM Cursor_test INTO #ParentId
WHILE ##FETCH_STATUS = 0
BEGIN
IF OBJECT_ID('tempdb..#Queue') IS NOT NULL
DROP TABLE #Queue
CREATE TABLE #Queue
(
PK INT NOT NULL PRIMARY KEY identity (3,2),
ID INT NOT NULL
)
INSERT INTO #Queue (id)
SELECT id
FROM #MainTable
SET #QueueId = SCOPE_IDENTITY()
-- real-time creation
EXEC dbo.sp_Testing #QueueId, #MainId, #MessageTx OUT, #SuccessIn OUT
FETCH NEXT FROM Cursor_test INTO #ParentId
END
CLOSE Cursor_test
DEALLOCATE Cursor_test
This is a bit too long to be in the comment.
Firstly you must understand that the temp table #test only exists within the stored procedure. It is created in your stored procedure and dropped once the stored procedure exits.
So every time you execute the stored procedure, it creates the temp table, when you insert the row, it return the same identity seed value which is 5.

Read INSERT output from stored procedure

I have a following stored procedure
CREATE PROCEDURE [dbo].[InsertCategory]
#Name nvarchar(100)
AS
BEGIN
INSERT INTO [dbo].[Category]([Name])
OUTPUT INSERTED.CategoryId, INSERTED.[Timestamp]
VALUES (#Name)
END
And I call it like this:
EXEC [dbo].[InsertCategory] #Name= #Name
I would like to know what the id of inserted Category is (it is output in insert statement). It is written to the output, but I can't figure out how to assign it to a variable without modifying stored procedure. In C# I can use command.ExecuteReader and I get it, but I do not know how to get it in SQL Server.
I also cannot use SCOPE_IDENTITY as we have our own system of generating ids.
Try this:
-- count use a temp table as well
-- syntax: CREATE TABLE #t(CategoryId int,[Timestamp] datetime)
DECLARE #t table(CategoryId int,[Timestamp] datetime)
INSERT #t(CategoryId, [TimeStamp])
EXEC [dbo].[InsertCategory] #Name= #Name
SELECT CategoryId, [TimeStamp]
FROM #t
You can Declare a table and insert output into it.
CREATE PROCEDURE [dbo].[InsertCategory]
#Name nvarchar(100)
AS
BEGIN
DECLARE #Result AS TABLE (
CategoryId int,
TimeStamp varchar(50)
)
INSERT INTO [dbo].[Category]([Name])
OUTPUT INSERTED.CategoryId, INSERTED.[Timestamp]
INTO #Result(CategoryId, TimeStamp)
VALUES (#Name)
SElect * from #Result
END

Returning Scope_Identity from 2 insert statements simultaneously in SQL Server

I am having problem with my stored procedure:
CREATE PROCEDURE [dbo].[Project]
#Code as nvarChar(255) = null,
#Id as nvarChar(255) = null,
#Status as nvarChar(max) = null,
#Project as nvarChar(max) = null,
#ClientSystem as nvarchar(max) = null,
#UserId as bigint = 0,
#ProjectId as bigint = 0,
#ProjectDetailsId bigint = 0 Output
AS
SET NOCOUNT OFF;
IF NOT EXISTS (SELECT [Code]
FROM [dbo].[Project]
WHERE Project.Code = #Code)
BEGIN
INSERT INTO [dbo].[Project]([Code], [Id], [Status], [Project])
VALUES(#Code, #Id, #Status, #Project)
SELECT #ProjectId = SCOPE_IDENTITY()
INSERT INTO [dbo].[ProjectDetails]([FK_ProjectId], [ClientSystem], [UserId])
VALUES(#ProjectId, #ClientSystem, #UserId)
SELECT #ProjectDetailsId = SCOPE_IDENTITY()
END
ELSE
BEGIN
SELECT [ProjectId] AS 'ProjectId'
FROM [dbo].[Project]
WHERE Project.Code = #Code
END
I want to return Scope_Identity from both Insert statements and pass the values of first insert as parameter to 2nd Insert and return the Scope_Identity of 2nd Insert statement also.
I am getting error is when I get the identity of first Insert, the identity in the specific table increases 2 times like in db table it will be inserted 2 but in coding it will return 1. And that return when i pass to other insert it s giving conflict.
Solution: Instead of using SCOPE IDENTITY(), you need to make use of he OUTPUTclause of the INSERT statement, like this:
INSERT INTO [dbo].[Project]([Code], [Id], [Status], [Project])
OUTPUT inserted.ID into #ProjectID
SELECT ...
Explanation: SCOPE_IDENTITY() returns the value of the last insert, regardless where the insert takes place. So, when when another insert is running in parallel, then your call to SCOPE_IDENTITY() will return the value from the other parallel running procedure. This then leads to an error.
However, the usage of the OUTPUT clause will guarantee to return the value from the current INSERT.
Here is an interesting article regarding SCOPE_IDENTITY and parallel plans:
http://blog.sqlauthority.com/2009/03/24/sql-server-2008-scope_identity-bug-with-multi-processor-parallel-plan-and-solution/
You need use OUTPUT clause at the procedure parameter
#ProjectId as bigint = 0 output,

SQL Server OUTPUT clause

I am a little stuck with why I can not seem to get the 'new identity' of the inserted row with the statement below. SCOPE_IDENTITY() just returns null.
declare #WorkRequestQueueID int
declare #LastException nvarchar(MAX)
set #WorkRequestQueueID = 1
set #LastException = 'test'
set nocount off
DELETE dbo.WorkRequestQueue
OUTPUT
DELETED.MessageEnvelope,
DELETED.Attempts,
#LastException,
GetUtcdate(), -- WorkItemPoisened datetime
DELETED.WorkItemReceived_UTC
INTO dbo.FaildMessages
FROM dbo.WorkRequestQueue
WHERE
WorkRequestQueue.ID = #WorkRequestQueueID
IF ##ROWCOUNT = 0
RAISERROR ('Record not found', 16, 1)
SELECT Cast(SCOPE_IDENTITY() as int)
Any assistance would be most appreciated.
For now I use a workaround this like so.
declare #WorkRequestQueueID int
declare #LastException nvarchar(MAX)
set #WorkRequestQueueID = 7
set #LastException = 'test'
set nocount on
set xact_abort on
DECLARE #Failed TABLE
(
MessageEnvelope xml,
Attempts smallint,
LastException nvarchar(max),
WorkItemPoisened_UTC datetime,
WorkItemReceived_UTC datetime
)
BEGIN TRAN
DELETE dbo.WorkRequestQueue
OUTPUT
DELETED.MessageEnvelope,
DELETED.Attempts,
#LastException,
GetUtcdate(), -- WorkItemPoisened datetime
DELETED.WorkItemReceived_UTC
INTO
#Failed
FROM
dbo.WorkRequestQueue
WHERE
WorkRequestQueue.ID = #WorkRequestQueueID
IF ##ROWCOUNT = 0 BEGIN
RAISERROR ('Record not found', 16, 1)
Rollback
END ELSE BEGIN
insert into dbo.FaildMessages select * from #Failed
COMMIT TRAN
SELECT Cast(SCOPE_IDENTITY() as int)
END
EDITED FEB'2013
#MartinSmith alerts us that this bug don't want be fixed by Microsoft.
"Posted by Microsoft on 2/27/2013 at 2:18 PM Hello Martin, We
investigated the issue and found that changing the behavior is not an
easy thing to do. It would basically require redefining some of the
behavior when both INSERT & OUTPUT INTO target has identity columns.
Given the nature of the problem & the uncommon scenario, we have
decided not to fix the issue. -- Umachandar, SQL Programmability
Team"
EDITED OCT'2012
This is caused by a bug:
Testing bug:
Quoting OUTPUT Clause doc:
##IDENTITY, SCOPE_IDENTITY, and IDENT_CURRENT return identity values
generated only by the nested DML statement, and not those generated by
the outer INSERT statement.
After test it It seems that scope_identity() only works if outer operation is an insert in a table with identity columns:
Test 1: Delete
create table #t ( a char(1) );
create table #d ( a char(1), i int identity );
insert into #t
values ('a'),('b'),('c');
delete #t
output deleted.a into #d;
select SCOPE_IDENTITY(), * from #d;
a i
---- - -
null a 1
null b 2
null c 3
Test 2: Inserting in outer table with identity
create table #t ( a char(1), i int identity );
create table #d ( a char(1), i int identity );
insert into #t
values ('x'),('x'),('x');
insert into #t
output inserted.a into #d
values ('a'),('b');
select scope_identity(), * from #d;
a i
- - -
2 a 1
2 b 2
Test 3: Inserting in outer table without identity
create table #t ( a char(1) );
create table #d ( a char(1), i int identity );
insert into #t
values ('x'),('x'),('x');
insert into #t
output inserted.a into #d
values ('a'),('b');
select scope_identity(), * from #d;
a i
---- - -
null a 1
null b 2
You might try to use a table variable for your output clause, thus allowing you to explicitly insert into FaildMessages:
declare #WorkRequestQueueID int
declare #LastException nvarchar(MAX)
set #WorkRequestQueueID = 1
set #LastException = 'test'
set nocount off
-- Declare a table variable to capture output
DECLARE #output TABLE (
MessageEnvelope VARCHAR(50), -- Guessing at datatypes
Attempts INT, -- Guessing at datatypes
WorkItemReceived_UTC DATETIME -- Guessing at datatypes
)
-- Run the deletion with output
DELETE dbo.WorkRequestQueue
OUTPUT
DELETED.MessageEnvelope,
DELETED.Attempts,
DELETED.WorkItemReceived_UTC
-- Use the table var
INTO #output
FROM dbo.WorkRequestQueue
WHERE
WorkRequestQueue.ID = #WorkRequestQueueID
-- Explicitly insert
INSERT
INTO dbo.FaildMessages
SELECT
MessageEnvelope,
Attempts,
#LastException,
GetUtcdate(), -- WorkItemPoisened datetime
WorkItemReceived_UTC
FROM #output
IF ##ROWCOUNT = 0
RAISERROR ('Record not found', 16, 1)
SELECT Cast(SCOPE_IDENTITY() as int)

SQL Server understand SCOPE_IDENTITY()

I have this piece of code in a stored procedure:
BEGIN
SET #UserId = NULL;
IF (#Username IS NOT NULL)
BEGIN
EXECUTE SP_ADD_USER #Username, #UserId OUTPUT;
END
EXECUTE SP_ADD_ALERT #Name, #AlertType, #AlertId OUTPUT;
INSERT INTO AlertLogs (Datastamp, AlertID, UserID, NotificationMessage)
VALUES (#Datastamp, #AlertId, #UserId, #EmailMessage);
SET #AlertLogId = SCOPE_IDENTITY();
END
#AlertLogId is an output parameter that I want to be assigned to the result of the last insert in AlertLogs table. Do I have to include
INSERT INTO AlertLogs (Datastamp, AlertID, UserID, NotificationMessage)
VALUES (#Datastamp, #AlertId, #UserId, #EmailMessage);
in a new block (a new begin/end scope) in order for SCOPE_IDENTITY() to work correctly ?
(and not report for example the last ID of an inserted record done in SP_ADD_ALERT for example ?)
In your query, SCOPE_IDENTITY() is going to return the last entered identity value into the database, for this scope.
In this instance, it will be the identity for the AlertLogs table, if this has an identity.
A scope is a module: a stored procedure, trigger, function, or batch.
Therefore, two statements are in the same scope if they are in the
same stored procedure, function, or batch.
http://msdn.microsoft.com/en-us/library/ms190315.aspx
You can also use an OUTPUT clause in your insert statement. This means you don't need to worry about scope and you make other (non-identity) information available from the inserted table.
Consider this simple table:
CREATE TABLE [dbo].[SampleTable](
[ID] [int] IDENTITY(1,1) NOT NULL,
[InsertDate] [datetime] NOT NULL,
[Name] [nvarchar](100) NULL
) ON [PRIMARY]
With this default added:
ALTER TABLE [dbo].[SampleTable]
ADD CONSTRAINT [DF_SampleTable_Inserted]
DEFAULT (getdate()) FOR [InsertDate]
You can get values for both the default and the identity from the insert operation.
DECLARE #InsertedDetails TABLE (ID int, InsertDate DateTime);
INSERT INTO SampleTable ([Name])
OUTPUT inserted.ID, inserted.InsertDate
INTO #InsertedDetails
VALUES ('Fred');
DECLARE #ID int;
DECLARE #InsertDate datetime;
SELECT #ID = ID, #InsertDate = InsertDate FROM #InsertedDetails;
PRINT #ID;
PRINT #InsertDate;
Here I've just pulled the values out of the table variable and printed them.

Resources