1373

How can I get the IDENTITY of an inserted row?

I know about @@IDENTITY and IDENT_CURRENT and SCOPE_IDENTITY, but don't understand the implications or impacts attached to each. How do these differ, and when would each be used?

1
  • 13
    INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...), or older method: INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY(); you can get it in c# using ExecuteScalar(). Nov 14, 2016 at 8:11

15 Answers 15

1720
  • @@IDENTITY returns the last identity value generated for any table in the current session, across all scopes. You need to be careful here, since it's across scopes. You could get a value from a trigger, instead of your current statement.

  • SCOPE_IDENTITY() returns the last identity value generated for any table in the current session and the current scope. Generally what you want to use.

  • IDENT_CURRENT('tableName') returns the last identity value generated for a specific table in any session and any scope. This lets you specify which table you want the value from, in case the two above aren't quite what you need (very rare). Also, as @Guy Starbuck mentioned, "You could use this if you want to get the current IDENTITY value for a table that you have not inserted a record into."

  • The OUTPUT clause of the INSERT statement will let you access every row that was inserted via that statement. Since it's scoped to the specific statement, it's more straightforward than the other functions above. However, it's a little more verbose (you'll need to insert into a table variable/temp table and then query that) and it gives results even in an error scenario where the statement is rolled back. That said, if your query uses a parallel execution plan, this is the only guaranteed method for getting the identity (short of turning off parallelism). However, it is executed before triggers and cannot be used to return trigger-generated values.

13
  • 57
    known bug with SCOPE_IDENTITY() returning the wrong values: blog.sqlauthority.com/2009/03/24/… the work around is to not run the INSERT in a Multi Processor Parallel Plan or use the OUTPUT clause
    – KM.
    Jan 28, 2010 at 14:59
  • 6
    Almost every time I have ever wanted 'identity', I have wanted to know the key(s) of the record(s) I just inserted. If that is your situation, you want to use the OUTPUT clause. If you want something else, apply the effort to read and understand bdukes response.
    – jerry
    Feb 15, 2012 at 15:48
  • 7
    With output you don't need to create a temp table to store and query the results. Just leave off the into part of the output clause and it will output them to a resultset.
    – spb
    Jan 6, 2014 at 19:04
  • 142
    To save others from panicing, the bug mentioned above was fixed in Cumulative Update 5 for SQL Server 2008 R2 Service Pack 1. Sep 8, 2014 at 17:09
  • 1
    @niico, I think the recommendation is the same as it has been, which is that OUTPUT is the "best" so long as you aren't using triggers and are handling errors, but SCOPE_IDENTITY is the simplest and very rarely has issues
    – bdukes
    Feb 26, 2019 at 19:07
226

I believe the safest and most accurate method of retrieving the inserted id would be using the output clause.

for example (taken from the following MSDN article)

USE AdventureWorks2008R2;
GO
DECLARE @MyTableVar table( NewScrapReasonID smallint,
                           Name varchar(50),
                           ModifiedDate datetime);
INSERT Production.ScrapReason
    OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
        INTO @MyTableVar
VALUES (N'Operator error', GETDATE());

--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM @MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate 
FROM Production.ScrapReason;
GO
6
  • 3
    Yes this is the correct method going forward, only use one of the others if you are not on SQL Server 2008 (we skipped 2005 so not sure if OUTPUT was available then)
    – HLGEM
    May 20, 2011 at 14:48
  • 1
    @HLGEM There's an MSDN page for OUTPUT in SQL Server 2005, so looks like it's just SQL Server 2000 and earlier that are without it
    – bdukes
    May 20, 2011 at 15:14
  • 12
    For a really concise example to just get the inserted ID, have a look at: stackoverflow.com/a/10999467/2003325
    – Luke
    Jul 29, 2015 at 7:29
  • Your use of INTO with OUTPUT is a good idea. See: blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (From a comment here: stackoverflow.com/questions/7917695/…)
    – shlgug
    Dec 1, 2016 at 17:22
  • I Just learned about this OUTPUT INSERT feature, that looks like the real answer, works very well in sqlserver, but doesn't work with SqlClient class, it throws System.Data.SqlClient.SqlException: 'Cannot find either column "INSERTED" or the user-defined function or aggregate "INSERTED.Id", or the name is ambiguous.', I'm raising a question in other thread so if anybody knows the solution, will be appreciated: stackoverflow.com/questions/49786640/…
    – Yogurtu
    Apr 12, 2018 at 1:31
134

I'm saying the same thing as the other guys, so everyone's correct, I'm just trying to make it more clear.

@@IDENTITY returns the id of the last thing that was inserted by your client's connection to the database.
Most of the time this works fine, but sometimes a trigger will go and insert a new row that you don't know about, and you'll get the ID from this new row, instead of the one you want

SCOPE_IDENTITY() solves this problem. It returns the id of the last thing that you inserted in the SQL code you sent to the database. If triggers go and create extra rows, they won't cause the wrong value to get returned. Hooray

IDENT_CURRENT returns the last ID that was inserted by anyone. If some other app happens to insert another row at an unforunate time, you'll get the ID of that row instead of your one.

If you want to play it safe, always use SCOPE_IDENTITY(). If you stick with @@IDENTITY and someone decides to add a trigger later on, all your code will break.

2
  • What if let's say 2 or 5 users will crate a record at the same time, will SCOPE_IDENTITY() gave us that right record for each user, or?
    – SlavaCa
    Nov 13, 2020 at 0:44
  • 3
    @SlavaCa it returns the right record for each SQL statement, per connection. If you have 5 users creating records at the same time, it is likely that will be 5 different database connections, so each would get their own identity. It works :-) Aug 3, 2021 at 10:03
77

The best (read: safest) way to get the identity of a newly-inserted row is by using the output clause:

create table TableWithIdentity
           ( IdentityColumnName int identity(1, 1) not null primary key,
             ... )

-- type of this table's column must match the type of the
-- identity column of the table you'll be inserting into
declare @IdentityOutput table ( ID int )

insert TableWithIdentity
     ( ... )
output inserted.IdentityColumnName into @IdentityOutput
values
     ( ... )

select @IdentityValue = (select ID from @IdentityOutput)
4
  • 8
    SQL server clustering is a high availability feature and has no bearing on parallelism. It is very uncommon for single row inserts (the most common case for scope_identity()) to get parallel plans anyway. And this bug was fixed more than a year before this answer. Jan 7, 2017 at 18:51
  • What do you mean by parallelism. Nov 6, 2017 at 8:47
  • @MartinSmith The client wasn't willing to allow downtime on their server cluster to install the CU fixing this issue (not joking), so the only solution was for us to rewrite all the SQL to use output instead of scope_identity(). I have removed the FUD about clustering in the answer.
    – Ian Kemp
    Nov 6, 2017 at 12:26
  • 2
    Thank you, this is the only example I've been able to find that shows how to use the value from the output in a variable instead of just outputting it.
    – Sean Ray
    Mar 21, 2018 at 14:50
38

Add

SELECT CAST(scope_identity() AS int);

to the end of your insert sql statement, then

NewId = command.ExecuteScalar()

will retrieve it.

4
  • 1
    Where are you getting NewId from? And what is its declared type? To store command.ExecuteScalar() in it I am assuming it is an Object?
    – TylerH
    Sep 10, 2020 at 22:00
  • @TylerH In this example, the first part is SQL (the select) and the second part is .NET (the ExecuteScaler), so NewId assumed to be variable that the SQL returns to C#.
    – b.pell
    Sep 21, 2021 at 15:10
  • 1
    @b.pell I'm aware the second part is C# and not SQL; I'm asking OP to explain what each part means since they created that part out of thin air, and didn't show how to use it as part of the presumed solution.
    – TylerH
    Sep 21, 2021 at 17:55
  • Thank you for this. $"INSERT INTO PageCacher_Cycles (CycleStart) VALUES ('{DateTime.Now}'); SELECT CAST(scope_identity() AS int)"; worked for me
    – Rodney
    Aug 30, 2023 at 15:07
26

From MSDN

@@IDENTITY, SCOPE_IDENTITY, and IDENT_CURRENT are similar functions in that they return the last value inserted into the IDENTITY column of a table.

@@IDENTITY and SCOPE_IDENTITY will return the last identity value generated in any table in the current session. However, SCOPE_IDENTITY returns the value only within the current scope; @@IDENTITY is not limited to a specific scope.

IDENT_CURRENT is not limited by scope and session; it is limited to a specified table. IDENT_CURRENT returns the identity value generated for a specific table in any session and any scope. For more information, see IDENT_CURRENT.

  • IDENT_CURRENT is a function which takes a table as a argument.
  • @@IDENTITY may return confusing result when you have an trigger on the table
  • SCOPE_IDENTITY is your hero most of the time.
25

When you use Entity Framework, it internally uses the OUTPUT technique to return the newly inserted ID value

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID ]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
   WHERE @@ROWCOUNT > 0

The output results are stored in a temporary table variable, joined back to the table, and return the row value out of the table.

Note: I have no idea why EF would inner join the ephemeral table back to the real table (under what circumstances would the two not match).

But that's what EF does.

This technique (OUTPUT) is only available on SQL Server 2008 or newer.

Edit - The reason for the join

The reason that Entity Framework joins back to the original table, rather than simply use the OUTPUT values is because EF also uses this technique to get the rowversion of a newly inserted row.

You can use optimistic concurrency in your entity framework models by using the Timestamp attribute: 🕗

public class TurboEncabulator
{
   public String StatorSlots;

   [Timestamp]
   public byte[] RowVersion { get; set; }
}

When you do this, Entity Framework will need the rowversion of the newly inserted row:

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID], t.[RowVersion]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
WHERE @@ROWCOUNT > 0

And in order to retrieve this Timestamp you cannot use an OUTPUT clause.

That's because if there's a trigger on the table, any Timestamp you OUTPUT will be wrong:

Action Timestamp
Initial insert 1
OUTPUT clause outputs: 1
Trigger modifies row 2

The returned timestamp will never be correct if you have a trigger on the table. So you must use a separate SELECT.

Second reason you must use the join

And even if you were willing to suffer the incorrect rowversion, the other reason to perform a separate SELECT is that you cannot OUTPUT a rowversion into a table variable:

DECLARE @generated_keys table([Id] uniqueidentifier, [Rowversion] timestamp)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID, inserted.Rowversion INTO @generated_keys
VALUES('Malleable logarithmic casing');

Third reason you must use the join

The third reason to do it is for symmetry. When performing an UPDATE on a table with a trigger, you cannot use an OUTPUT clause. Trying do UPDATE with an OUTPUT is not supported, and will give an error:

The only way to do it is with a follow-up SELECT statement:

UPDATE TurboEncabulators
SET StatorSlots = 'Lotus-O deltoid type'
WHERE ((TurboEncabulatorID = 1) AND (RowVersion = 792))

SELECT RowVersion
FROM TurboEncabulators
WHERE @@ROWCOUNT > 0 AND TurboEncabulatorID = 1

Entity Framework 7 and the Intentional Won't-Fix Crash

Entity Framework 7 introduced a breaking change that causes EF to:

  • crash if the table has a trigger
  • return the wrong rowversion

It's the terrible, wrong-headed, premature optimization, that breaks all existing code over a non-issue.

  • They're throwing developers into the pit of failure
  • rather than throwing them into the pit of success

Everyone's code now contains a unexploded landmine; just waiting for you to accidentally step on it—making it one more thing you have to be sure to go out of your way to turn off in every class, in every project, from nowm until the heat death of the universe. I wish they would stop doing the wrong thing.

1
  • 2
    i imagine they match them to ensure integrity (e.g. in optimistic concurrency mode, while you are selecting from the table variable, someone may have removed the inserter rows). Also, love your TurboEncabulators :)
    – zaitsman
    Nov 23, 2017 at 3:30
23

I can't speak to other versions of SQL Server, but in 2012, outputting directly works just fine. You don't need to bother with a temporary table.

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES (...)

By the way, this technique also works when inserting multiple rows.

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES
    (...),
    (...),
    (...)

Output

ID
2
3
4
2
  • 1
    If you want to use it later though, I imagine you need the temp table Nov 5, 2018 at 20:39
  • @JohnOsborne You are welcome to use a temp table if you like, but my point was that it's not a requirement of OUTPUT. If you don't need the temp table, then your query ends up being much simpler. Nov 5, 2018 at 23:33
17

@@IDENTITY is the last identity inserted using the current SQL Connection. This is a good value to return from an insert stored procedure, where you just need the identity inserted for your new record, and don't care if more rows were added afterward.

SCOPE_IDENTITY is the last identity inserted using the current SQL Connection, and in the current scope -- that is, if there was a second IDENTITY inserted based on a trigger after your insert, it would not be reflected in SCOPE_IDENTITY, only the insert you performed. Frankly, I have never had a reason to use this.

IDENT_CURRENT(tablename) is the last identity inserted regardless of connection or scope. You could use this if you want to get the current IDENTITY value for a table that you have not inserted a record into.

2
  • 3
    You should never use @@identity for this purpose. If someone adds a trigger later, you will lose data integrity. @@identiy is an extremely dangerous practice.
    – HLGEM
    May 20, 2011 at 14:51
  • 1
    "value for a table that you have <<not>> inserted a record into." Really? Feb 26, 2013 at 10:07
15

ALWAYS use scope_identity(), there's NEVER a need for anything else.

6
  • 14
    Not quite never but 99 times out of 100, you'll use Scope_Identity().
    – CJM
    Oct 9, 2009 at 20:44
  • For what have you ever used anything else?
    – erikkallen
    Oct 9, 2009 at 21:31
  • 13
    if you insert several rows with an INSERT-SELECT, you would need to capture the multiple IDs using the OUTPUT clause
    – KM.
    Jan 28, 2010 at 14:48
  • 1
    @KM: Yes, but I referred to scope_identity vs @@identity vs ident_current. OUTPUT is a completely different class and often useful.
    – erikkallen
    Jan 29, 2010 at 11:10
  • 3
    Check out Orry's ( stackoverflow.com/a/6073578/2440976) answer to this question - in parallelism, and just as a best practice, you would be wise to follow his setup... just brilliant!
    – Danimal111
    Sep 1, 2014 at 21:06
3

One other way to guarantee the identity of the rows you insert is to specify the identity values and use the SET IDENTITY_INSERT ON and then OFF. This guarantees you know exactly what the identity values are! As long as the values are not in use then you can insert these values into the identity column.

CREATE TABLE #foo 
  ( 
     fooid   INT IDENTITY NOT NULL, 
     fooname VARCHAR(20) 
  ) 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (1, 
             'one'), 
            (2, 
             'Two') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

INSERT INTO #foo 
            (fooname) 
VALUES      ('Three') 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

-- YOU CAN INSERT  
SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (10, 
             'Ten'), 
            (11, 
             'Eleven') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SELECT * 
FROM   #foo 

This can be a very useful technique if you are loading data from another source or merging data from two databases etc.

2

Create a uuid and also insert it to a column. Then you can easily identify your row with the uuid. Thats the only 100% working solution you can implement. All the other solutions are too complicated or are not working in same edge cases. E.g.:

1) Create row

INSERT INTO table (uuid, name, street, zip) 
        VALUES ('2f802845-447b-4caa-8783-2086a0a8d437', 'Peter', 'Mainstreet 7', '88888');

2) Get created row

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';
5
  • Don't forget to create a index for the uuid in the database. So the row will be found faster.
    – Frank Roth
    Dec 24, 2019 at 11:54
  • For node.js you can use this module to simply create a uuid: https://www.npmjs.com/package/uuid. const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
    – Frank Roth
    Dec 24, 2019 at 12:00
  • 4
    A GUID is not an identity value, it has some backdraws compared to a simple integer.
    – Alejandro
    Mar 26, 2020 at 16:27
  • 1
    Also if the UUID is generated at the SQL table level as a UNIQUEIDENTIFIER datatype with a default of newid() then you will not be able to get it using this method. So you would need to INSERT, leaving the UUID blank and then do the OUTPUT INSERTED.uuid in order to get it Jun 17, 2020 at 21:18
  • FYI - inserts are designed to operate most efficiently when using sequential identity numbers (Specifically the indexes).
    – Jay
    Mar 7, 2022 at 21:16
1

Even though this is an older thread, there is a newer way to do this which avoids some of the pitfalls of the IDENTITY column in older versions of SQL Server, like gaps in the identity values after server reboots. Sequences are available in SQL Server 2016 and forward which is the newer way is to create a SEQUENCE object using TSQL. This allows you create your own numeric sequence object in SQL Server and control how it increments.

Here is an example:

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

Then in TSQL you would do the following to get the next sequence ID:

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

Here are the links to CREATE SEQUENCE and NEXT VALUE FOR

2
  • Sequences have the very same problems of identity, like the gaps (which aren't really problems).
    – Alejandro
    Mar 26, 2020 at 16:29
  • The identity gaps occurred randomly when the SQL Server was restarted. These gaps do not occur in the new SEQUENCE increments, unless the developer does not use the SEQUENCE that is generated, or rolls back a transaction that was to use the next SEQUENCE id. From the online documentation: The sequence object generates numbers according to its definition, but the sequence object does not control how the numbers are used. Sequence numbers inserted into a table can have gaps when a transaction is rolled back, ... or when sequence numbers are allocated without using them in tables.
    – StevenJe
    Jun 22, 2020 at 18:18
-1

Complete solution in SQL and ADO.NET

 const string sql = "INSERT INTO [Table1] (...) OUTPUT INSERTED.Id VALUES (...)";

using var command = connection.CreateCommand();
command.CommandText = sql;
 var outputIdParameter = new SqlParameter("@Id", SqlDbType.Int) { Direction = ParameterDirection.Output };
        command.Parameters.Add(outputIdParameter);

        await connection.OpenAsync();

        var outputId= await command.ExecuteScalarAsync();

        await connection.CloseAsync();
        
        int id = Convert.ToInt32(outputId);
-3

After Your Insert Statement you need to add this. And Make sure about the table name where data is inserting.You will get current row no where row affected just now by your insert statement.

IDENT_CURRENT('tableName')
4
  • 5
    Did you notice this exact same suggestion has been answered several times before?
    – TT.
    Dec 31, 2017 at 7:53
  • yes. but i am trying to describe the solution in my own way. Dec 31, 2017 at 10:09
  • 4
    And if someone else have inserted a row in between your insert statement and your IDENT_CURRENT() call, you'll get the id of the record someone else have inserted - probably not what you want. As noted in most of the replies above - in most cases you should rather use SCOPE_IDENTITY().
    – Trondster
    Mar 9, 2018 at 13:50
  • 1
    @Trondster very well put. I would say either that or set transaction isolation level SERIALIZABLE or again WITH(HOLDLOCK) Apr 29, 2022 at 11:34

Not the answer you're looking for? Browse other questions tagged or ask your own question.