Confused by SCOPE_IDENTITY() and GO - sql

I am a bit confused by the documentation & behavior of SCOPE_IDENTITY() in SQL Server.
This page https://learn.microsoft.com/en-us/sql/t-sql/functions/scope-identity-transact-sql?view=sql-server-2017 says this about SCOPE_IDENTITY():
Returns the last identity value inserted into an identity column in
the same scope. A scope is a module: a stored procedure, trigger,
function, or batch. Therefore, if two statements are in the same
stored procedure, function, or batch, they are in the same scope.
And it contains this example
USE AdventureWorks2012;
GO
INSERT INTO Person.ContactType ([Name]) VALUES ('Assistant to the Manager');
GO
SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY];
GO
SELECT ##IDENTITY AS [##IDENTITY];
GO
Which returns
SCOPE_IDENTITY
21
##IDENTITY
21
From the docs I would have thought that the result of SCOPE_IDENTITY() would be NULL because SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY]; is executed in a different batch (because it comes after GO) than the INSERT command... What am I missing here?

I'd agree, I think the documentation is slightly misleading. SCOPE_IDENTITY does retain its value across multiple batches directly executed on the same connection.
But note that if you create an inner batch, by executing EXEC with a string, that inner batch's SCOPE_IDENTITY is independent from your outer batch's SCOPE_IDENTITY
This script produces the value 2, not 5:
create table T1 (ID int IDENTITY(2,1000) not null,Val char(1))
create table T2 (ID int IDENTITY(5,1000) not null, Val char(1))
go
insert into T1(Val) values ('a')
exec('insert into T2(Val) values (''b'')')
select SCOPE_IDENTITY()

Don't use scope_indentity. SQL Server has a much, much better way of returning values from the insert, the OUTPUT clause.
DECLARE #ids TABLE (id INT);
INSERT INTO Person.ContactType ([Name])
OUTPUT inserted.ID INTO #ids -- I'm not sure what the identity is named
VALUES ('Assistant to the Manager');
This has multiple advantages:
You can return more columns than just the id.
You can return the ids from inserts that have more than one row.
You don't have to worry about scoping at all.

Related

How to create a temporary variable equal to the id of the last inserted row

SQL n00b here. Any idea what I'm doing wrong? Hopefully you can figure out what I'm intending to to.
DECLARE #CatId INT;
SET #CatId = (
INSERT INTO Categories (CategoryName) VALUES ('TestCategory');
SELECT SCOPE_IDENTITY()
);
INSERT INTO Fields (CategoryID,FieldName,DisplayName) VALUES (#CatId,'TestName','TestDisplayName');
I'm getting the not-very-detailed error
Incorrect syntax near 'INSERT'
In SQL Server, the right way to handle this is the OUTPUT clause. I would recommend that you learn this method and stick to it.
DECLARE #CatIds TABLE (catid int);
INSERT INTO Categories (CategoryName)
OUTPUT inserted.catid INTO #CatIds;
VALUES ('TestCategory');
INSERT INTO Fields(CategoryID, FieldName, DisplayName)
SELECT CatId, 'TestName', 'TestDisplayName'
FROM #CatIds;
This is preferable to other methods because it explicitly captures the desired information into a table, which can then be further processed. Code is more robust because there is no danger of adding a line of code "in between" and breaking the existing code. It also supports inserts of multiple values at the same time.
You don't even need a variable for this. Remember the KISS principal.
INSERT INTO Categories (CategoryName) VALUES ('TestCategory');
INSERT INTO Fields (CategoryID,FieldName,DisplayName) VALUES (SCOPE_IDENTITY(),'TestName','TestDisplayName');
You can use IDENT_CURRENT
DECLARE #CatId INT
SELECT #CatId = IDENT_CURRENT('Categories')
Then reuse the #CatId in the INSERT
INSERT INTO Fields (CategoryID, FieldName, DisplayName) VALUES (#CatId,'TestName','TestDisplayName');
IDENT_CURRENT returns the last identity value generated for a specific table in any session and any scope.
If you need it to be only scoped to your session you can use SCOPE_IDENTITY

Get inserted colm_ID from table A to used it in table B using stored procedure

I just want to ask how to get latest inserted ID from table A as an example and use it to enter a record in another table B?
If I got your question well, You need to perform two INSERT statements within one Stored Procedure and use the ID generated from the first statement to use it in the second statement, use SCOPE_IDENTITY()
create proc yourproc
(
-- parameter definitions here
)
as
begin
insert into Employee
(FN,LN,Address)
values
(#FN,#LN,#Address)
declare #EmployeeID int
set #EmployeeID = SCOPE_IDENTITY()
insert into EmpContact
(Empid, ContactType, ContactNumber)
values
(#EmployeeID, #ContactType, #ContactNumber)
end
SCOPE_IDENTITY and ##IDENTITY return the last identity values that are
generated in any table in the current session. However, SCOPE_IDENTITY
returns values inserted only within the current scope; ##IDENTITY is
not limited to a specific scope.
SCOPE_IDENTITY (Transact-SQL) - MSDN

Getting the identity of the row I just inserted

I've got two tables that are linked with foreign keys. I need to do a few inserts on one table and then use the identity column values are part of my next batch of insert statements. This all has to be done through SQL.
I've been reading about SCOPE_IDENTITY() but all the examples I'm seeing are using ASP/PHP/Whatever to do the substitution in the next batch of inserts, I dont have this option.
Any pointers appreciated.
Use
SCOPE_IDENTITY() - Function
instead of
##IDENTITY Variable -
otherwise you get the result of the last inserted identity - if there is a trigger on the table than does inserting somewhere you get the wrong result back (the value inserted by the trigger and not by your statement). Scope_Identity returns the identity of your statement and that is what you normally want.
IN SQl Server 2008 you can also use the OUTPUT clause.
DECLARE #output TABLE (myid INT)
INSERT mytable (field1, field2)
OUTPUT inserted.myid INTO #output
VALUES ('test', 'test')
SELECT * FROM #output
What makes this espcially valuable is if you use it to insert a bunch of records instead of one you can get all the identities, You can also return any of the other fields you might need.
something like:
DECLARE #output TABLE (myid INT, field1 datetime)
INSERT mytable (field1, field2)
OUTPUT inserted.myid, inserted.field1 INTO #output
Select '20100101', field3 from mytable2
SELECT * FROM #output
You can do the same with SQL or TSQL. Just assign the identify column as soon as you do the insert.
-- Variable to hold new Id
Declare #NewId int
--Insert some values in to create a new ID
Insert Into dbo.MyTable1 (Col1)
Values (#NewValue)
-- Grab the new Id and store it in the variable
Select #NewId = Scope_Identity()
--Insert the new Id in to another table
Insert Into dbo.AnotherTable (Col1)
Values (#NewId)
You can use
SELECT SCOPE_IDENTITY() [Iden]
After the insert statement in the same block. This should work.

SQL Server - Get Inserted Record Identity Value when Using a View's Instead Of Trigger

For several tables that have identity fields, we are implementing a Row Level Security scheme using Views and Instead Of triggers on those views. Here is a simplified example structure:
-- Table
CREATE TABLE tblItem (
ItemId int identity(1,1) primary key,
Name varchar(20)
)
go
-- View
CREATE VIEW vwItem
AS
SELECT *
FROM tblItem
-- RLS Filtering Condition
go
-- Instead Of Insert Trigger
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
-- RLS Security Checks on inserted Table
-- Insert Records Into Table
INSERT INTO tblItem (Name)
SELECT Name
FROM inserted;
END
go
If I want to insert a record and get its identity, before implementing the RLS Instead Of trigger, I used:
DECLARE #ItemId int;
INSERT INTO tblItem (Name)
VALUES ('MyName');
SELECT #ItemId = SCOPE_IDENTITY();
With the trigger, SCOPE_IDENTITY() no longer works - it returns NULL. I've seen suggestions for using the OUTPUT clause to get the identity back, but I can't seem to get it to work the way I need it to. If I put the OUTPUT clause on the view insert, nothing is ever entered into it.
-- Nothing is added to #ItemIds
DECLARE #ItemIds TABLE (ItemId int);
INSERT INTO vwItem (Name)
OUTPUT INSERTED.ItemId INTO #ItemIds
VALUES ('MyName');
If I put the OUTPUT clause in the trigger on the INSERT statement, the trigger returns the table (I can view it from SQL Management Studio). I can't seem to capture it in the calling code; either by using an OUTPUT clause on that call or using a SELECT * FROM ().
-- Modified Instead Of Insert Trigger w/ Output
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
-- RLS Security Checks on inserted Table
-- Insert Records Into Table
INSERT INTO tblItem (Name)
OUTPUT INSERTED.ItemId
SELECT Name
FROM inserted;
END
go
-- Calling Code
INSERT INTO vwItem (Name)
VALUES ('MyName');
The only thing I can think of is to use the IDENT_CURRENT() function. Since that doesn't operate in the current scope, there's an issue of concurrent users inserting at the same time and messing it up. If the entire operation is wrapped in a transaction, would that prevent the concurrency issue?
BEGIN TRANSACTION
DECLARE #ItemId int;
INSERT INTO tblItem (Name)
VALUES ('MyName');
SELECT #ItemId = IDENT_CURRENT('tblItem');
COMMIT TRANSACTION
Does anyone have any suggestions on how to do this better?
I know people out there who will read this and say "Triggers are EVIL, don't use them!" While I appreciate your convictions, please don't offer that "suggestion".
You could try SET CONTEXT_INFO from the trigger to be read by CONTEXT_INFO() in the client.
We use it the other way to pass info into the trigger but would work in reverse.
Have you in this case tried ##identity? You mentioned both scope_Identity() and identity_current() but not ##identity.

scope_identity vs ident_current

After much research I am a little confused by which identity tracker I should use in sql.
From what I understand scope_identity will give me the last id updated from any table and ident_current will will return the last id from a specified table.
So given that information it would seem to me the best version to use (if you know which table you will be updating) is ident_current. Yet, upon reading it seems most people prefer to use scope_identity. What is the reasoning behind this and is there a flaw in my logic?
In that case you need to write the table name, what happens if you decide to change the table name? You then also must not forget to update your code to reflect that. I always use SCOPE_IDENTITY unless I need the ID from the insert that happens in a trigger then I will use ##IDENTITY
Also the bigger difference is that IDENT_CURRENT will give you the identity from another process that did the insert (in other words last generated identity value from any user)
so if you do an insert and then someone does an insert before you do a SELECT IDENT_CURRENT you will get that other person's identity value
See also 6 Different Ways To Get The Current Identity Value which has some code explaining what happens when you put triggers on the table
From what I've read scope_identity() should be the right answer, however it looks like there is a bug in SQL 2005 and SQL 2008 that can come into play if your insert results in a parallel query plan.
Take a look at the following articles for more details:
##IDENTITY vs SCOPE_IDENTITY() vs IDENT_CURRENT - Retrieve Last Inserted Identity of Record
Article: Six reasons you should be nervous about parallelism
See section titled: 1. #328811, "SCOPE_IDENTITY() sometimes returns incorrect value"
See this blogpost for your answer in detail. Scope_identity will never return identities due to inserts done by triggers. It wont be a great idea to use ident_current in a world of change where tablenames are changed..like in a dev env.
/*
* IDENT_CURRENT returns the last identity value generated for a specific table in any session and any scope.
* ##IDENTITY returns the last identity value generated for any table in the current session, across all scopes.
* SCOPE_IDENTITY returns the last identity value generated for any table in the current session and the current scope.
*/
IF OBJECT_ID(N't6', N'U') IS NOT NULL
DROP TABLE t6 ;
GO
IF OBJECT_ID(N't7', N'U') IS NOT NULL
DROP TABLE t7 ;
GO
CREATE TABLE t6 (id INT IDENTITY) ;
CREATE TABLE t7
(
id INT IDENTITY(100, 1)
) ;
GO
CREATE TRIGGER t6ins ON t6
FOR INSERT
AS
BEGIN
INSERT t7
DEFAULT VALUES
END ;
GO
--End of trigger definition
SELECT id
FROM t6 ;
--IDs empty.
SELECT id
FROM t7 ;
--ID is empty.
--Do the following in Session 1
INSERT t6
DEFAULT VALUES ;
SELECT ##IDENTITY ;
/*Returns the value 100. This was inserted by the trigger.*/
SELECT SCOPE_IDENTITY() ;
/* Returns the value 1. This was inserted by the
INSERT statement two statements before this query.*/
SELECT IDENT_CURRENT('t7') ;
/* Returns 100, the value inserted into t7, that is in the trigger.*/
SELECT IDENT_CURRENT('t6') ;
/* Returns 1, the value inserted into t6 four statements before this query.*/
-- Do the following in Session 2.
SELECT ##IDENTITY ;
/* Returns NULL because there has been no INSERT action
up to this point in this session.*/
SELECT SCOPE_IDENTITY() ;
/* Returns NULL because there has been no INSERT action
up to this point in this scope in this session.*/
SELECT IDENT_CURRENT('t7') ;
/* Returns 100, the last value inserted into t7.*/
The theory says: To be aware of race conditions and to do not care about inserts inside triggers, you should be using SCOPE_IDENTITY() BUT ... there are know bugs on SCOPE_IDENTITY() (and ##IDENTITY) as is mentioned and linked on other anwsers. Here are the workarounds from Microsoft that takes into account this bugs.
Below the most relevant part from the article. It uses output insert's clause:
DECLARE #MyNewIdentityValues table(myidvalues int)
declare #A table (ID int primary key)
insert into #A values (1)
declare #B table (ID int primary key identity(1,1), B int not null)
insert into #B values (1)
select
[RowCount] = ##RowCount,
[##IDENTITY] = ##IDENTITY,
[SCOPE_IDENTITY] = SCOPE_IDENTITY()
set statistics profile on
insert into _ddr_T
output inserted.ID into #MyNewIdentityValues
select
b.ID
from #A a
left join #B b on b.ID = 1
left join #B b2 on b2.B = -1
left join _ddr_T t on t.T = -1
where not exists (select * from _ddr_T t2 where t2.ID = -1)
set statistics profile off
select
[RowCount] = ##RowCount,
[##IDENTITY] = ##IDENTITY,
[SCOPE_IDENTITY] = SCOPE_IDENTITY(),
[IDENT_CURRENT] = IDENT_CURRENT('_ddr_T')
select * from #MyNewIdentityValues
go
SELECT IDENT_CURRENT -- as you said will give you the table specific last inserted identity value. There are issues associated with this, one the user need to have permission to see the metadata otherwise it returns NULL and second you are hardcoding the name of the table , which will cause a problem in case the table name changes.
The best practice is to use Scope_Identity together with a variable ...Look the following example
DECLARE #myFirstTableID INT
DECLARE #mySecondTableID INT
INSERT INTO MYFirstTable (....) VALUES (.....)
SELECT #myFirstTableID =SCOPE_IDENTITY()
INSERT INTO MYSecondTable () VALUES (.....)
SELECT #mySecondTableID=SCOPE_IDENTITY()
Thus by making use of variable and scope_identity next to the insert statement of interest, you can make sure that you are getting the right identity from the right table.
Enjoy